Make sure template errors are reported to the LLM and front-end without panicking

This commit is contained in:
Clément Renault 2025-06-11 09:27:14 +02:00
parent 506ee40dc5
commit 77cc3678b5
No known key found for this signature in database
GPG Key ID: F250A4C4E3AE5F5F
4 changed files with 49 additions and 17 deletions

View File

@ -394,6 +394,7 @@ UnimplementedExternalFunctionCalling , InvalidRequest , NOT_IMPL
UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ;
UnimplementedMultiChoiceChatCompletions , InvalidRequest , NOT_IMPLEMENTED ;
ChatNotFound , InvalidRequest , NOT_FOUND ;
InvalidChatSettingDocumentTemplate , InvalidRequest , BAD_REQUEST ;
InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ;
InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ;
InvalidChatCompletionApiVersion , InvalidRequest , BAD_REQUEST ;

View File

@ -646,7 +646,7 @@ async fn handle_meili_tools(
}
let result = match serde_json::from_str(&call.function.arguments) {
Ok(SearchInIndexParameters { index_uid, q }) => process_search_request(
Ok(SearchInIndexParameters { index_uid, q }) => match process_search_request(
index_scheduler,
auth_ctrl.clone(),
search_queue,
@ -655,7 +655,14 @@ async fn handle_meili_tools(
q,
)
.await
.map_err(|e| e.to_string()),
{
Ok(output) => Ok(output),
Err(err) => {
let error_text = err.to_string();
tx.send_error(&StreamErrorEvent::from_response_error(err)).await?;
Err(error_text)
}
},
Err(err) => Err(err.to_string()),
};
@ -664,7 +671,6 @@ async fn handle_meili_tools(
if report_sources {
tx.report_sources(resp.clone(), &call.id, &documents).await?;
}
text
}
Err(err) => err,

View File

@ -1,5 +1,6 @@
use async_openai::error::{ApiError, OpenAIError};
use async_openai::reqwest_eventsource::Error as EventSourceError;
use meilisearch_types::error::ResponseError;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@ -53,12 +54,13 @@ pub struct StreamError {
}
impl StreamErrorEvent {
const ERROR_TYPE: &str = "error";
pub async fn from_openai_error(error: OpenAIError) -> Result<Self, reqwest::Error> {
let error_type = "error".to_string();
match error {
OpenAIError::Reqwest(e) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "internal_reqwest_error".to_string(),
code: Some("internal".to_string()),
@ -69,7 +71,7 @@ impl StreamErrorEvent {
}),
OpenAIError::ApiError(ApiError { message, r#type, param, code }) => {
Ok(StreamErrorEvent {
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
event_id: Uuid::new_v4().to_string(),
error: StreamError {
r#type: r#type.unwrap_or_else(|| "unknown".to_string()),
@ -82,7 +84,7 @@ impl StreamErrorEvent {
}
OpenAIError::JSONDeserialize(error) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "json_deserialize_error".to_string(),
code: Some("internal".to_string()),
@ -100,7 +102,7 @@ impl StreamErrorEvent {
Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError { r#type, code, message, param, event_id: None },
})
}
@ -111,13 +113,13 @@ impl StreamErrorEvent {
Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError { r#type, code, message, param, event_id: None },
})
}
EventSourceError::Utf8(error) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "invalid_utf8_error".to_string(),
code: None,
@ -128,7 +130,7 @@ impl StreamErrorEvent {
}),
EventSourceError::Parser(error) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "parser_error".to_string(),
code: None,
@ -139,7 +141,7 @@ impl StreamErrorEvent {
}),
EventSourceError::Transport(error) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "transport_error".to_string(),
code: None,
@ -150,7 +152,7 @@ impl StreamErrorEvent {
}),
EventSourceError::InvalidLastEventId(message) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "invalid_last_event_id".to_string(),
code: None,
@ -161,7 +163,7 @@ impl StreamErrorEvent {
}),
EventSourceError::StreamEnded => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "stream_ended".to_string(),
code: None,
@ -173,7 +175,7 @@ impl StreamErrorEvent {
},
OpenAIError::InvalidArgument(message) => Ok(StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: error_type,
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "invalid_argument".to_string(),
code: None,
@ -184,4 +186,19 @@ impl StreamErrorEvent {
}),
}
}
pub fn from_response_error(error: ResponseError) -> Self {
let ResponseError { code, message, .. } = error;
StreamErrorEvent {
event_id: Uuid::new_v4().to_string(),
r#type: Self::ERROR_TYPE.to_string(),
error: StreamError {
r#type: "response_error".to_string(),
code: Some(code.as_str().to_string()),
message,
param: None,
event_id: None,
},
}
}
}

View File

@ -9,7 +9,7 @@ use async_openai::types::{
FunctionCall, FunctionCallStream, Role,
};
use bumpalo::Bump;
use meilisearch_types::error::ResponseError;
use meilisearch_types::error::{Code, ResponseError};
use meilisearch_types::heed::RoTxn;
use meilisearch_types::milli::index::ChatConfig;
use meilisearch_types::milli::prompt::{Prompt, PromptData};
@ -237,7 +237,15 @@ pub fn format_documents<'doc>(
Some(doc) => doc,
None => unreachable!("Document with internal ID {docid} not found"),
};
let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap();
let text = match prompt.render_document(&external_docid, document, &gfid_map, doc_alloc) {
Ok(text) => text,
Err(err) => {
return Err(ResponseError::from_msg(
err.to_string(),
Code::InvalidChatSettingDocumentTemplate,
))
}
};
renders.push(text);
}