☰
本地CLI调试工具Paper:AI Agent执行轨迹与Token成本分析实战
2026/10/15 13:00:01 网站建设 项目流程

1. 为什么我花时间做一个本地 CLI 调试工具

如果你最近在折腾 AI Agent,不管是 Claude Code、Codex 这类偏编码的智能体,还是自己用 LangChain、OpenAI SDK 搭的多步推理工作流,你一定经历过这种痛点:Agent 跑完一轮任务,结果不对,你想知道它中间到底做了哪些决策、调了哪些工具、哪一步开始跑偏——结果只能翻日志、打日志、加 print,一轮下来几个小时没了。

我自己的项目里,Agent 一多,日志一杂,问题就变成了一场灾难。云端 LLM 服务商自带的调试面板不是不好用,而是它只管模型侧的内容,管不到你本地代码里工具调用的上下文、变量状态、token 消耗和重试策略。出于这个背景,我把手头一直在用的内部调试脚本梳理了一遍,做成了一个本地 CLI 工具,名字就叫 Paper。它解决的问题很直接:让 Agent 的执行过程像翻文件一样可以回看、可以搜索、可以对比,而不是靠肉眼在一堆混合日志里大海捞针。

这篇文章我会把 Paper 的设计思路、核心功能、真实使用场景和踩过的坑一次性讲清楚。适合正在做 Agent 开发、或者打算入坑 LLM 应用调试的工程师参考。

2. 整体设计:为什么选 CLI 而不是 Web 面板

2.1 本地优先的三个理由

做调试工具,市面上九成方案是 Web Dashboard,数据上报、端口监听、图表可视化,一套组合拳。但我不太认同这条路。至少对于 Agent 调试这个场景,CLI 有它不可替代的优势。

第一,环境零依赖。Web 面板要起服务、要开端口、要浏览器访问,这在本地开发环境里没什么,但一旦跑到 Docker 容器、CI 流水线、服务器上的定时任务里,你根本没有机会打开浏览器。CLI 工具只要一个二进制文件和一条命令,终端里直接跑,任何环境都适用。

第二,管道友好。CLI 天然支持管道和重定向,这意味着你可以在脚本里把调试输出直接喂给 grep、jq、awk 继续处理,数据加工链路可以完全自动化。Web 面板的接口再丰富,也不如终端管道的灵活性来得直接。

第三,心智负担低。调试这件事,本身就是在一个终端里同时操作代码和观察行为。切到浏览器再切回来,每切换一次都是上下文的损失。CLI 把"运行-观察-修改"三个动作收敛在一个窗口里,这个体验差异长期下来非常明显。

2.2 Paper 的架构分层

Paper 整体分三层。最底层是数据采集层,它通过环境变量注入和回调接口来拦截 Agent 运行过程中的各类事件,包括 LLM 请求、工具调用、中间推理、最终输出。中间层是存储层,数据不落外部数据库,直接写到本地目录下的结构化文件里,按会话 ID 分文件夹,每个事件一条记录。最上层是展示层,提供paper view、paper search、paper stats这类命令,把存储的数据以不同视角呈现给你。

这个架构最大的好处是:采集和展示完全解耦。也就是说,你可以先在后台跑一批 Agent 任务,采集数据,数据落地之后再慢慢分析。不需要实时盯着屏幕,这比较接近"事后取证"的调试哲学——但"事后"不是指 Agent 跑完了才看,而是指分析动作和执行动作可以不在同一个时刻发生。

2.3 和现有开源方案的差异化

市面上现有的 Agent 调试方案,要么是 LangSmith 这样的托管服务,要么是各类 E2B、AgentOps 之类的观测平台。它们的问题在于:其一,数据默认上传云端,对某些敏感项目来说这本身就是不可接受的;其二,平台的抽象层级通常绑定特定框架,你用自己的代码做 Agent 编排时,接入成本很高;其三,平台展示的是它们认为重要的信息,而不是你真正关心的信息。

Paper 做的就是反过来:不绑定任何框架,不强制上传任何数据,所有字段你可以自定义扩展。它像一个本地记录仪,你告诉它要记录什么,它就忠实记什么。从需求匹配的角度,我认为本地 CLI 才是"调试"这个动作的最佳载体——观测平台适合做大盘监控,但真正一行一行看执行轨迹的,还是命令行来得顺手。

3. 核心功能拆解:从执行轨迹到成本分析

3.1 执行轨迹回放:tracing 的核心实现

Paper 最核心的功能是执行轨迹回放。每个 Agent 会话的所有事件,都会按照发生顺序记录下来,形成一个完整的执行树。这个执行树不是只有名称的列表,它包含每一个节点的输入、输出、耗时、token 消耗,以及和父节点的调用关系。

实现上,我用了类似于 OpenTelemetry Span 的数据模型。每个事件是一个 TraceEntry,包含session_id、parent_id、event_type、timestamp、payload这几个核心字段。执行树就是通过parent_id关联出来的。查看命令做了缩进树形展示,缩进层级就是调用深度。

这事看着简单,实际上有一个细节非常关键:还原状态。调试 Agent 时,只看到"调用了一个工具"是不够的,你还得知道调用前后变量值变成了什么。所以 Paper 在拦截事件的时候,会额外捕获执行上下文的关键变量快照,记录到 payload 里。这也是我踩过最深的一个坑——早期版本不记录上下文,回放的时候经常出现"对,调了这个工具,但为什么不传这几个参数?"的困惑,加了快照之后,很多问题一眼就能定位。

3.2 检索能力:为什么一般的 grep 不够

Agent 调试中有一个高频场景:任务跑完,结果不对,你想知道"某个工具在哪些会话里被调用过""某段报错信息是哪个环节产生的"。如果只是 grep 日志,你会被海量噪声淹没,因为日志里 Agent 的中间输出、框架自身的打印、第三方 SDK 的日志全都混在一起。

Paper 的paper search做了一个结构化查询的事:你可以指定事件类型、工具名称、时间范围、以及 payload 中的任意字段来做组合过滤。比如paper search --session abc123 --tool web_search --has-error,意思就是只查这个会话中所有 web_search 工具调用、且结果里包含 error 的记录。这在排查工具链故障时非常高效,把"看日志找线索"变成了"按字段查记录"。

底层存储我选的是 JSON Lines 格式,每条事件一行。搜索时默认基于行列定位,超大会话会建一个轻量索引来加速。为什么不用 SQLite?因为 JSONL 的可读性最好,出了问题可以直接用编辑器打开看,而且 diff 友好——你可以在调试前后导出两份 JSONL,直接 diff 出差异事件。这对于复现"为什么上次跑通这次跑挂"特别有用。

3.3 Token 与成本分析:Agent 这么贵,得盯着点

Agent 的 token 消耗和普通单次 LLM 调用不同,它呈阶梯式累加。一个典型的多步 Agent 任务,可能先调用一次规划模型,再执行两次工具调用,每次工具返回之后还要再请求一次模型来决策下一步。每滚一层,上下文就可能把前面的结果全部带上,消耗会指数级膨胀。很多人在 Agent 上线之后才发现成本暴增,就是因为没有在开发阶段监控 token 变化。

Paper 专门做了一个paper stats命令,它按会话归组、按事件类型分桶展示 token 使用情况。核心指标包括:总prompt_tokens、completion_tokens、以及按工具维度拆解的二次消耗统计。最有用的是一个"token 增长曲线"——看每个事件之后的累计 token 数,你能清晰地看到是哪一步调用的上下文把预算烧掉的。

我自己的实测数据:某个抓取网页后做总结的 Agent 任务,前两步 token 消耗都很正常,到了第三步把网页全文塞进去,瞬间从 2K 跳到 15K。这类行为,在开发调试期发现和上线后发现,成本差着两个数量级。

3.4 交互式会话检查:深入现场

除了事后分析,Paper 还支持交互式模式paper attach。这个命令把一个正在运行的 Agent 进程挂到 Paper 上,实时打印它的事件流,并且可以按Ctrl+O临时打开一个只读事件浏览器,上下键翻页查看已经发生的事件,确认当前 Agent 走到了哪一步。

这个功能模仿了kubectl attach和docker attach的思路。调试分布式系统时,attach 是一个成熟的手段;Agent 是某种意义上的"异步系统"——它的执行上下文是逐步累积的,你没法预知哪一步出错,实时观察比事后翻查更符合直觉。

4. 实操过程:从接入到定位问题的完整流程

4.1 一分钟接入:环境变量注入

接入方式我用的是环境变量注入,不需要改业务代码。Paper 会在安装时生成一段加载脚本,你只要在启动 Agent 前 source 它,之后该进程内所有符合协议的事件都会被自动采集。

# 按 Paper 生成的配置,预先加载采集钩子 source ~/.paper/env.sh # 正常运行你的 Agent 程序 python run_agent.py --task "汇总本周项目进展"

env.sh里实际做的事是设置几组环境变量,包括事件输出目录、采样率、脱敏规则。如果你的 Agent 是用 LangChain 或 OpenAI SDK 写的,Paper 通过 Monkey-Patch SDK 底层客户端来捕获事件;如果是自定义编排,提供了一组显式采集 API,在你的关键路径上手动埋点。

对大多数框架型 Agent,第一条路零侵入。但是要注意,Monkey-Patch 方式只对当前进程有效,如果你有多个子进程,需要确保环境变量沿进程树继承。这是我建议优先用source而不是export的原因——source 你在哪个终端执行,就管哪个终端,行为可预期。

4.2 跑一次任务并查看轨迹

接入之后,跑一次 Agent 任务。任务跑完或跑挂之后,用paper view来看完整轨迹:

# 列出所有会话 paper list # 查看最近一个会话的执行轨迹 paper view --last # 查看指定会话,展开所有节点 paper view --session 8f3a2e --depth 10
Session: 8f3a2e Task: 汇总本周项目进展 耗时 42.8s LLM调用 7次 工具调用 5次 ├─[1] planner.plan (LLM) input=项目进展数据收集 output=分三步执行计划 tokens=1240 │ ├─[2] tool.read_files (TOOL) path=./reports/*.md output=找到4个报告文件 │ ├─[3] tool.web_search (TOOL) query=团队周报模版 output=搜索完成 │ ├─[4] planner.summarize (LLM) input=报告内容+搜索内容 output=生成周报摘要 tokens=8320 │ └─[5] tool.write_file (TOOL) path=./summary.md output=写入完成

这是我用 Paper 记录的真实会话轨迹。你能直观看到执行链路,哪一步耗时最长、哪一步调用 LLM 吞了大额 token,一目了然。这个例子中[4]的 8320 tokens 就是前面说的累积问题:它把四份报告的全文都带进了上下文,token 数远超第一阶段。

4.3 实战排查:一个典型的 Agent 任务失败案例

我拿一个真实的排查过程演示。某次任务,Agent 需要总结多个网页内容,但最终生成的摘要缺少第一个网页的信息。第一次排查,我用paper search快速定位了所有fetch_url相关事件:

paper search --session 8f3a2e --tool fetch_url
[2] fetch_url url=https://site-a.com/article/1 status=200 length=24KB [3] fetch_url url=https://site-b.com/article/2 status=500 error="Timeout" [5] fetch_url url=https://site-c.com/article/3 status=200 length=18KB

看到了吗?问题不在总结环节,而是在第三步抓取时发生了超时。Agent 的策略是继续往下执行而不是重试,最终总结阶段就没有拿到第二个页面的内容,整个任务"带伤跑完"。这类问题如果你只盯着最终输出,会误判是总结模型的问题,白白浪费好几次调试迭代;有了结构化轨迹,问题源头一次就锁定了。

修复方案也很直接:在 Agent 的工具调用逻辑里加了一个 fetch 重试策略,并对超时事件打上标记,供 Paper 后续统计失败率。这个案例充分说明了调试工具最怕的不是信息不够,而是信息太杂。

4.4 自定义事件扩展:记录你自己的业务指标

如果你的 Agent 不是框架型,而是自己写的编排代码,那用 Paper 的采集 API 做精细埋点更合适。它提供一个装饰器,你只需要在关键函数上一挂即可:

# agent_task.py from paper import capture @capture(event_type="task_step", fields=["status", "elapsed"]) def execute_step(step_name: str, context: dict): # 你的业务逻辑 ... return {"status": "ok", "elapsed": 0.8}

这个装饰器会在函数调用时自动记录函数名、参数快照和返回值,全部按事件写入当前会话。此后execute_step的每次调用都会出现在paper view的轨迹树里。配合paper search --event-type task_step,你甚至可以做一个简单的"Agent 单步健康度看板"——统计每类 step 的成功率、平均耗时,及时发现哪个环节开始频繁失败。

这个能力的重要性在于:通用工具只能记录 LLM 和 SDK 层面的行为,但真正需要排障的往往是你的业务逻辑。框架外的自定义事件,是让调试工具从"能看"到"好用"的关键一步。

5. 常见问题与排查技巧实录

5.1 采集不到事件:先检查环境变量继承

接入了 Paper 但运行后paper list显示空的会话,这是最常见的入门问题。九成原因是子进程环境变量没继承——你可能把source ~/.paper/env.sh写在了脚本里,脚本内部的子进程能采集,但脚本执行完环境变量就消失了。另一个常见原因是 SDK 版本兼容性:新版 OpenAI SDK 改了底层方法名,Monkey-Patch 挂载时静默失败。Paper 内部对这类情况有个自检命令:

paper doctor

它会检查当前环境下 SDK 版本、采集钩子是否生效、环境变量是否正确,并把检查结果直接输出。如果doctor报告钩子未生效,优先确认 Python 版本和 SDK 版本是否在支持列表内。

5.2 轨迹记录不完整:异步回调丢事件

如果你用异步 SDK(比如AsyncOpenAI),在异步回调中写文件时没有做同步处理,进程可能在事件写入缓存但还没刷盘时就退出了,导致最后几个事件丢失。解决方法是监听退出信号并主动 flush,或者给写入层加一个定时落盘机制。

Paper 的采集层做了解法:默认每 50 条事件落盘一次,进程正常退出时再补一次 flush。但如果你用kill -9强杀进程,最后一批总是会丢。所以调试期间建议把多步 Agent 任务做完再退出,或者至少保留重跑能力,不要让"丢尾部事件"干扰你的判断。

5.3 输出太多看不过来:采样率调节

长任务轨迹动辄上百个节点,全展开根本看不完。这种情况下,除了用--depth限制展开深度,Paper 还提供采样率配置:

# 只显示调用了工具的节点和出错节点,忽略纯 LLM 中间过程 paper view --session 8f3a2e --focus-tool --only-error

采样率设置的逻辑是"按事件类型过滤展示",而不是"按比例丢弃记录"。这个区别很重要——丢弃记录是数据损失,过滤展示只是视角调整。记录永远全量,展示永远可选,这是设计调试工具的一个底线原则。

5.4 token 统计对不上账单

Paper 统计的 token 和你的账单数字有出入,这通常不是 bug,而是上下文缓存被忽略导致的。主流 LLM 服务商对 prompt 缓存部分有专门的折扣计价和独立的 token 计费显示,如果 Paper 按朴素prompt_tokens统计,会把缓存命中的 token 算作全价。解决方式是在配置里显式开启缓存字段采集,从响应的usage结构中读出prompt_tokens_details.cached_tokens,单独做一列展示。

5.5 常见问题速查表

现象可能原因排查手段
采集不到任何事件环境变量未生效、SDK 版本不兼容paper doctor自检
轨迹尾部缺失进程被强杀、异步未 flush正常退出流程、定时落盘
事件时间戳错乱多线程并发写入未做串行化按事件序号而非时间排序
搜索找不到内容payload 字段结构变化先paper dump导出再 grep
重复记录同一调用同时启用 Monkey-Patch 和手动埋点关闭其中一种采集方式

以上每一条我都实际遇到过,最隐蔽的是最后一种:某次我既保留了框架自动采集,又在自己的工具函数上加了@capture,结果同一个fetch_url被记录两次,统计失败率时数字翻倍。后来统一为只保留一种采集路径才恢复正常。建议你接入时就把采集方式定死,混合使用会让数据一致性难以保障。

6. 一些经验沉淀与扩展方向

6.1 调试思维的转变

用了很长一段时间的 Paper 之后,我最大的体会是:调试 AI Agent 和调试传统程序,本质上是不同的思维模式。传统程序的行为是确定性的,你定位到代码行,问题就解决了一大半。而 Agent 的行为是概率性的,同样的输入每次输出都可能不同。所以"调试 Agent"的核心不是找哪一行代码错了,而是找出"执行轨迹中哪个分支选择和你的预期出现了偏差"。

因此工具的抓手也不同。传统调试器提供的是断点和变量监视,Agent 调试工具提供的应该是完整的决策路径和每一步的输入输出快照。Paper 的整个设计原则都围绕"决策路径可回溯"来展开,没有走传统调试器的老路。

6.2 这个工具后续可以怎么扩展

Paper 目前已经能覆盖单机、单进程的调试场景,但横向扩展的空间还很大。我个人下一个打算做的是把多个 Agent 会话聚合对比——同一个任务跑多次,通过 diff 轨迹来定位"偶发失败"背后的模式。目前遇到的真实案例:一个任务 10 次里会有 1 次结果异常,单看某一次轨迹找不出原因,但如果能把 10 次轨迹并排对比,定位哪个节点的行为在失败批次中出现了分歧,会比逐次排查高效得多。

另外,把 Paper 的事件格式标准化,兼容 OpenTelemetry 的 Trace 语义,就可以直接接入 Grafana 或 Jaeger,把本地调试数据和服务端监控打通。一旦建立这个桥,开发期和运维期的数据就能形成闭环,Agent 的行为观测就不止步于 debug,而能沉淀为长期的健康度基线。

最后分享一个小技巧:如果你现在正被 Agent 的诡异行为困扰,先别急着改提示词。把这一次的运行轨迹完整留下来,跑第二次,再留一次,然后 diff 两次轨迹。多数"看似随机"的失败,在轨迹对比下都会露出规律。好的调试工具不应该只是帮你找到这次的问题,更应该帮你建立一套可复用的排障流程。Paper 目前算是我自己比较满意的沉淀,后续还会继续在这个方向打磨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询