Agent 的 Run / Step 运行记录表怎么设计?
简化版
用两张表:Run 表一次执行一行,记这次执行的身份(唯一编号、属于哪个业务对象、谁发起的)、状态(运行中 / 已完成 / 失败)、进度(当前第几步、共几步)、输入快照、输出摘要、失败原因和耗时;Step 表每一步一行,记属于哪次运行、第几步、给人看的步骤名、调了哪个工具、入参、返回、状态、错误和耗时。写入时机很关键:Run 在执行开始时就插一条「运行中」,失败也要回写状态和原因;Step 在工具执行前先插一条「运行中」拿到 ID,执行完再回填结果,这样执行卡住或中断时也能看到停在哪一步。大字段要截断,完整报文可以放到按工具组织的调用日志里,两边用运行 ID 和步骤 ID 对上。
详细版
agent_run(一次执行一行)
id / run_no(唯一,可读)/ 业务对象 ID / user_id / run_type
status:运行中 → 已完成 / 运行失败
current_step / total_step 进度
input_snapshot / output_summary 输入快照、输出摘要
error_message(截断)/ start_time / end_time / cost_millis
agent_step(每一步一行)
id / run_id / step_index(第几步)/ step_name(给人看)
tool_code / request_params / response_result(截断)
status:运行中 → 已完成 / 运行失败
error_message / cost_millis / create_time
| 设计点 | 做法 | 目的 |
|---|---|---|
| 先插后回填 | Step 执行前插「运行中」,执行后更新 | 卡住、中断时能看到停在哪一步 |
| 失败也落库 | Run、Step 在异常分支里都回写状态和原因 | 失败的运行才是最需要排查的 |
| 关联业务产物 | 产物落库后把产物 ID 回填到 Run | 从结果能查到过程,从过程能查到结果 |
| 输入快照 | 把本次执行依赖的输入序列化存下 | 数据后来变了,也能复现当时的条件 |
| 大字段截断 | 入参、返回、错误各有长度上限 | 避免单行过大拖慢查询 |
| 与调用日志分工 | 步骤记摘要看流程,工具日志记全文看细节 | 各自按最常用的维度组织 |
完整版教学
一、为什么要单独记运行过程
Agent 的步骤由模型临场决定,同一个输入跑两次,调用的工具和次数都可能不同。只存最终结果,出了问题只能看着一个不满意的答案猜原因:是模型没去查,还是查到的数据不对,还是查对了但结论写错了?
把每次执行和每一步都落库之后,这些问题都能回答:
这次调了哪些工具、按什么顺序 → Step 按序号排列
每一步传了什么、拿到了什么 → Step 的入参和返回
卡在哪一步、为什么失败 → 状态为「运行中」或「失败」的那一步
整体花了多久、成功率多少 → Run 的状态和耗时聚合
所以运行记录不是可有可无的日志,它是自主决策的 Agent 能被排查、被信任的前提。有了这些记录之后怎么评估和监控 Agent,见「如何评估和监控 AI Agent?为什么不能只看最终答案?」。
二、Run 表:一次执行的身份证
Run 表的字段可以按「谁、什么、怎么样」分组:
| 分组 | 字段 | 说明 |
|---|---|---|
| 身份 | 主键、运行编号 | 编号要唯一且人能读,如「前缀 + 时间 + 业务 ID」 |
| 归属 | 业务对象 ID、发起人、运行类型 | 按业务对象和发起人查询、做权限过滤 |
| 状态 | 运行中 / 已完成 / 运行失败 | 三态足以覆盖同步执行;有中断恢复时再加「已中断」等 |
| 进度 | 当前步数、总步数 | 运行中也能显示「3 / 5」 |
| 输入输出 | 输入快照、输出摘要 | 快照存执行时依赖的条件;摘要不点进详情也能看懂结果 |
| 失败 | 错误信息 | 写库前截断,给出能照着处理的中文原因 |
| 时间 | 开始、结束、耗时 | 统计平均耗时、找慢运行 |
输入快照值得单独说:Agent 执行时依赖的数据(候选资源、用户偏好、配置阈值)以后可能被修改,只存 ID 的话,过一个月就没法知道当时模型看到的是什么。把执行那一刻的关键输入序列化存下,复盘时才能还原条件。
三、Step 表:每一步的回放材料
Step 表最重要的是能按顺序还原过程,几个字段各有讲究:
step_index 第几步,同一次运行内连续递增;时间线按它排序,而不是按时间排序
step_name 给人看的中文名(如「按偏好查询点位」)
tool_code 代码里的工具编码,能对上源码和工具日志
request_params / response_result 模型传的参数、工具返回给模型的内容(即 Observation)
中文名和编码都要存:中文让人一眼看懂,编码让人对得上代码。最后如果模型给出了最终结论,也可以补一条「最终结论」步骤,工具编码留空,这样时间线从第一步读到最后一步就是完整的执行过程。
四、写入时机:先插后回填
步骤记录有两种写法:执行完一次性插入,或者执行前先插「运行中」、执行后回填。推荐后者:
执行前:INSERT step(status=运行中, request_params=…) → 拿到 step_id
执行中:工具调用、写工具日志时带上 run_id + step_id
执行后:UPDATE step SET status=已完成/运行失败, response_result=…, cost=…
同时推进 run.current_step
先插的好处有三个:执行卡住或进程被杀时,库里能看到有一步停在「运行中」;工具日志能拿到步骤 ID,两张表可以一一对应;页面轮询时能实时看到「正在调用某某工具」。无论成功失败都要回填,失败时写上错误原因。
记忆钩子:步骤先占位、后填空;失败也要填完再走。
五、多轮、多版本怎么组织
有外层循环的 Agent(比如排完一版不合格再排一版),一次执行里会有好几版。组织方式有两种:每一版一条 Run,或者一次执行一条 Run、步骤上带版次号。后者更常见:
agent_run:1 条(这次执行)
agent_step:round_no = 1 的 60 步,round_no = 2 的 45 步 ……
step_index 在整次执行里连续编号:第 2 版的第一步是第 61 步
版次号让时间线能按版分组,连续编号让整次执行读下来不断档。Run 上可以再记当前在跑第几版、最终跑了几版。
六、大字段与调用日志的分工
入参和返回可能很大,工具返回几百条候选时一次就是几万字。用一组示意上限估算存储:
一次执行最多 180 步,入参上限 2000 字、返回上限 20000 字
最坏情况:180 × (2000 + 20000) = 3,960,000 字 ≈ 一次执行约 400 万字
所以要截断,而且要分清步骤表和工具调用日志的用途:步骤表按运行、版次组织,给时间线看流程,可以只存摘要或截断后的内容;工具调用日志按工具组织,给工具统计和排查细节,存完整报文。两边通过运行 ID 和步骤 ID 对上。
七、查询和权限
运行记录里常常带着用户的原始输入和内部检索结果,查询时要做权限控制:普通用户只能看自己发起的运行,或者整个执行记录只对管理员开放。前端轮询进度时,只取数字和最近一步即可,不要每两秒把几百步的完整报文重新拉一遍。按运行查步骤、按业务对象查运行、按状态筛失败的运行,这几个最常用的查询都要有索引。
八、常见误区与追问
- 误区:只存最终结果就够了。 自主决策的过程每次不同,不记步骤就没法排查是查错了还是写错了。
- 误区:步骤执行完再一次性插入。 卡住或中断时库里什么都没有,也没法让工具日志关联到步骤。
- 误区:失败的运行没价值,可以不记。 失败的运行正是最需要排查的,要写明停在哪一步、什么原因。
- 误区:入参返回原样全存。 大报文会让表迅速膨胀,步骤表截断存摘要,完整内容交给调用日志。
- 误区:多版本就建多条 Run。 一次执行一条 Run、步骤带版次号更便于把整次执行串起来看。
- 追问:时间线按什么排序? 按步骤序号,不按时间;一次运行几秒钟跑完,时间戳几乎一样。
- 追问:为什么要存输入快照? 执行依赖的数据会被修改,只存 ID 没法还原当时模型看到的条件。
九、加强记忆
Run / Step 记「一次一行、一步一行、先占位后填空」:Run 表记身份(唯一可读编号)、归属(业务对象、发起人)、三态状态、进度、输入快照、输出摘要、截断后的错误和耗时,产物落库后回填产物 ID。Step 表记第几步(连续编号、时间线按它排)、中文名加工具编码、入参和返回、状态、错误、耗时,最终结论也可以补一步。写入先插「运行中」再回填,失败也回填。多版本用一条 Run 加步骤上的版次号,大字段截断、完整报文交给按工具组织的调用日志,两边用运行 ID 和步骤 ID 对齐。
项目实战落地
项目里怎么做的
《AI Agent旅游行程智能规划平台》一次「开始规划」可能排好几版行程,运行记录这样组织:
- 一次执行一条
agent_run:运行编号是「TR + 14 位时间 + 横杠 + 行程单 ID」,唯一;带行程单、发起人、运行类型(行程编排);current_round记当前在排第几版,total_round建记录时写轮次上限、收尾时改成实际跑过的版数;input_snapshot存候选池快照(各类数量与每城、每段的 ID 清单);错误信息截到 500 字以内; - 步骤跨版连续编号:
agent_step带round_no区分是第几版的步骤,step_index在整次执行里连续往后排,时间线读下来就是完整的执行过程; - 截断:步骤记录里的入参截到 2000 字以内,工具返回截到 20000 字以内再入库,交给模型的返回不截断;
- 两张表两个用途:每次工具调用同时写一条
agent_step和一条tool_call_log,前者按运行、版次组织给执行过程页的时间线,后者按工具组织给工具调用日志页和看板。
《AI Agent 智慧医院智能导诊就诊系统》的步骤是「先插后回填」:工具执行器在执行前插一条「运行中」的 agent_step 拿回主键,带着运行 ID 和步骤 ID 进工具执行入口写调用日志,执行完回填返回、状态和耗时并推进运行的当前步数;循环结束后补一条「Final Answer」步骤;分诊结果落库后把结果 ID 回填到 agent_run.task_id;执行时间线只对管理员开放。
《AI 多Agent智能相亲交友匹配平台》是多个角色写同一条运行记录:一次匹配一条 agent_run(运行类型「匹配编排」),agent_step 用 step_type 分出三种步骤:
| 步骤类型 | 谁写 | 记什么 |
|---|---|---|
| 调度 | 红娘主管 | 派谁、给的交代、主管的理由;入参存的是这一轮主管看到的进展文本 |
| 工具调用 | 子角色的执行器 | 工具编码、入参、返回、状态与耗时 |
| 角色结论 | 子角色的执行器 | 这个角色这一轮的结论,主管下一轮拼进展时读的就是这一条 |
一次运行实际调度了几轮不放在内存变量里,而是数这条运行下「调度」步骤的条数:进程重启之后再看这条记录,数出来的还是真实的轮次。
为什么这样取舍
- 一次执行一条运行记录:各版的步骤靠
round_no区分、序号连续,整次执行的过程在一条时间线上读完。 - 时间线只对管理员开放:执行过程里带着患者主诉和内部检索结果,医生和患者账号即使拿到地址也读不到。
面试官还会追问
- 运行摘要
output_summary是怎么拼出来的?里面哪些内容来自代码、哪些来自模型? - 运营看板的「反思轮次分布」为什么只统计运行状态为「已完成」的记录?
学完《AI Agent旅游行程智能规划平台》,上面这些追问你都会迎刃而解。