跳转到内容

08 - 消息 Sanitization 与上下文保护

学习目标

理解 CowAgent 如何验证和修复消息历史合法性,处理 tool_use/tool_result 配对错误,以及在上下文溢出时进行多级恢复。

项目实践

消息合法性验证

CowAgent 在每个 turn 开始前调用 _validate_and_fix_messages() 验证消息历史:

验证委托给 sanitize_claude_messages() 函数,处理以下异常情况:

  • tool_use 后无对应 tool_result(LLM 被中断或出错)
  • tool_result 无对应 tool_use(trim 裁剪了边界)
  • 连续多条 user text 消息(内部 hint 注入导致)

Context 裁剪后的边界修复

裁剪是产生 orphan tool_use 的主要原因。CowAgent 以完整 turn 为单位裁剪,但裁剪点可能在 tool_use 之后、tool_result 之前:

裁剪前: [user text] [assistant text + tool_use] [user tool_result] [assistant text] [user text]
裁剪后: [assistant text + tool_use] [user text] ← orphan tool_use!

修复方案:在 _validate_and_fix_messages() 中检测到 orphan tool_use 时,立即注入对应的 error tool_result

# 伪代码
for block in last_assistant_content:
if block["type"] == "tool_use":
orphan_tool_results.append({
"type": "tool_result",
"tool_use_id": block["id"],
"content": "Error: Tool result was lost due to context trimming",
"is_error": True
})
messages.append({"role": "user", "content": orphan_tool_results})

JSON 参数修复

LLM 流式返回的 tool args JSON 可能因截断而无效:

def _parse_tool_args(args_str, finish_reason):
try:
return json.loads(args_str), None
except json.JSONDecodeError as e:
# 1. 先检查是否是 max_tokens 截断
if finish_reason in ("length", "max_tokens") or not args_str.endswith("}"):
return {}, "Output truncated (max_tokens reached)..."
# 2. 尝试 json_repair 修复转义问题
if _HAS_JSON_REPAIR:
try:
repaired = _repair_json(args_str, return_objects=True)
if isinstance(repaired, dict):
return repaired, None
except Exception:
pass
# 3. 返回原始解析错误
return {}, f"Invalid JSON: {e.msg}"

这种三层降级(正常解析 → 截断检测 → json_repair → 原始错误)最大程度提高了 tool args 的解析成功率。

上下文溢出恢复三级降级

当 API 返回上下文溢出错误时:

_aggressive_trim_for_overflow() 比常规 _trim_messages() 更激进:

  • 常规裁剪保留最后 max_context_turns
  • 激进裁剪只保留最后 5 轮
  • 所有工具结果统一截断到 10K 字符(常规是 50K/20K 分级)
  • tool_use 的 input 也截断(常规不裁剪)

问题与规避

问题CowAgent 的规避策略
裁剪产生 orphan tool_use_validate_and_fix_messages() 注入 error tool_result
LLM 返回无效 JSON三层降级:正常解析 → 截断检测 → json_repair
激进裁剪后仍然溢出清空历史 + 清除 DB 脏数据
清空历史后重启加载脏数据_clear_session_db() 同步清除 SQLite
不同模型工具格式不兼容历史恢复只保留纯文本对,不恢复 tool 消息
MiniMax tool_call_id 不匹配宽泛关键词匹配(包含 MiniMax 错误码 2013)

设计取舍

为什么验证消息而不是直接信任 LLM 返回?

CowAgent 的注释说明:“Context trimming is done once… NOT here — trimming mid-execution would strip the current run’s tool_use/tool_result chains.” 即使裁剪在循环前执行,边界情况仍然可能产生非法消息。验证函数是最后一道防线。相比在裁剪逻辑中精确处理所有边界情况(极其复杂),“裁剪后验证+修复”的方案更健壮。

为什么使用 json_repair 而非要求 LLM 严格输出 JSON?

LLM 在流式输出时可能因为各种原因产生无效的 JSON(转义字符、截断等)。要求 LLM”严格输出”不现实——模型不是确定性的。json_repair 库能自动修复常见的 JSON 错误(未转义的引号、尾部逗号等),提高了工具调用的成功率。

参考来源