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 错误(未转义的引号、尾部逗号等),提高了工具调用的成功率。