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

《Agent Runtime 工程化》为什么你的 Agent Demo 永远上不了生产?

为什么你的 Agent Demo 永远上不了生产? 写一个 Agent Demo 真的不难。 一个模型,几个 tools,再加一个 while loop。模型说要调用工具,你执行工具;工具返回结果,你再塞回模型。顺利的时候,二三百行代码就能

陈堂会发布于 —3 浏览
《Agent Runtime 工程化》为什么你的 Agent Demo 永远上不了生产?

为什么你的 Agent Demo 永远上不了生产?

写一个 Agent Demo 真的不难。

一个模型,几个 tools,再加一个 while loop。模型说要调用工具,你执行工具;工具返回结果,你再塞回模型。顺利的时候,二三百行代码就能跑出一个让人挺兴奋的 demo。

麻烦一般发生在上线后。

比如一个很普通的订单场景:

用户:帮我创建一张订单
  ↓
Agent 调用 create_order()
  ↓
接口超时
  ↓
Agent 认为失败,开始 retry
  ↓
create_order() 又执行了一次
  ↓
用户收到两张订单

这时候你很难把锅甩给模型。

模型只是看到了一个超时。真正的问题是:你的系统没有告诉它这次工具调用到底有没有产生副作用,也没有为这次业务意图生成稳定的幂等键,更没有一份 tool ledger 记录'这个 create_order 已经发出去过'。

Agent 没有神秘地变坏。你的 runtime 太薄了。

社区里早就有这些味道了

如果你搜过 LangChain、LangFlow 或各种 agent 项目的 issue,会看到一个反复出现的句子:

Agent stopped due to max iterations.

LangChain 在 2023 年就有人遇到类似问题:Agent 一直尝试工具调用,最后被最大迭代次数拦住。LangChainJS 也有 'agent stuck in a loop' 的 issue,输出里同样出现 max iterations。LangFlow 到 2025 年还有用户报告这个现象。

这不是某个框架'写得不够聪明'。这是 Agent Loop 的基本事实:只要下一步由模型决定,循环就有不收敛的可能。

如果你的系统只有 prompt 和 tools,没有 stop reason、重复调用检测、失败分类、trace 和预算,max iterations 最后就会变成一个很粗暴的保险丝。保险丝当然要有,但保险丝跳了以后,你仍然不知道房子里哪根线短路。

更麻烦的是,Agent 不只是会卡住。它还会一边卡住,一边花钱,一边改东西。

Demo 为什么总是显得很顺

Demo 环境通常太友好了。

工具不会真的失败。即使失败,也只返回一句 'error'。上下文刚好够用。文件很少。用户不会中途取消。模型不会连续三次传错参数。Shell 命令不会挂住。接口不会在'已经创建成功但响应丢失'的尴尬时刻超时。

生产环境不吃这一套。

生产环境的问题更像这样:

  • 模型传了一个 schema 上合法、业务上危险的参数。
  • 工具返回 500,但副作用可能已经发生。
  • 第 17 步时上下文被压缩,用户最开始的限制条件没了。
  • 读文件工具吐出两万行日志,直接挤掉了真正重要的 observation。
  • Agent 反复调用同一个搜索工具,因为它不知道前两次失败是同一个失败。
  • 用户拒绝了高风险命令,但最终回答里看不出它被拒绝过。
  • 崩溃恢复后,系统把已经执行过的写操作又跑了一遍。

你看,问题不再是'模型会不会思考'。问题变成了'系统能不能承载一个不稳定的决策源'。

这就是 Agent Runtime 要解决的事。

Tool calling 不是 Runtime

现在模型 API 的 tool calling 已经比早期舒服很多。OpenAI 的文档里,工具调用有明确的 call id,工具输出也可以是结构化 JSON 或普通文本;Structured Outputs 还能让模型输出贴合你给的 JSON Schema。

这些都很有用。

但它们解决的是接口层的一部分问题,不是运行时问题。Schema 能减少'少字段、类型错、JSON 乱飞'这类低级错误,却不能替你判断:

  • 这个工具有没有副作用?
  • 这次失败能不能重试?
  • 第二次调用是不是同一个业务意图?
  • 用户是否授权它访问这个目录?
  • 输出太大时应该截断、摘要,还是保存成 artifact?
  • 工具成功了,但模型最终回答没引用观察结果,要不要拦?

Schema 是门槛。Runtime 是秩序。

这句话我不想说得太玄。落到代码里,其实就是几个很朴素的结构。

type ToolSpec = {
  name: string;
  description: string;
  schema: unknown;
  riskLevel: "read" | "write" | "network" | "destructive";
  timeoutMs: number;
  retryPolicy?: RetryPolicy;
  outputPolicy: ToolOutputPolicy;
};

type ToolResult =
  | { ok: true; content: string; artifactId?: string }
  | {
      ok: false;
      code:
        | "INVALID_ARGUMENTS"
        | "NOT_FOUND"
        | "PERMISSION_DENIED"
        | "TIMEOUT"
        | "SIDE_EFFECT_UNCERTAIN";
      message: string;
    };

很多 demo 不写这些东西,因为写了不炫。可一旦上线,恰恰是这些东西救命。

Retry 最容易骗过工程师

重试看上去是可靠性手段,写起来也简单:

for (let i = 0; i < 3; i++) {
  try {
    return await tool.call(input);
  } catch (err) {
    await sleep(1000 * (i + 1));
  }
}

这段代码的问题是,它不知道自己在重试什么。

读文件失败了,重试大概率没事。创建订单失败了,重试就要小心。扣款、发券、发邮件、删除文件、提交 PR、执行数据库 migration,这些工具都带副作用。你不能只看异常类型,还要知道'上一次请求有没有被对方收到''副作用有没有发生''这一次 retry 和上一次是不是同一个业务意图'。

AWS Builders Library 专门写过一篇关于 idempotent APIs 和安全重试的文章。Stripe 的 API 文档也明确建议在创建或更新对象时使用 idempotency key,这样网络错误后可以用同一个 key 安全重试,避免创建第二个对象或执行两次更新。

把这个思想搬到 Agent Runtime 里,就是:工具调用不能只是函数调用。它至少要有一份账本。

type ToolLedgerEntry = {
  runId: string;
  stepId: string;
  toolName: string;
  inputHash: string;
  idempotencyKey?: string;
  status: "started" | "succeeded" | "failed" | "uncertain";
  sideEffect: "none" | "possible" | "committed";
};

当 create_order() 超时,runtime 不能只把 TIMEOUT 丢给模型。它要把这次调用标记成 SIDE_EFFECT_UNCERTAIN,接下来优先查订单状态,而不是直接再创建一次。

这个细节很土,但生产系统就靠这种土办法活着。

Max iteration 不是停止条件的全部

很多 Agent 框架会提供 maxIterations 或 maxSteps。这东西必须有,但它只是最后一道闸。

更好的 runtime 会记录停止原因:

type StopReason =
  | "final_answer"
  | "max_steps"
  | "max_tokens"
  | "timeout"
  | "permission_denied"
  | "repeated_tool_call"
  | "same_error_repeated"
  | "side_effect_uncertain";

这比一句'达到最大迭代次数'有用得多。

如果停止原因是 repeated_tool_call,你要查工具描述是不是误导了模型。如果是 same_error_repeated,你要看 observation 有没有给出可修复信息。如果是 max_tokens,问题可能在 Context Builder。如果是 permission_denied,最终回答应该告诉用户哪一步被拒绝,而不是装作什么都没发生。

我见过不少 demo 的循环长这样:

while not done:
  call model
  if tool_calls:
    call tools
  else:
    done

这不是不能用。它适合教学,适合视频演示,适合第一天证明'模型能调工具'。但它还缺少工程系统最关心的东西:

  • 这一步为什么开始?
  • 这一步用了哪些上下文?
  • 工具调用是否合法?
  • 工具结果有没有被截断?
  • 有没有重复调用同一个高风险工具?
  • 失败后是否应该让模型继续?
  • 中断后能从哪里恢复?

这些问题没有答案,Agent Demo 就只能停留在 Demo。

聊天记录不是 Trace

很多团队第一次排查 Agent 问题,会先打开聊天记录。

聊天记录当然有用,但它不是 trace。它通常看不到 timeout、duration、token、工具输入摘要、权限决策、输出截断、artifact id、retry 次数,也看不到'模型为什么没看见某个文件'。

OpenTelemetry 对 trace、span、attribute、event 的划分已经很成熟。一个 span 表示一次有开始和结束的操作,attribute 记录键值信息,event 记录某个有时间点意义的事件。Agent Runtime 没必要重新发明可观测性,但需要把 Agent 专属事实记进去。

我会把一次 run 拆成这几类 span:

agent.run
  model.call
  context.build
  tool.call
  permission.decision
  checkpoint.save

每个 tool.call 至少记录:

{
  toolName: "create_order",
  inputHash: "sha256:...",
  status: "timeout",
  durationMs: 31200,
  sideEffect: "uncertain",
  retryCount: 1,
  idempotencyKey: "order:user_42:cart_998"
}

有了这个,你才知道事故怎么发生。没有它,复盘会变成猜谜。

第一版 Runtime 先做什么

如果你现在手里有一个 Agent Demo,别急着换框架,也别急着把 prompt 改得更长。先补六件事。

第一,定义统一消息结构。用户消息、模型消息、tool call、tool result 分清楚,不要靠字符串拼接糊在一起。

第二,写 Tool Registry。哪怕只有三个工具,也要有 schema、risk level、timeout、retry policy 和 output policy。

第三,所有工具返回结构化结果。失败不要只写 error: string,至少分出 INVALID_ARGUMENTS、NOT_FOUND、PERMISSION_DENIED、TIMEOUT、OUTPUT_TOO_LARGE、SIDE_EFFECT_UNCERTAIN。

第四,加停止原因。maxSteps 要有,但重复工具调用、重复错误、超时、预算耗尽、权限拒绝也要变成明确 stop reason。

第五,给有副作用的工具加 ledger 和 idempotency key。尤其是创建、更新、删除、支付、发消息、执行命令这类工具。

第六,写最小 trace。不要等接入完整观测平台才开始记录。先落 JSON 文件都行,只要能还原一次 run。

做到这里,你的系统未必已经是产品。但它已经不像玩具了。

真正的分水岭

我现在判断一个 Agent 项目有没有进入工程化,基本不看它 demo 视频多顺。

我看这些东西:

  • 有没有 stop reason。
  • 有没有 tool failure taxonomy。
  • 有没有 permission decision。
  • 有没有 context budget。
  • 有没有 tool ledger。
  • 有没有 checkpoint。
  • 有没有 trace。
  • 有没有 eval 能复现失败。

这些东西听上去没有'多智能体协作'酷,也没有'自动完成复杂任务'好卖。但一个 Agent 真到生产环境,最后天天救火的就是这些模块。

写 Agent Demo 是让模型动起来。

写 Agent Runtime 是让它出错时还能被控制。

我最近在把这套东西整理成《Agent Runtime 工程化》。后面会继续把架构图、代码样例、Production Checklist 和试读章节放出来。下一篇先讲一个最容易吵起来、也最值得讲清楚的问题:

Agent Loop、Harness、Runtime,到底是什么关系?

参考资料

  • LangChain issue: "Agent stopped due to max iterations."
  • LangChainJS issue: agent stuck in a loop
  • LangFlow issue: Agent stopped due to max iterations
  • OpenAI Docs: Function calling
  • OpenAI Docs: Structured model outputs
  • AWS Builders Library: Making retries safe with idempotent APIs
  • Stripe Docs: Idempotent requests
  • OpenTelemetry Docs: Traces

推荐阅读

  • Agent Loop、Harness、Runtime 到底是什么关系?
  • Agent 生产事故 #001:Retry 为什么会导致重复下单?
  • 一次 Agent 任务为什么能烧掉几十次 LLM 调用?

目录

  1. 为什么你的 Agent Demo 永远上不了生产?
  2. 社区里早就有这些味道了
  3. Demo 为什么总是显得很顺
  4. Tool calling 不是 Runtime
  5. Retry 最容易骗过工程师
  6. Max iteration 不是停止条件的全部
  7. 聊天记录不是 Trace
  8. 第一版 Runtime 先做什么
  9. 真正的分水岭
  10. 参考资料
  11. 推荐阅读
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 转行网络安全学习指南与核心技能路径
  • Linux 系统基础:体系、命令与 Vim 编辑器
  • 基于 ROS 和 EtherCAT 的机器人多轴协同控制实现方案
  • Whisper 模型本地化部署:版本下载与离线环境搭建
  • Ubuntu 22.04 生产环境部署 FastAPI + Uvicorn + Nginx 实战
  • 微信群机器人配置与使用指南
  • 后仿 SDF 反标 Warning 描述与解决方案
  • Docker 部署 MySQL 8.0 完整指南:从拉取镜像到配置远程访问
  • ChatGPT 与 DALL·E 制作日漫风格小故事全流程
  • Stable Diffusion WebUI 本地安装与配置教程
  • Python 核心语法详解:测试脚本开发基础
  • 在 Linux 桌面里跑 Windows 应用:Winboat 实战笔记
  • AI 编程工具深度对比:Trae、Cursor、Copilot 与 Windsurf
  • 论文阅读--Agent AI 探索多模态交互的前沿领域(一)
  • OpenCode 开源 AI 编程助手使用教程
  • Linux diff 与 patch 命令实战指南
  • 网络安全工程师职业定义、核心技能与认证体系详解
  • Python 为何如此流行?深度解析其核心优势与应用场景
  • 大疆无人机如何导出日志并解析
  • 前端 WebSocket 通信实战与最佳实践

相关免费在线工具

  • 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