fix(chat): capture du final et cause réelle des fins de tour « Le modèle n'a renvoyé aucune réponse. » — #231

This commit is contained in:
2026-08-21 20:45:50 +02:00
parent c1d7f6dd27
commit 3f3eb5938a
4 changed files with 44 additions and 38 deletions

View File

@ -35,6 +35,11 @@ use domain::ports::{
};
use crate::dto::{ReplyChunk, ReplyProgressDto, ReplyProgressKindDto, ReplyProgressStageDto};
/// Visible terminal message when a structured stream ends without the authoritative
/// `ReplyEvent::Final`.
pub const MODEL_RESPONSE_NOT_FINALIZED_MESSAGE: &str =
"La réponse du modèle n'a pas pu être finalisée.";
use crate::stream::TauriChannelSink;
/// Maximum number of recent chat chunks retained for transport reattach replay.
@ -338,3 +343,12 @@ pub fn chunk_from_event(event: ReplyEvent) -> Option<ReplyChunk> {
ReplyEvent::RateLimited { .. } => None,
}
}
/// Terminal wire chunk for an exhausted structured stream that never produced the
/// authoritative `ReplyEvent::Final`.
#[must_use]
pub fn unfinished_turn_chunk() -> ReplyChunk {
ReplyChunk::Error {
message: MODEL_RESPONSE_NOT_FINALIZED_MESSAGE.to_owned(),
}
}

View File

@ -2512,7 +2512,6 @@ pub async fn agent_send(
let meta = state.structured_sessions.meta_for_session(&sid);
std::thread::spawn(move || {
let mut terminal_visible = false;
let mut streamed_text = String::new();
let mut settler = crate::chat::ProgressSettler::default();
for event in stream {
// Non-terminal, sans contenu chat : un signal de limite alimente le service
@ -2543,9 +2542,6 @@ pub async fn agent_send(
let Some(chunk) = crate::chat::chunk_from_event(event) else {
continue;
};
if let ReplyChunk::TextDelta { text } = &chunk {
streamed_text.push_str(text);
}
if matches!(chunk, ReplyChunk::Final { .. } | ReplyChunk::Error { .. }) {
terminal_visible = true;
}
@ -2556,15 +2552,7 @@ pub async fn agent_send(
let _ = bridge.send_output_settled(&sid, &mut settler, chunk);
}
if !terminal_visible {
let chunk = if streamed_text.trim().is_empty() {
ReplyChunk::Error {
message: "Le modèle n'a renvoyé aucune réponse.".to_owned(),
}
} else {
ReplyChunk::Final {
content: streamed_text,
}
};
let chunk = crate::chat::unfinished_turn_chunk();
let _ = bridge.send_output_settled(&sid, &mut settler, chunk);
}
bridge.detach_if(&sid, gen);