当要对大量 markdown 文件进行结构化输出时,你会如何处理?我一开始觉得让 Agent 写个脚本跑一遍不就完事了吗?实践证明事情没那么容易。
最近我拿到 100 多个值班记录文件,需要对文件里记录的所有工单问题进行分析。然而工单问题存储在100多个文件中,得先把全部文件内容结构化提取到一个文件中才能便于分类、统计、总结。我先让 Agent 写个脚本,通过正则表达式提取关键字段。结果跑了一遍才发现文件内容非常不规范,有的排版错乱,还混杂了各种 HTML 标签。尝试多次之后,发现脚本提取出来的结果还是没法用,结果里不仅有内容截断,还有信息错位的问题。
既然脚本规则提取行不通,那改为尝试用模型提取了。于是我把这个任务交给了 OpenClaw,希望它能帮我把这些非结构化的文件输出到一个结构化文件中。当然,用 OpenClaw 处理也是踩了几个坑。
01第一个坑:偷懒不遵循指令
先给 OpenClaw 发送指令,示意如下:
每次读取 10 个文件,分多批处理指定目录下的所有文件。
刚开始的前几批执行得很完美,但当执行到中后段时,事情开始不对劲了。从执行过程看,它突然不再按“每次 10 个”的规则执行,而是随机跳过了一些文件,甚至会试图把剩下的几十个文件一口气读完。执行过程示意如下:
批次 1:读取文件 001-010... 成功 批次 2:读取文件 011-020... 成功 ... 批次 5:读取文件 041-050... 成功 批次 6:跳过读取(数据缺失) 批次 7:试图一次读取剩余全部文件 100-180...
OpenClaw 聪明的是开始时会遵循指令执行且给出的执行结果在可接受范围内,但执行一段时间后在它缺少约束规则时就开始自作主张,甚至直接把一些文件都跳过了。
在长周期的任务中,Agent 很容易遗忘最初的指令约束,产生行为漂移。
02第二个坑:读文件但只读一半
如果说不按批次读取是宏观行为失控,那微观层面的内容丢失就更隐蔽了。
在核对部分长文件的提取结果时,我发现有些信息被截断了。仔细排查后发现,问题可能出在工具本身的限制上。
OpenClaw 的 read 工具在读取文件时有一个限制“最大读取 2000 行 或 50KB 内容,触发了就会截断文件内容”。有一些长文本文件或大文件很容易出现内容丢失的问题,导致提取结果不完整。
图注:OpenClaw知道自己读文件有限制
难受的是这种截断是静默地发生的,它没有报错、没有提示、完全静默,如果没有人工审核还真的很难发现这个问题。
OpenClaw 不会告诉你“文件太长我没读完”,它只会根据读到的那部分内容给你输出结果。
这就导致很多长文件的后半部分内容直接就被丢弃截断了,但我很难发现。
03第三个坑:脑补有规律的文件名
还有一个坑导致任务执行频繁失败。从执行过程和对比文件列表后,发现是读取的文件名不存在导致的失败。在最初的几次执行中,Agent 是老老实实通过运行脚本代码(比如 ls 命令)来获取目录下的实际文件名的。
但随着任务的推进,我怀疑它发现这些文件名具有高度的规律性,像“2026-05 第一周值班记录”这样的格式。于是,为了省事它放弃了运行脚本获取真实文件列表,改为直接推理出下一批文件名去读取。
图注:OpenClaw知道自己读文件会脑补文件名
问题在于,真实的文件列表并不是连续的,中间可能因为某些原因跳过了几个编号。它用脑补的文件名去读取,自然会遇到“文件不存在”的错误,导致整个任务执行频繁报错,而且有些名称不规则的文件还被它漏读了。
04解决方法:用 Skill 给它装上工作规范
面对这几个坑,单纯靠在提示词里写“你一定要按批次读”、“你一定要读完整”是没用的,而且很难从输出结果看它是否完全遵循指令。
于是我让 openclaw 编写了一个专门的 Skill,在这个 Skill 里将“读长文件”和“批量读文件”两个场景固化下来,并引入了多 Agent 协同的机制。
首先,读长文件与批量读文件的场景封装。对于批量读取,要求通过真实的目录遍历获取文件名,杜绝“脑补”;对于长文件,利用分页读取并拼接的方式,绕开单次 2000 行的限制,确保长文件内容被完整获取。
先对目标目录做摸底扫描,wc -l 统计每个文件的总行数,行数超过 1800 的文件自动标记为"需要分段翻页",然后用 read 工具的 offset 参数每段读 1800 行,逐页推进直到读完文件末尾。
其次,双 Agent 协同互检,参考验证循环(Verification loop)思路。执行 Agent,负责按照封装好的逻辑去读取文件并提取信息。监督 Agent,负责在执行 Agent 完成一批任务后,核对实际读取的文件数量和内容完整度。
读完一批后触发验证:监督 Agent 对比原始目录的实际文件列表和行数与"读取记录.md"中记录的已读状态,一是检查行数完整性,wc -l 和已读行数逐文件比对;二是检查覆盖率,diff 对比实际文件列表和已读文件列表。发现未读完的、完全漏掉的就写入"重读队列.md"。执行 Agent 在开始下一批工作前先扫一眼这个文件,发现有待重读的立即补读、更新状态,监督 Agent 再验证一次直到清零。
最后,用文件对齐状态,确保可追溯。为了防止 Agent 做到一半忘了进度,我让它们通过读写本地文件来记录状态。执行 Agent 每处理完一个文件,就在共享的文件中进行记录,监督 Agent 依据文件记录来决定是否允许进入下一批次。
所有状态都落盘在本地文件里,Agent 重启或超时都不会丢进度。
Skill内容如下:
--- name: "batch-read-markdown" description: "Read large/batch markdown files completely with L2 verification loop. Handles read truncation, auto-paginates, validates via independent agent." --- # Batch File Reading with Verification Loop ## Purpose 你是一个需要完整读取大量文件或多个 Markdown 文件的 Agent。这个 Skill 教你如何处理 `read` 工具的截断限制(单次约 2000 行/128KB),通过分段翻页保证每个文件被完整读取,并通过独立的监督子任务(Verification Loop)验证读取完整性,发现缺陷后自动触发重读。 ## Context `read` 工具有截断限制:单次调用约返回 2000 行或 128KB 的内容,超出部分自动丢弃且不提示。这意味着: - **单个大文件**:如果一个文件超过 2000 行,一次 `read` 只能读到前半部分 - **批量小文件**:虽然每个文件可能不大,但数量多时容易遗漏或忘记读完 本 Skill 解决两个问题: 1. **读得全**:机制化地逐文件分段翻页,确保每个文件完整读取 2. **读得对**:通过独立监督者验证,确保无遗漏、无截断 ## Instructions ### Step 1:数据摸底(Pre-scan) 在开始读取之前,先获取所有待处理文件的元数据: ```bash # 对单个文件 wc -l <文件路径> # 对批量文件 ls <目录>/*.md | while read f; do echo "$(wc -l < "$f") $f"; done | sort -rn ``` 产出物:文件列表(含每个文件的总行数),用于决定每个文件需要分几段读。 **规则:** 任何文件,总行数 > 1800 就需要分段翻页(留 200 行缓冲,因 2000 行是硬上限)。行数 ≤ 1800 可以一次 `read` 读完。 ### Step 2:逐文件完整读取 对每个文件执行以下流程: ``` 2.1 如果文件行数 ≤ 1800: read path=<文件> → 一次性读完,标记为 ✅ 2.2 如果文件行数 > 1800: 段数 = ceil(总行数 / 1800) 第1段:read path=<文件> offset=1 → 读 1-1800 行 第2段:read path=<文件> offset=1801 → 读 1801-3600 行 ... 第N段:read path=<文件> offset=<最后> → 读到文件末尾 ``` **每次分段翻页后,检查 read 返回的行数:** - 如果返回行数 < 1800 且当前 offset + 返回行数 ≥ 总行数 → 此文件读完 ✅ - 如果返回行数 = 0 → 已到文件末尾 ✅ - 其他情况下继续翻页 ### Step 3:维护读取记录(Artifact A) 在当前工作目录下维护一个读取进度清单(如 `读取记录.md`),格式如下: ```markdown # 读取记录 ## 任务概要 - 总文件数: N - 已完成: M - 已验证完整: K - 待重读: R ## 逐文件状态 | 文件 | 总行数 | 已读行数 | 状态 | |-----|--------|---------|------| | xxx.md | 4477 | 4477 | ✅ | | yyy.md | 312 | 312 | ✅ | | zzz.md | 2500 | 1800 | ⚠️ 未读完 | ``` 重读队列放在独立的 `重读队列.md` 中(详见 Step 6)。两份文件分开维护,职责明确。 **每次成功读完一个文件后立即更新此记录**,确保监督者可以依赖它。 ### Step 4:验证判断(Verification Trigger) **何时触发验证:** 每读完一批文件后(一批可以是 1 个大文件,也可以是 10-50 个小文件)。 #### 4.1 行数完整性验证(必须) 对已读文件列表中的每个文件,跑: ```bash # 伪代码 实际行数 = wc -l <文件> 记录行数 = 从读取记录中提取该文件的已读行数 if 实际行数 != 记录行数: → 缺陷:文件未完整读取,加入重读队列 ``` #### 4.2 覆盖率验证(必须) ```bash # 对比实际文件列表 vs 已读文件列表 ls <目录>/*.md | sort > /tmp/actual.txt # 从读取记录中提取已读文件列表并排序,输出到 /tmp/read.txt # (用 grep/sed 或 awk 从读取记录.md 中提取文件名列) grep '|' 读取记录.md | grep -v '文件|总行数|-----' | awk -F'|' '{print $2}' | sed 's/^ *//;s/ *$//' | sort > /tmp/read.txt diff /tmp/actual.txt /tmp/read.txt if diff 有输出: → 缺陷:存在未读取的文件,加入重读队列 ``` #### 4.3 内容采样验证(可选,按需) 从已读文件中随机抽取 2-3 个,单独 `read` 末尾 100 行,与读取记录中的末尾内容对比,确认内容一致性。 ### Step 5:验证执行方式 **监督方案(推荐,L2 Verification Loop,优先使用):** 只有在当前环境不支持创建独立 Agent(如 OpenClaw 的 `sessions_spawn` 工具不可用或受限)时,才降级使用轻量方案。 **轻量方案(降级兜底):** 在当前会话中直接跑 `exec` 执行验证脚本,对比行数。优点是即时反馈,缺点是同一个 Agent 既执行又验证,存在自我验证盲区。仅当无法创建监督子任务时使用。 创建一个独立 Agent(如 OpenClaw 的 `sessions_spawn` 工具)担任监督者,赋予的任务 prompt 如下: ``` 任务:验证文件读取完整性 验证范围: - 读取记录文件路径:<路径> - 原始文件目录:<目录> 请执行以下检查: 1. 用 exec wc -l 获取原始目录下所有文件的行数 2. 用 read 读取记录文件,提取已读文件的列表和行数 3. 对比(1)和(2),列出所有: - 未被读取的文件 - 行数未读完整的文件 4. 如果有缺陷,将缺陷文件列表写入当前工作目录下的 `重读队列.md` 5. 如果无缺陷,在记录文件中标注本轮验证通过 输出格式: ## 验证结果 - 通过/失败 - 缺陷文件数: - 通过文件数: - 缺陷详情: ``` 监督子任务与主任务互不干扰,监督者的判断独立于执行者。 ### Step 6:重读闭环 #### 多 Agent 方案(优先) ``` 监督者发现缺陷 → 写入重读队列.md ↓ 主任务发现重读队列有新条目 → 对照队列对缺陷文件重读(Step 2) ↓ 主任务更新读取记录中的状态 ↓ 监督者再次验证 → 通过则从重读队列移除,否则继续重读 ↓ 所有缺陷清零 → 全量任务完成 ✅ ``` #### 单 Agent 降级方案(不支持创建监督子任务时) 当无法创建独立监督子任务时,验证和重读闭环在同一会话内完成: ``` 主任务读完一批文件后 ↓ 主任务自己执行验证脚本(exec 对比 wc -l 行数 + 覆盖率 diff) ↓ 发现缺陷 → 写入重读队列.md → 立即重读 ↓ 重读后再次验证 ↓ 所有缺陷清零 → 全量任务完成 ✅ ``` 执行者在验证自己时存在自我验证盲区(可能忽略自己的错误),因此降级方案中建议: - 验证脚本写好后,先用肉眼检查一遍再跑 - 在完成消息中附带验证结果摘要,供人工复核 - 条件允许时尽快切换回多 Agent 方案 **重读队列文件格式(当前工作目录下的 `重读队列.md`):** ```markdown # 重读队列 | 文件 | 缺陷类型 | 期望行数 | 实际行数 | 状态 | |-----|---------|---------|---------|------| | xxx.md | 行数不完整 | 4477 | 2000 | 待重读 | | yyy.md | 遗漏 | - | - | 待重读 | ``` ### Step 7:任务完成判定 所有文件满足以下条件时,任务正式完成: 1. ✅ 每个文件都已被读取 2. ✅ 每个文件的已读行数 = 实际行数 3. ✅ 重读队列为空 4. ✅ 读取记录中无 ⚠️ 状态的条目 --- ## 注意事项 1. **行数优先,而非大小**:判断是否需要分段的标准是 `wc -l` 的行数,不是文件磁盘大小。一行可能只占几个字节,一个 100KB 的文件可能有上万行。 2. **offset 是行号,从 1 开始**:`read path=xxx offset=1` 从第 1 行开始读,不是从第 0 行。 3. **批次大小的选择**:每批文件数量不要太多,建议 10-20 个小文件为一轮验证区间,或单个大文件每次读完都验证。批次太大时,监督者的验证工作本身也会消耗大量 tokens。
05结语
经过这次折腾,100 多个混乱的 markdown 文件终于被妥善处理,提取了5000+个工单问题到一个文件中,可以继续下一步分析总结了。
图注:提取的5000+问题,用于分析总结
我把这次踩坑过程提炼成了一份简单的经验列表:
- 外部规则约束 Agent:长任务必然导致指令遗忘,必须用外部状态(如进度文件)来锚定进度。
- 警惕静默失败:了解底层工具的硬限制(如读取行数),重要操作一定要加上校验环节。
- 切断 Agent 的“小聪明”:当获取真实数据的成本高于推理时,Agent 会有倾向于推理。在文件名获取这类必须准确的环节,要通过工程手段强制其执行查询。
- 多 Agent 协同代替单打独斗:自己检查自己往往更容易校验通过,而引入独立的监督 Agent 能大幅提升结果校验的准确性。
- 流程固化为 Skill:高频、易错的复杂流程不要全靠提示词,写成 Skill 复用才是长久之计。
- 人工审核:人工审核进行最后把关。早期每执行一批就人工也审核一次,总结 Agent 失败模式并让其改正;中期可以执行多批后再去人工审核;最后再人工审核一遍。
当然,这次实践还有优化的空间。使用Skill后,模型推理调用的次数显著增加,成本也随之上升。如何在保证稳定性的同时精简不必要的检查轮次是可以继续探索的方向,例如使用脚本规则+模型推理的混合方法,在混合方法中探索哪些步骤适合规则、哪些必须靠模型、怎么判断分界线。
你在用 Agent 处理实际任务时,有遇到过哪些的“坑”?是怎么解决的呢?还有哪些让你头疼的问题?欢迎分享、探讨。
(如果这篇文章对您有所帮助,请帮忙 关注 并 转发,谢谢!)