跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言Agent RuntimeTool CallingAgent LoopProduction AgentRuntime Architecture

《Agent Runtime 工程化》从 Agent Loop 重构成 Agent Runtime

从 Agent Loop 重构成 Agent Runtime 写到第 14 天,可以把前面几篇收一下了。 前面写了 100 行 Agent Loop。 随后加了 max steps。 再往后加了 token budget。 接着处理了 re

陈堂会发布于 —2 浏览
《Agent Runtime 工程化》从 Agent Loop 重构成 Agent Runtime

从 Agent Loop 重构成 Agent Runtime

写到第 14 天,可以把前面几篇收一下了。

前面写了 100 行 Agent Loop。
随后加了 max steps。
再往后加了 token budget。
接着处理了 retry。
也讲了 idempotency。
最后讲到 checkpoint。

如果你把这些代码都塞进一个 while 里,恭喜,你会得到一个能跑、但很快开始发臭的 Agent。

这不是代码洁癖。

Agent Loop 一旦进入生产环境,问题会从'怎么让模型调用工具'变成'谁对这次行动负责'。Loop 只是控制流。Runtime 才是执行系统。

100 行 Loop 的甜蜜期很短

最小 Agent Loop 大概长这样:

while (step < maxSteps) {
  const response = await model.complete(messages, tools);

  if (response.final) return response.text;

  for (const call of response.toolCalls) {
    const result = await tools[call.name](call.input);
    messages.push(toToolMessage(result));
  }
}

它适合教学,也适合 demo。

但你上线之后,很快会补东西:

这个工具不存在怎么办?
参数 JSON 坏了怎么办?
read_file 读到 5MB 日志怎么办?
shell 命令要不要用户确认?
工具超时要不要重试?
重试会不会重复下单?
模型连续 5 次调用同一个工具怎么办?
任务跑到一半进程挂了怎么办?
用户取消后状态写到哪里?
下次怎么复盘这次失败?

每补一个问题,你就在 Loop 里加一层 if。

最后那个 while 会变成一团东西:模型调用、工具校验、权限判断、预算扣减、trace 写入、checkpoint 保存、错误归因,全都挤在一起。任何人改一行,都可能把另一个责任撞坏。

这就是从 Loop 走向 Runtime 的拐点。

重构不是为了抽象,是为了隔离责任

我不太喜欢一上来就讲'架构分层'。太容易写成 PPT。

更朴素的判断是:如果两个问题的失败处理完全不同,它们就不该挤在同一个函数里。

模型超时和工具越权,不是一类问题。

上下文超限和文件写入失败,也不是一类问题。

checkpoint 保存失败和用户拒绝权限,更不是一类问题。

所以第一版 Runtime 至少应该拆出这些角色:

RunState
ContextBuilder
ModelProvider
ToolRegistry
PermissionGate
ToolExecutor
MessageStore
CheckpointStore
TraceSink
BudgetPolicy
LoopController

这些名字看起来普通,但它们把责任边界切开了。

ContextBuilder 只负责本轮给模型看什么。
ToolRegistry 只负责有哪些工具、schema 是什么、调用是否合法。
PermissionGate 只负责这个动作能不能执行。
ToolExecutor 只负责把已批准的工具跑完,并返回结构化结果。
CheckpointStore 只负责保存可恢复状态。
TraceSink 只负责留下证据。
LoopController 负责把它们按顺序推进。

这时 Loop 还在,但它不再是一个万能函数。

Runtime 的主线顺序不能乱

在我的 demo 里,AgentRuntime.run() 的顺序是:

context -> model -> validate -> permission -> execute -> observe -> checkpoint

这行顺序比类名更重要。

先构建上下文,再请求模型。
模型输出 tool call 后,先校验,再审批。
审批通过后才执行。
执行结果要写回消息,也要写 trace。
每一步结束后保存 checkpoint。

如果顺序乱了,很多 bug 会变得很隐蔽。

比如先执行再审批,权限系统就只是日志。
比如先写消息再校验,模型幻觉出来的工具会污染历史。
比如工具执行后不立刻 checkpoint,进程崩溃时就不知道副作用发生到哪里。
比如 trace 只写最终答案,排障时只能靠猜。

生产级 Agent 最怕的不是失败。

最怕失败没有证据。

RunState 是 Runtime 的脊柱

很多 Agent 项目只有 messages。

这不够。

messages 是模型上下文的一部分,不是 runtime 状态本身。Runtime 至少还要知道:

type RunState = {
  ids: RuntimeIds;
  status: "running" | "finished" | "failed" | "cancelled";
  messages: RuntimeMessage[];
  steps: RunStep[];
  toolLedger: ToolLedgerEntry[];
  budgets: BudgetState;
  workspaceRoot: string;
  runDir: string;
  finalAnswer?: string;
  stopReason?: StopReason;
};

messages 给模型看。
steps 给排障看。
toolLedger 给恢复和幂等看。
budgets 给成本控制看。
workspaceRoot 给文件权限看。
runDir 给 trace、checkpoint、artifact 看。

如果一个系统只有聊天记录,它就很难回答'第几个 step 出事了'。也很难回答'这个工具到底执行过没有'。

到最后,所有复杂问题都会退回到一句话:

你有没有一个可信的 RunState?

Tool Call 不是函数调用

很多 bug 来自一个误会:把 tool call 当普通函数调用。

普通函数调用,调用方和被调用方都在你的进程里。参数类型、异常、返回值,大多能被语言和框架兜住。

Agent tool call 不一样。

调用计划来自模型。模型可能填错参数,可能选择不存在的工具,可能把路径写到 workspace 外,可能连续调用危险工具,也可能在工具失败后继续编故事。

所以 Runtime 必须把 tool call 当外部输入处理:

工具名是否存在
参数是否符合 schema
参数是否符合本地策略
风险级别是什么
是否需要审批
是否允许并行
是否可重试
是否幂等
输出是否要截断
错误是否能转成 observation

MCP 官方文档也把工具描述为由 server 暴露、可被模型调用的能力,并提醒实现方保留人类拒绝工具调用的能力。这个点很关键:协议负责连接,Runtime 负责治理。

Runtime 要接受'部分成功'

普通 Web API 喜欢追求一次请求要么成功,要么失败。

Agent 不是这样。

一个 run 里可能发生这些状态:

模型调用成功
第一个工具成功
第二个工具被拒绝
第三个工具超时
第四个工具状态不确定
文件已经写了一半
预算快耗尽
用户点了取消

如果你的 Runtime 只会返回 success: true/false,它处理不了长任务。

长任务需要承认部分成功。承认之后,系统才会认真设计 step、toolLedger、checkpoint、rollback 和 trace。

这也是为什么 LangGraph 这类项目会强调 durable execution、persistence、human-in-the-loop。行业在往同一个方向走:Agent 不再只是一次模型调用,而是一段可以被保存、暂停、恢复、观察的执行过程。

一个实用的重构路径

如果你手上已经有一个能跑的 Agent Loop,我建议不要一次性推翻。

按这个顺序重构:

第一步,把 provider 适配层抽出来。

不要让业务代码直接依赖某个模型返回的原始字段。内部统一成:

type ModelResponse =
  | { kind: "final"; text: string; usage?: TokenUsage }
  | { kind: "tool_calls"; calls: ToolCall[]; usage?: TokenUsage };

第二步,把工具注册表抽出来。

每个工具至少要有:

name
description
inputSchema
risk
timeoutMs
parallelSafe
outputPolicy
execute()

第三步,把 tool result 结构化。

不要只返回一段字符串。至少要有:

status
content
errorCode
durationMs
outputChars
truncated
retryable

第四步,引入 RunState 和 RunStep。

先存在内存里也行。关键是让每个 step 有证据。

第五步,加 TraceSink。

先写 JSONL,不用上来就接复杂平台。你要能看到:哪一步调用了模型,哪一步执行了工具,token 用了多少,错误在哪里。

第六步,加 CheckpointStore。

先保存 run-state-latest.json。后面再补 snapshot、file patch、tool ledger 的重放策略。

第七步,把权限从工具里拿出来。

工具自己判断不了'用户是否允许'。权限是 runtime 和产品策略的交界,不应该散落在各个工具函数里。

做到这里,你会发现原来的 Loop 还在,但它已经变薄了。

这就是好事。

不要把框架当 Runtime 答案

LangGraph 官方说自己是面向 long-running、stateful agents 的 orchestration runtime,提供 durable execution、streaming、human-in-the-loop、persistence 等能力。这个定义很接近我们在书里讲的 Runtime。

但工程判断不能停在'用了某框架,所以有 Runtime'。

你还要继续问:

工具权限在哪里做?
工具参数本地策略在哪里做?
checkpoint 里有没有不可重放工具状态?
trace 能不能定位错误归因?
预算是按 token、step、wall-clock、tool-call 四类都管了吗?
模型输出坏 JSON 时是崩溃,还是 observation?
用户中途改文件时,resume 会不会覆盖?

这些问题不是为了否定框架。

框架能省掉很多底层工作,但不能替你定义业务风险。Runtime 最终是产品责任,不是依赖包名字。

这本书后面要补的代码

我会把这一套逐步整理到这个仓库:

https://github.com/zeeklog/agent-runtime-engineering


推荐阅读

  • LangGraph 到底是不是 Agent Runtime?
  • Agent Framework 会被模型原生 Tool Use 淘汰吗?
  • MCP 解决了什么,又没有解决什么?

目录

  1. 从 Agent Loop 重构成 Agent Runtime
  2. 100 行 Loop 的甜蜜期很短
  3. 重构不是为了抽象,是为了隔离责任
  4. Runtime 的主线顺序不能乱
  5. RunState 是 Runtime 的脊柱
  6. Tool Call 不是函数调用
  7. Runtime 要接受“部分成功”
  8. 一个实用的重构路径
  9. 不要把框架当 Runtime 答案
  10. 这本书后面要补的代码
  11. 推荐阅读
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Python 核心语法详解:测试脚本开发基础
  • 在 Linux 桌面里跑 Windows 应用:Winboat 实战笔记
  • AI 编程工具深度对比:Trae、Cursor、Copilot 与 Windsurf
  • 论文阅读--Agent AI 探索多模态交互的前沿领域(一)
  • OpenCode 开源 AI 编程助手使用教程
  • Linux diff 与 patch 命令实战指南
  • 网络安全工程师职业定义、核心技能与认证体系详解
  • Python 为何如此流行?深度解析其核心优势与应用场景
  • 大疆无人机如何导出日志并解析
  • 前端 WebSocket 通信实战与最佳实践
  • OpenClaw 进阶教程:记忆系统、定时任务、多模型与子代理解析
  • Python 爬虫实战指南:从基础请求到分布式框架
  • Kubernetes Python 客户端实战教程
  • Java IO 核心:BufferedReader、BufferedWriter、PrintStream 与 PrintWriter 详解
  • AI 辅助编程工具:GitHub Copilot 安装与使用指南
  • 基于 Rokid AR 眼镜的聚会游戏助手开发实践
  • 百瑞互联 BR8654A02 蓝牙 6.0 SOC 芯片规格介绍
  • Windows 7 安装 Python 3.9+ 配置指南
  • 前端加密:常用方式与使用示例
  • GitHub Copilot 学生认证教程(2026 版)

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online