同一个名字出现在 JSONL 的顶层和消息内容数组里,处理方式可能不同。把 thinking、tool_use 全部描述成“会删除的噪音”,就会误解当前解析器保留了什么。
本文检查 ChatCrystal 的 Claude Code 适配器,版本为 7642768。目标是解释应用的投影视图,不是宣称完整复原 Claude Code 的内部状态;目录与操作步骤见 导入指南。
第一层:决定是否处理这条事件
isRelevantMessage 要求记录有 uuid,排除已知跳过类型和所有顶层 system,最终只接受顶层 user、assistant。文件历史、进度、流式片段等不进入正文提取路径。
这里的“不进入”是知识提取用途的选择,不代表这些记录对源工具没有价值,也不能仅凭缺少 uuid 认定任意记录都是流式消息。解析器使用的是明确字段规则。
下面是构造样本,用于比较顶层事件与嵌套块:
{"uuid":"sample-1","type":"thinking","thinking":"顶层事件"}
{"uuid":"sample-2","type":"assistant","message":{"content":[{"type":"thinking","thinking":"嵌套推理"},{"type":"text","text":"可阅读的结论"}]}}
第一条被顶层规则跳过;第二条进入助手消息提取,正文和 thinking 分别保留。不能用第一条的行为推导第二条也会丢弃。
第二层:提取正文与辅助字段
字符串 content 经过清理成为正文。数组 content 按块类型处理:
| 嵌套块 | 当前处理 |
|---|---|
text |
拼接为正文,再进行标签清理 |
thinking |
收集到独立的 thinking 字段 |
tool_use |
设置 hasToolUse,不保存为正文 |
tool_result |
不进入正文提取 |
只有工具标记或 thinking、没有普通正文的相关消息,也可能被保留。工具标记说明曾使用工具,不是工具参数、输出和执行链的完整档案。检索或摘要中缺少某段输出时,应回到原始记录确认它是否被该规则排除。
独立保存也不等于参与摘要。摘要输入准备代码读取消息的 type、content、has_tool_use,没有读取 thinking。因此,已导入的 thinking 不会沿这条路径进入摘要输入。
第三层:清理指定标签
sanitizeContent 删除 system-reminder、命令回显和部分本地命令标签。它使用具体正则表达式,不是通用 XML 解析器,也不是对所有潜在指令的安全判定。
构造输入如下:
<system-reminder>示例系统提示</system-reminder>
真正的调试结论。
清理后正文保留后面的调试结论。system-reminder 和 local-command-caveat 的模式支持跨行;部分命令标签的内容模式使用 [^<]*,有嵌套标签、属性或其他变形时,不能保证全部移除。用户正文里恰好包含这些标签的示例,也可能一起被删掉。
清理意味着内容转换,应同时验证去掉了什么和留下了什么。原文件仍是追溯依据;经过清理的导入消息不应冒充字节级备份。
为错误路径准备样本
用临时文件或测试副本构造至少四类输入:坏 JSON 行、无 uuid 行、含嵌套 thinking 的助手消息、正文只包含待清理标签的消息。检查坏行是否跳过、相邻好消息是否还在、正文与 thinking 是否分别正确。
还需区分 JSON 语法无效与 JSON 有效但字段类型错误。当前逐行 JSON.parse 的异常可跳过;后续逻辑仍依赖内容数组和字符串等形状,不能承诺所有合法 JSON 都不会导致解析失败。增加字段类型错误、缺失时间与未知块类型作为兼容性检查,比只测试正常文本更有价值。
判断过滤是否适合知识提取
先选一条包含实际问题、处理过程和结论的会话,比较原始记录、导入后的消息和最终笔记。检查关键诊断是否只出现在已排除的工具输出中,代码示例是否被标签清理改变,以及摘要是否保留适用条件。
“消息被保留”也不代表会生成笔记。导入与经验门槛、摘要、向量是不同阶段,见 找回调试方案。当上下文不足时,应承认遗漏并回看来源,不能把过滤后的简洁度当作知识准确度。