修复: 后端若干bug
This commit is contained in:
@@ -168,7 +168,13 @@ pub(crate) async fn save_conversation(
|
||||
let mut session = session_arc.lock().await;
|
||||
let mut msgs = session.conv(conv_id).messages.all_messages_clone();
|
||||
for m in &mut msgs {
|
||||
m.content = truncate_for_persist(&m.content);
|
||||
// P0-2:tool result 是结构化 JSON(provider 读工具返回原样),
|
||||
// 中段截断会插裸换行+中文省略标记破坏 JSON 结构致 reload/重发 parse FAIL
|
||||
// (实测 tool result read 大文件后落库 Invalid control character @pos3072)。
|
||||
// tool result 体量已由 read_file limit 控源,此处跳过 content 截断,仅截 assistant/user 文本。
|
||||
if !matches!(m.role, df_ai::provider::MessageRole::Tool) {
|
||||
m.content = truncate_for_persist(&m.content);
|
||||
}
|
||||
// F-260614-05 Phase 2a: parts(Image base64) 同样截断(替换占位 Text 片),
|
||||
// 防大体量图把对话 JSON 撑爆。仅作用于持久化副本,不污染内存真相源。
|
||||
if let Some(parts) = m.parts.as_ref() {
|
||||
@@ -434,4 +440,68 @@ mod tests {
|
||||
fn truncate_parts_empty_returns_none() {
|
||||
assert_eq!(truncate_parts_for_persist(&[]), None);
|
||||
}
|
||||
|
||||
// ---------- P0-2: tool role content 不截断(防 JSON 结构破坏) ----------
|
||||
|
||||
/// 辅助:构造指定 role + content 的 ChatMessage,用 provider 全路径类型
|
||||
fn make_msg(role: df_ai::provider::MessageRole, content: &str) -> df_ai::provider::ChatMessage {
|
||||
df_ai::provider::ChatMessage {
|
||||
id: None,
|
||||
role,
|
||||
content: content.to_string(),
|
||||
parts: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
model: None,
|
||||
status: None,
|
||||
reasoning_content: None,
|
||||
timestamp: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// save_conversation 的 content 截断决策片段(抽测 role 分支,避免起 DB/锁)。
|
||||
/// 对 tool role 跳过截断,其余 role 走截断。
|
||||
fn truncate_decision(m: &mut df_ai::provider::ChatMessage) {
|
||||
if !matches!(m.role, df_ai::provider::MessageRole::Tool) {
|
||||
m.content = truncate_for_persist(&m.content);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_role_large_content_not_truncated() {
|
||||
// tool result 是结构化 JSON,中段截断插裸换行+省略标记会破坏 JSON parse。
|
||||
// >8192(阈值)的 tool content 必须原样保留(不进 truncate_for_persist)。
|
||||
let json_like = format!(
|
||||
"{{\"file\":\"x.rs\",\"content\":\"{}\"}}",
|
||||
"a".repeat(TRUNCATE_THRESHOLD + 2000)
|
||||
);
|
||||
let original = json_like.clone();
|
||||
let mut m = make_msg(df_ai::provider::MessageRole::Tool, &json_like);
|
||||
truncate_decision(&mut m);
|
||||
// 关键断言:tool role 不截断,原样保留(长度与内容完全一致)
|
||||
assert_eq!(m.content.len(), original.len(), "tool role content 不应被截断");
|
||||
assert_eq!(m.content, original, "tool role content 原样保留");
|
||||
// 不含截断标记(证明未进 truncate_for_persist)
|
||||
assert!(!m.content.contains("已截断"), "tool role 不应含截断标注");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assistant_role_large_content_still_truncated() {
|
||||
// 对照:assistant role 超阈值仍截断(回归保护)
|
||||
let long: String = "x".repeat(TRUNCATE_THRESHOLD + 1000);
|
||||
let mut m = make_msg(df_ai::provider::MessageRole::Assistant, &long);
|
||||
truncate_decision(&mut m);
|
||||
assert!(m.content.contains("已截断"), "assistant role 超阈值应截断");
|
||||
assert!(m.content.len() < long.len(), "assistant role 截断后应变短");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_role_short_content_unchanged() {
|
||||
// 短 tool content:即使进了 truncate_for_persist 也不会改(阈值以下),
|
||||
// 但此处验证 role 分支本身对短 tool content 也安全(原样保留)。
|
||||
let short = r#"{"ok":true,"rows":3}"#;
|
||||
let mut m = make_msg(df_ai::provider::MessageRole::Tool, short);
|
||||
truncate_decision(&mut m);
|
||||
assert_eq!(m.content, short);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user