优化: tool_result截断50KB→8KB治GLM1214(conversation.rs持久化视图)
This commit is contained in:
@@ -57,12 +57,18 @@ pub(crate) fn accumulate_tokens(old: Option<i64>, add: u32) -> Option<i64> {
|
||||
///
|
||||
/// 防 read_file 1MB 洞 / list_directory 大体量结果落库后每轮重发累积致 token 暴增
|
||||
/// (Sprint 19 实测单对话 in=115万 / 消息体 1.6MB)。仅作用于持久化视图,不污染内存真相源。
|
||||
pub(crate) const TRUNCATE_THRESHOLD: usize = 50_000;
|
||||
const TRUNCATE_HEAD: usize = 20_000;
|
||||
const TRUNCATE_TAIL: usize = 20_000;
|
||||
///
|
||||
/// B-260619-02:50KB → 8KB 收紧。治 GLM 1214 messages 非法根因——GLM anthropic 端点对
|
||||
/// 单条 tool_result content 限制更严(实测 ~10-20KB 即拒 1214),read_file 默认前 500 行源码
|
||||
/// 约 11-21KB,旧 50KB 阈值未触发→DB 存全量→reload/下轮重发→GLM 1214 整请求失败。
|
||||
/// 8KB 对齐 GLM 单条上限留余量;截断后 LLM 仍能据截断片段推理(优于 1214 直接拒收)。
|
||||
pub(crate) const TRUNCATE_THRESHOLD: usize = 8_192;
|
||||
const TRUNCATE_HEAD: usize = 3_072;
|
||||
const TRUNCATE_TAIL: usize = 3_072;
|
||||
|
||||
/// 落库前对超长 content 做截断(保留头尾各 ~20KB + 中段标注省略字符数)。
|
||||
/// 50KB 阈值以下原样返回(零开销);按字符而非字节切避免 UTF-8 切坏中文。
|
||||
/// 落库前对超长 content 做截断(保留头尾各 ~3KB + 中段标注省略字符数 + 总字节数)。
|
||||
/// 8KB 阈值以下原样返回(零开销);按字符而非字节切避免 UTF-8 切坏中文。
|
||||
/// 截断提示含原始字节数,让 LLM 明确感知非全文(防它误以为已看全)。
|
||||
pub(crate) fn truncate_for_persist(content: &str) -> String {
|
||||
let chars: Vec<char> = content.chars().collect();
|
||||
if chars.len() <= TRUNCATE_THRESHOLD {
|
||||
@@ -72,15 +78,15 @@ pub(crate) fn truncate_for_persist(content: &str) -> String {
|
||||
let tail: String = chars[chars.len() - TRUNCATE_TAIL..].iter().collect();
|
||||
let omitted = chars.len() - TRUNCATE_HEAD - TRUNCATE_TAIL;
|
||||
format!(
|
||||
"{}\n\n[...省略 {} 字符(已截断,完整内容仅在内存态可读)...]\n\n{}",
|
||||
head, omitted, tail
|
||||
"{}\n\n[...省略 {} 字符(已截断,原 {} 字节,仅显示前 8KB,完整内容仅在内存态可读)...]\n\n{}",
|
||||
head, omitted, content.len(), tail
|
||||
)
|
||||
}
|
||||
|
||||
/// 落库前对 `ChatMessage.parts` 做截断(F-260614-05 Phase 2a)。
|
||||
///
|
||||
/// - `Text` 片:复用 `truncate_for_persist` 头尾截断语义(单 Text 片 > 50KB 才截)。
|
||||
/// - `Image` 片:base64 通常已 50KB+,落库前替换为占位 `Text` 片
|
||||
/// - `Text` 片:复用 `truncate_for_persist` 头尾截断语义(单 Text 片 > 8KB 才截)。
|
||||
/// - `Image` 片:base64 通常已 8KB+,落库前替换为占位 `Text` 片
|
||||
/// `<image: base64 已省略, 共 N 字节>`,避免大体量图把对话 JSON 撑爆
|
||||
/// (一张 1MB PNG 的 base64 ≈ 1.3MB 字符串)。原 Image 片仅在内存真相源(ContextManager)
|
||||
/// 保留,重发时仍带图——对齐现有「持久化视图不污染内存真相源」约定。
|
||||
@@ -149,9 +155,10 @@ pub(crate) async fn save_conversation(
|
||||
touch_updated_at: bool,
|
||||
) {
|
||||
// 取 messages + 懒创建首次落库所需的 provider_id/created_at
|
||||
// 工具结果(content)超 50KB 时截断头尾各 ~20KB + 中段标注,防大体量结果(read_file 1MB 洞 /
|
||||
// list_directory 13782 项)落库后每轮重发累积致 token 暴增。仅影响持久化视图,不污染
|
||||
// 内存真相源(ContextManager)——build_for_request 仍读全量 messages。
|
||||
// 工具结果(content)超 8KB(B-260619-02:50KB→8KB,治 GLM 1214)时截断头尾各 ~3KB + 中段
|
||||
// 标注(含原字节数),防大体量结果(read_file 1MB 洞 / list_directory 13782 项)落库后每轮重发
|
||||
// 累积致 token 暴增。仅影响持久化视图,不污染内存真相源(ContextManager)——build_for_request
|
||||
// 仍读全量 messages。
|
||||
//
|
||||
// F-260616-09 B 批4:per_conv.messages 唯一真相源(conv_id 来源:本函数入参,与 save 落库的 conv 一致)。
|
||||
// loop 内 save 由 run_agentic_loop 入参 conv_id 透传;IPC 路径(commands.rs)save 也传 conv_id。
|
||||
@@ -354,9 +361,11 @@ mod tests {
|
||||
// 头尾各 20k 字符应在结果中
|
||||
assert!(result.starts_with(&"x".repeat(TRUNCATE_HEAD)));
|
||||
assert!(result.ends_with(&"x".repeat(TRUNCATE_TAIL)));
|
||||
// 中段标注存在 + 标注省略字符数(中段 = 总长 - 头 - 尾 = 51000 - 20000 - 20000 = 11000)
|
||||
// 中段标注存在 + 标注省略字符数(中段 = 总长 - 头 - 尾 = 9192 - 3072 - 3072 = 3048)
|
||||
assert!(result.contains("已截断"));
|
||||
assert!(result.contains("省略 11000 字符"));
|
||||
assert!(result.contains("省略 3048 字符"));
|
||||
// B-260619-02:截断提示含原字节数(9192 个 'x' = 9192 字节),让 LLM 感知非全文
|
||||
assert!(result.contains("原 9192 字节"));
|
||||
// 结果总长应远小于原长(20k 头 + 20k 尾 + 标注)
|
||||
assert!(result.chars().count() < TRUNCATE_THRESHOLD + 1000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user