Reject incomplete AI completions before showing generated answers
This commit is contained in:
1 parent
8c005f5f57
commit
7c7cecb5ab
3 files changed
+53
No files matched your search
@@ -165,6 +165,15 @@ impl AiClient {
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(AiError::InvalidResponse("AI endpoint returned no choices"))?;
|
||||
if choice
|
||||
.finish_reason
|
||||
.as_deref()
|
||||
.is_some_and(|reason| reason != "stop")
|
||||
{
|
||||
return Err(AiError::InvalidResponse(
|
||||
"AI endpoint did not finish a complete answer",
|
||||
));
|
||||
}
|
||||
let content = choice.message.content.ok_or(AiError::InvalidResponse(
|
||||
"AI endpoint returned no textual content",
|
||||
))?;
|
||||
@@ -351,6 +360,9 @@ struct Completion {
|
||||
#[derive(Deserialize)]
|
||||
struct Choice {
|
||||
message: Message,
|
||||
// Some compatible local servers omit this. When present, incomplete,
|
||||
// filtered or tool-directed completions must not appear as finished drafts.
|
||||
finish_reason: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Message {
|
||||
|
||||
@@ -20,6 +20,39 @@ fn completion(answer: &str, citations: Vec<&str>) -> Vec<u8> {
|
||||
serde_json::to_vec(&serde_json::json!({"choices":[{"message":{"content":serde_json::json!({"answer":answer,"citations":citations}).to_string()}}]})).unwrap()
|
||||
}
|
||||
type Wire = Arc<Mutex<Option<(String, Vec<u8>)>>>;
|
||||
|
||||
#[test]
|
||||
fn incomplete_or_filtered_completions_are_not_accepted_as_finished_answers() {
|
||||
for reason in ["length", "content_filter", "tool_calls", "unexpected"] {
|
||||
let mut body: serde_json::Value = serde_json::from_slice(&completion(
|
||||
"Sixty days written notice. [selected]",
|
||||
vec!["selected"],
|
||||
))
|
||||
.unwrap();
|
||||
body["choices"][0]["finish_reason"] = serde_json::json!(reason);
|
||||
let (base, _, worker) = server(200, serde_json::to_vec(&body).unwrap(), Duration::ZERO);
|
||||
let result = AiClient::new(config(base)).unwrap().generate(&request());
|
||||
worker.join().unwrap();
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
AiError::InvalidResponse("AI endpoint did not finish a complete answer")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_stop_completion_is_accepted() {
|
||||
let mut body: serde_json::Value = serde_json::from_slice(&completion(
|
||||
"Sixty days written notice. [selected]",
|
||||
vec!["selected"],
|
||||
))
|
||||
.unwrap();
|
||||
body["choices"][0]["finish_reason"] = serde_json::json!("stop");
|
||||
let (base, _, worker) = server(200, serde_json::to_vec(&body).unwrap(), Duration::ZERO);
|
||||
let result = AiClient::new(config(base)).unwrap().generate(&request());
|
||||
worker.join().unwrap();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
fn server(status: u16, body: Vec<u8>, delay: Duration) -> (String, Wire, thread::JoinHandle<()>) {
|
||||
server_with_length(status, body, delay, true)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user