feat: Handle empty Ollama responses gracefully
Introduce `OllamaError::EmptyResponse` to represent cases where the Ollama model returns an empty result due to silence rules or lack of keywords. This change prevents treating an empty response as a parse error and logs it at a lower severity level. A new integration test verifies this behavior.
This commit is contained in:
@@ -322,7 +322,9 @@ pub async fn handle_case_page(
|
||||
let is_admin = user.is_admin();
|
||||
|
||||
let most_recent = recordings.last().map(|r| r.filename.as_str());
|
||||
let recorded_at_iso = most_recent.map(recorded_at_iso_of).filter(|s| !s.is_empty());
|
||||
let recorded_at_iso = most_recent
|
||||
.map(recorded_at_iso_of)
|
||||
.filter(|s| !s.is_empty());
|
||||
let time_hms_utc = most_recent.map(extract_hhmm_utc).unwrap_or_default();
|
||||
|
||||
CasePageTemplate {
|
||||
|
||||
@@ -30,8 +30,15 @@ const MAX_CHARS: usize = 60;
|
||||
#[derive(Debug)]
|
||||
pub enum OllamaError {
|
||||
Http(reqwest::Error),
|
||||
Status { status: u16, body: String },
|
||||
Status {
|
||||
status: u16,
|
||||
body: String,
|
||||
},
|
||||
Parse(String),
|
||||
/// Model obeyed the silence rule: transcript had no extractable keyword,
|
||||
/// so the response was empty. Not a failure — callers should treat this
|
||||
/// as "no oneliner this round" and log below error level.
|
||||
EmptyResponse,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OllamaError {
|
||||
@@ -40,6 +47,10 @@ impl std::fmt::Display for OllamaError {
|
||||
Self::Http(e) => write!(f, "ollama http error: {e}"),
|
||||
Self::Status { status, body } => write!(f, "ollama returned {status}: {body}"),
|
||||
Self::Parse(msg) => write!(f, "ollama response parse error: {msg}"),
|
||||
Self::EmptyResponse => write!(
|
||||
f,
|
||||
"ollama response was empty (no medical keyword in transcript)"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +143,7 @@ pub async fn generate_oneliner(
|
||||
|
||||
let normalized = normalize(&content);
|
||||
if normalized.is_empty() {
|
||||
return Err(OllamaError::Parse("empty after normalization".into()));
|
||||
return Err(OllamaError::EmptyResponse);
|
||||
}
|
||||
Ok(normalized)
|
||||
}
|
||||
|
||||
@@ -229,6 +229,12 @@ pub(crate) async fn update_oneliner(
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(ollama::OllamaError::EmptyResponse) => {
|
||||
warn!(
|
||||
case = %case_dir.display(),
|
||||
"oneliner skipped — model returned empty (no medical keyword in transcript)"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(case = %case_dir.display(), error = %e, "oneliner generation failed");
|
||||
}
|
||||
|
||||
@@ -500,6 +500,31 @@ async fn ollama_oneliner_sends_keep_alive_and_model() {
|
||||
// Mock::expect(1) + server drop verifies the matcher hit exactly once.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ollama_oneliner_empty_response_when_model_obeys_silence_rule() {
|
||||
// Model followed the silence rule for a keyword-less transcript and
|
||||
// returned empty content. That's a valid outcome, not a parse error.
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/chat"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(ok_chat_body(" \n ")))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let err = generate_oneliner(
|
||||
&client,
|
||||
&server.uri(),
|
||||
MODEL,
|
||||
0,
|
||||
"äh, Moment",
|
||||
Duration::from_secs(5),
|
||||
)
|
||||
.await
|
||||
.expect_err("expected EmptyResponse");
|
||||
assert!(matches!(err, OllamaError::EmptyResponse), "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ollama_oneliner_parse_error_on_missing_content() {
|
||||
let server = MockServer::start().await;
|
||||
|
||||
Reference in New Issue
Block a user