能做和能上线之间,隔着一条叫”可靠性”的鸿沟。

ChatGPT 刚出来那会儿,很多人都有过一种错觉:这东西太强了,给它接上 API、挂上工具,是不是就能代替人干活了?我自己也这么想过。你让它帮你查个天气、订个外卖,它确实能聊得头头是道,感觉离”数字员工”就差一步。

但真动手做过 Agent 项目的人都知道,从”能聊天”到”能干活”这一步,远比想象中难。差距不在提示词写得好不好——Chatbot 只需要把话说对,Agent 还要把事做对。前者出错了你最多翻个白眼,后者出错了你可能是数据库没了。

所以一个能干活的 Agent,最小可行结构是什么样的?从 Demo 里跑通一个 Agent 到真正把它推上生产,中间到底缺了什么?这篇文章想聊聊这件事。

先从最基本的结构讲起。不管外面包装得多花哨,一个 Agent 的核心循环其实就这么简单:

1
2
3
4
5
while not done:
thought = llm.think(context) # 想
action = llm.decide(thought) # 决定做什么
observation = tools.execute(action) # 做
context.update(observation) # 把结果记下来

就这几行。想、做、看、记,循环往复,直到任务完成。几乎所有 Agent 框架——不管是 LangChain 的 AgentExecutor 还是 AutoGPT 的主循环——本质上都是这个结构的变体。

这个循环里有三个组件:LLM 是大脑,负责理解意图、规划步骤、做出决策;Tools 是手脚,搜索引擎、数据库、API、文件系统,都是 Agent 和外部世界交互的通道;Memory 是记忆,让 Agent 知道自己之前干了什么、走到哪一步了。没有记忆的 Agent 就像一个失忆症患者,每一步都在重复上一轮的思考。

Memory 这块值得展开说,因为它在实践中的选择比大多数人想的要复杂。光”记住之前做了什么”这一件事,就有好几种方案,各有各的适用场景和坑:

方案 原理 适合场景 局限性
对话缓冲(Buffer) 把所有历史对话原样塞进 context 轮次少、token 预算充裕的短任务 轮次一多就爆 context,完全没有压缩
滑动窗口(Window) 只保留最近 N 轮,更早的丢弃 对话型 Agent、轮次多但旧上下文不关键 丢掉的可能是关键信息,”第 3 轮说过的话”彻底消失
摘要压缩(Summary) 用 LLM 把历史对话压缩成摘要 长链任务、上下文重要但不需要原文 压缩过程本身消耗 token,且可能丢失细节
向量存储(Long-term) 把历史交互写入向量数据库,按需检索 跨会话记忆、知识量大的场景 检索质量依赖 embedding 模型,增加了一层复杂度
结构化状态(State) 用 key-value 或 JSON 保存明确的任务状态 流程化任务、需要精确追踪进度 只能存预定义的字段,灵活性差

没有哪个方案是万能的。短任务用对话缓冲就够了,长链任务需要摘要压缩,跨会话场景才值得上向量存储。实际生产中往往是组合使用——滑动窗口保近期细节,摘要压缩保中期记忆,向量存储保长期知识。选型的关键是想清楚:你的 Agent 到底需要”记住什么”,以及能为记忆付出多少 token 预算。

把这三个东西拼到一起,就得到了 ReAct 模式。ReAct 是 Reasoning + Acting 的缩写,最早在 2022 年的一篇论文里被系统地提出来。它的核心直觉很朴素:Agent 不应该”想完再动”或者”不动只想”,而应该边想边动、边动边想。每一步思考之后立即执行一个动作,观察动作的结果,再基于这个结果决定下一步。Thought → Action → Observation,循环推进。

这个模式之所以成为事实上的标准,就是因为它足够自然。人解决复杂问题的时候就是这么干的——你不会在脑子里把所有步骤都推演完毕再动手,你先做一步看看结果,然后调整。ReAct 把这个直觉形式化了。

但 ReAct 是一个好的起点,却不是一个好的终点。它只解决了 Agent “能不能跑”的问题,没解决”能不能靠谱地跑”的问题。

这就引出了一个更现实的问题:为什么裸 Agent 上不了生产?

说”上不了”可能有点绝对。更准确地说,一个没有工程包装的裸 Agent,在生产环境中几乎必然失败。失败的方式还挺多样的:

  • 工具调用不可控。Agent 觉得需要某个工具就调了,但你没给它设边界。让它管理服务器,它可能心血来潮把负载均衡器给删了——因为从它的推理链来看,”减少不必要的组件”是合理的。听起来荒谬,但 LLM 的推理链里这种”逻辑自洽但结果灾难”的情况一点都不罕见。

  • 上下文爆炸。Agent 循环每走一步,上下文就膨胀一圈——历史对话、工具返回结果、中间推理过程,全部塞进去。跑个十几轮之后,token 飙升到模型上下文窗口的天花板,Agent 开始丢失关键信息或者做出荒腔走板的判断。更糟的是,有些框架根本没有上下文管理机制,就让它一直膨胀到崩掉。

  • 错误不可恢复。Agent 调用了一个工具,工具报错了。然后呢?裸 Agent 的典型行为是把错误信息塞回上下文,让 LLM 再想想。但 LLM 面对错误信息时往往会陷入重复尝试的死循环——用同样的方式再调一次,再失败,再重试。没有退避策略,没有降级方案,一个 tool 调用的失败就能把整个任务卡死。

  • 行为不可观测。Agent 跑挂了,你打开日志一看,密密麻麻的文本输出,没有结构化的 trace 链路,没有步骤编号,没有 cost 统计。你甚至不知道它到底是在第三步还是第八步出的错,也不知道那一步到底调了什么工具、传了什么参数。出了问题不知道哪步出错,排查起来就是大海捞针。

  • 幻觉导致执行危险操作。这是最让人后怕的。LLM 会”编”出一些不存在的东西,这在聊天场景里顶多是个笑话,但在 Agent 场景里可能是个灾难。举个最直白的例子:Agent 负责管理数据库,用户让它”清理测试数据”,Agent 幻觉出了一个合理的 SQL,执行了 DROP TABLE users——不是 test_users,是 users。表没了。生产数据没了。而且它还觉得自己做对了。

说白了,就是不可靠。

这些失败模式不是理论推演,每一个都是实际踩过的坑。裸 Agent 就像一个没有刹车、没有仪表盘、没有安全带的车——发动机能转,但你不会开上高速。

所以问题的本质其实变了:不是”Agent 能不能做”,而是”怎么让 Agent 在可控的范围内做”。这就需要一层东西包在外面。

Harness 这个词在工程领域用得很广泛,通常翻译成”线束”或者”测试套件”,但在 Agent 的语境下,我更愿意把它理解成包裹在 Agent 外层的工程基础设施——负责控制、监控、恢复。如果 Agent 本身是发动机,Harness 就是围绕发动机搭建的整车系统:油路控制、仪表盘、安全气囊、ABS 防抱死。

拿工具权限来说。裸 Agent 的问题是所有工具对所有场景一视同仁,但这在生产环境里是不可接受的。Harness 要做的第一件事就是按角色、按场景给工具加权限——Agent 在执行查询任务的时候,不应该有写入和删除的权限;在测试环境里可以随意操作,切到生产环境,所有危险操作必须走审批。最小权限原则,和给服务器做权限管理是同一个逻辑。

上下文管理是另一个容易踩的坑。裸 Agent 的上下文只增不减,但 Harness 需要一套策略来控制膨胀:滑动窗口,只保留最近 N 轮的完整交互,更早的只保留摘要;让 LLM 自己把长对话压缩成关键信息;把任务目标、用户约束、安全规则这些不能丢的东西钉在 context 头部,确保不管怎么裁剪,核心指令始终在线。这块容易被忽视,但在长链任务里其实是生存必需——上下文一崩,Agent 就等于失忆了。

再往下就是错误恢复。裸 Agent 遇到错误就卡住,Harness Agent 遇到错误会有一套策略。具体怎么选,取决于错误的类型和你的容错偏好:

策略 做法 适用场景 代价
立即重试(Retry) 同样的调用再来一次 临时性故障,比如网络抖动、API 限流 如果是持久性故障,纯属浪费时间
指数退避(Backoff) 第 1 次等 1s,第 2 次等 2s,第 3 次等 4s API 限流、下游过载 总延迟变长,需要设上限防止无限等待
降级(Fallback) 换一个备用工具或 API 主路径不可用但有替代方案 替代方案的结果质量可能不如主方案
跳过(Skip) 跳过当前步骤,用默认值继续 非关键步骤失败,不值得阻塞整体 结果不完整,需要在报告里标注
断路器(Circuit Break) 多次失败后直接中断,通知上层 持续性故障、下游彻底不可用 任务直接中止,需要人工介入

这些模式在传统的微服务工程里已经非常成熟了,只不过现在需要搬到 Agent 的场景里重新实现一遍。一个常见的组合是:遇到错误先退避重试三次,仍然失败就降级,降级也失败就断路——层层递进,比”出错了让 LLM 再想想”靠谱得多。

可观测性做的事听起来不性感,但在生产环境里极其重要。结构化日志,每一步的输入输出都有标准化的格式;trace 链路,类似 OpenTelemetry 那种,你能看到 Agent 从第一步到最后一步,每一步调了什么工具、传了什么参数、花了多少 token、返回了什么结果。cost tracking 也很关键——Agent 跑一次任务烧了多少 token、花了多少钱,得有数。不然月底一算账,发现 Agent 的 API 调用费比预期多了两个数量级,那就不是技术问题了,是业务问题。

还有一件事不得不提:不是所有决策都应该交给 Agent 自主完成。删除操作、大额支出、不可逆的配置变更——这些关键节点,Harness 应该暂停执行,等人类确认之后才继续。这看起来像是”退步”,毕竟 Agent 的卖点就是自主性。但在生产环境里,完全自主和完全不可控之间只有一线之隔。在关键决策前加一个确认步骤,不是对 Agent 能力的不信任,是对业务安全的最低尊重。

用同一个任务串起来看会更直观。假设任务是:部署一个新版本的服务到生产环境。

裸 Agent 的版本大概是这样的:Agent 接到任务,开始规划步骤——拉取最新代码、运行测试、构建镜像、部署上线。看起来没问题。但跑到部署那一步的时候,Agent 决定先把旧版本的容器全部停掉再启动新版本——这确实是一种部署策略,只不过是一种会导致服务中断的策略。它还顺手把旧的 Docker 镜像清理了。新版本启动之后发现有个配置没对,服务起不来。Agent 想回滚,但旧镜像已经被删了。你去看日志,发现只有一堆 LLM 的文本输出,没有结构化的执行记录,排查了半天才知道是第三步的环境变量写错了。 meanwhile,生产环境已经断了二十分钟。

Harness Agent 的版本完全不同。同样是部署任务,但在执行之前,Harness 先检查权限——这个 Agent 只有测试环境的写入权限,生产环境的部署操作需要触发确认机制。人类审批通过后,Agent 开始执行,每一步的操作都被记录在结构化的 trace 链路里。部署策略被限制在预设的白名单里——只允许蓝绿部署或滚动更新,不允许一次性停掉所有旧容器。镜像清理被标记为危险操作,必须经过二次确认。如果在任何一步出错——比如新版本启动失败——断路器立即触发,自动回滚到旧版本,同时把错误详情和完整 trace 发给值班人员。

整个过程里,Agent 的推理能力没变,LLM 还是那个 LLM。但外层的 Harness 确保了:权限有边界、操作有记录、错误有兜底、关键节点有人把关。

维度 裸 Agent Harness Agent
工具权限 所有工具随时可用 按场景、角色授权
上下文管理 无限膨胀直到崩溃 滑动窗口 + 摘要压缩
错误处理 重复尝试或直接卡死 重试 + 降级 + 断路器
可观测性 文本日志,难以排查 结构化 trace + cost tracking
安全控制 关键操作需人类确认

一句话:Agent = LLM + Tools + Memory,生产级 Agent = Agent + Harness。ReAct 给了 Agent 一个能跑的骨架,Harness 给了它上生产的资格。

骨架搭好了,但光有骨架还不够。一个只会用工具的 Agent,和一个真正理解业务、能基于领域知识做判断的 Agent,中间还差一样东西——知识。下篇聊聊 RAG,聊聊怎么给 Agent 的骨架里注入灵魂。