不要迷信“超级 Agent”:生产级 AI Agent 的 12 条工程实践
Agent 是过去一年 AI 应用开发中最火的概念之一。
从 AutoGPT 到 LangGraph,从 CrewAI 到各种开源 Agent 平台,人们似乎越来越接近一个诱人的愿景:只要给 AI 一个目标,再给它一些工具,它就能像“数字员工”一样自主规划、调用 API、处理异常、完成复杂任务。
这个愿景确实令人兴奋。因为传统软件开发中,程序员需要提前设计好流程、分支、异常处理和状态管理;而 Agent 看起来像是把这些工作交给了模型。开发者不再需要把所有路径写死,只需要告诉模型“你可以使用哪些工具”,剩下的就由模型自己判断。
但真正把 Agent 放进生产环境后,很多团队都会遇到同一个问题:Demo 很惊艳,做到 70% 或 80% 也不难,可剩下的 20% 却异常困难。模型会选错工具,会重复尝试失败路径,会在长上下文里忘记最初目标,会把无关信息当作关键线索,甚至会陷入循环。
于是,Agent 从“看起来像数字员工”,变成了“看起来像一个很聪明但不稳定的实习生”。
Dex Horthy 在《12-Factor Agents》中提出的核心观点,正是对这种现象的反思:真正可靠的 Agent,并不是靠无限放大模型的自主性实现的,而是靠成熟的软件工程实现的。
换句话说,Agent 不是魔法,也不是可以替代工程设计的黑盒。它依然是软件系统的一部分,必须接受软件工程中的控制流、状态管理、权限校验、错误恢复、可观测性和人机协作机制。
这篇文章将围绕 12 条实践原则,重新梳理生产级 Agent 应该如何设计。重点不在于介绍某个框架,而是理解一种更底层的工程思想:LLM 应该负责什么?代码应该负责什么?Prompt、Context、Tool Calling、状态和人类反馈之间,到底是什么关系?
一、Agent 的理想与现实:从流程图到动态决策
传统软件本质上是一张流程图。
无论是早期业务系统,还是今天复杂的微服务、数据平台、自动化流水线,本质上都是开发者提前设计好执行路径:第一步做什么,第二步做什么,遇到某种情况走哪个分支,失败以后如何重试,最终结果如何返回。
Airflow、Prefect、Dagster 这类工作流编排系统虽然增强了调度、监控、重试和可观测性能力,但它们的核心仍然是一张 DAG,也就是有向无环图。开发者始终掌握着流程控制权。
机器学习出现以后,软件系统变得“智能”了一些。例如在客服系统里增加情感分析,在数据处理流程里加入文本分类模型,在推荐系统里使用预测算法。但这些模型通常只是流程中的某个节点。整体流程仍然由开发者控制,模型只是确定性系统中的一段智能逻辑。
Agent 的想象力在于,它试图改变这种模式。
过去开发者需要定义完整路径,而 Agent 希望开发者只定义目标和工具。比如用户说“帮我部署最新版本后端”,系统不再提前写死每一步,而是让模型自己判断:是否要先查看 Git 标签,是否要检查 CI 状态,是否要请求人工审批,是否要执行部署命令,是否要把结果通知到 Slack。
这意味着软件从“流程驱动”走向“目标驱动”。开发者不再规定每一步怎么走,而是给模型一些可用工具,让模型根据当前上下文动态选择路径。
这种方式当然很有吸引力。因为现实业务场景复杂,很难提前穷举所有可能路径。如果模型真的能够自主规划,软件开发的复杂度似乎会大幅下降。
但问题也正在这里。
当模型获得过多控制权以后,系统就开始变得不可预测。当前许多 Agent 框架采用的是经典循环模式:模型读取上下文,选择下一步行动;系统执行工具,把结果写回上下文;模型继续判断下一步。这个循环看起来很通用,但在长任务里很容易失控。
随着任务推进,上下文会越来越大。最初只有用户需求,后来加入工具调用记录、执行结果、错误信息、中间状态、用户回复、系统提示、RAG 检索内容等。模型每一轮都要在这些信息里找出真正重要的部分。即使未来上下文窗口扩大到百万 Token,也不意味着问题自动解决。因为问题不只是“放不放得下”,更是“模型能不能关注到真正重要的信息”。
上下文越长,噪声越多;噪声越多,模型越容易偏离目标。
所以,生产级 Agent 的核心不是让模型更自由,而是让模型在合适的边界内发挥作用。最有效的 Agent 往往不是一个无所不能的超级 Agent,而是嵌入确定性工作流中的小型 Agent。它只处理那些需要语言理解、模糊判断和灵活决策的局部环节,其他部分仍然交给传统软件系统负责。
二、让模型负责决策,而不是执行
理解 Agent 的第一条原则,是把 LLM 放在正确的位置上。
很多人一开始会把 Agent 想象成“模型调用工具完成任务”。比如用户说:“请帮我给 Terri 创建一个 750 美元的付款链接,用于赞助二月份的 AI Tinkerers 活动。”于是 Agent 就去调用 Stripe API 创建付款链接。
但严格来说,模型并没有真正调用 Stripe API。
模型真正擅长的是理解自然语言,并把自然语言转换成结构化意图。它可以从用户的话里提取金额、对象、用途、备注,然后输出类似这样的结构化结果:
{
"function": "create_payment_link",
"parameters": {
"amount": 750,
"customer": "Terri",
"product": "AI Tinkerers sponsorship",
"memo": "February event sponsorship"
}
}
真正创建付款链接的,是后端代码。代码会检查参数是否合法,用户是否有权限,金额是否超过限制,然后调用 Stripe API,处理异常,记录日志,返回结果。
这个区分非常重要。
LLM 应该负责“判断下一步应该做什么”,而不是直接“执行这件事”。执行层永远应该掌握在确定性代码手里。因为执行涉及权限、安全、事务、一致性、异常处理和审计,这些都不是大模型最擅长的事情。
如果让模型直接控制执行流程,系统风险会迅速升高。比如模型决定删除客户数据,如果系统立即执行 delete_customer(),后果可能非常严重。但如果模型只是输出:
{
"intent": "delete_customer",
"customer_id": "123"
}
系统就可以在执行之前加入权限校验、人工审批、风险评估、审计日志甚至二次确认。
这就是生产级 Agent 的基本职责划分:
- LLM 负责理解和决策;
- 代码负责执行和约束;
- 业务系统负责权限、安全、状态和结果管理。
不要让模型直接做事,要让模型决定“建议做什么”。至于这个建议是否执行、什么时候执行、如何执行,应该由软件系统决定。
三、Prompt 不是配置项,而是代码
很多 Agent 框架为了降低门槛,会提供非常简洁的接口:
agent = Agent(...)
task = Task(...)
result = agent.run(task)
开发者只需要填写角色、目标、工具,就能快速跑起来一个 Agent。对于 Demo 或原型验证,这确实很方便。但一旦进入生产环境,这种便利就可能变成问题。
因为很多框架会隐藏大量 Prompt Engineering 逻辑。开发者看到的是几个配置字段,模型真正接收到的却可能是几百甚至上千个 Token 的系统指令、工具描述和行为规范。这些内容由框架维护,开发者很难完全掌握。
当 Agent 表现不符合预期时,你很难判断问题在哪里:是模型不行,是上下文组织不好,是工具描述有歧义,还是框架底层 Prompt 模板写得不适合业务?
所以,作者提出一个很重要的原则:不要把 Prompt 外包给框架。
在 Agent 系统里,Prompt 已经不是简单的提示文本,而是程序逻辑的一部分。它决定模型如何理解任务、如何选择行动、如何处理异常、如何输出结构化结果。从这个意义上说,Prompt 就是一种新的代码。
如果 Prompt 是代码,它就应该像代码一样被管理:进入版本控制,有清晰的变更记录,可以被测试,可以做灰度发布,也可以根据场景持续迭代。
很多开发者会把大量精力花在“调 Prompt”上,但调完以后只是把它放在一个配置文件里,缺少测试和工程化管理。这样做在早期可以,但长期维护会非常痛苦。
更好的方式是把 Prompt 纳入正式工程体系。比如为不同场景准备测试样例,检查模型是否能输出正确结构;记录每次 Prompt 修改对输出质量的影响;在模型版本变化时重新跑评估集;把 Prompt 与业务逻辑一起审查。
这听起来麻烦,但生产系统本来就需要这些能力。Agent 系统不是一次性写完的,而是在真实业务中不断迭代。Prompt 如果不被当成代码维护,系统就会越来越不可控。
四、真正决定 Agent 上限的不是 Prompt,而是 Context
很多人以为 Agent 效果不好,是 Prompt 不够好。于是不断修改系统提示词,调整角色设定,增加规则,优化输出格式。
但在复杂 Agent 系统中,Prompt 只是上下文的一部分。
模型真正看到的内容包括系统提示、用户输入、历史对话、工具调用记录、工具返回结果、错误信息、RAG 检索结果、长期记忆、结构化输出 Schema 和当前任务状态。
这些整体才是 Context。
LLM 本质上是一个无状态函数。它不会真的记得上一次发生了什么,它只是根据当前输入生成输出。所谓 Agent 的“记忆”,其实是开发者把历史信息重新组织后塞进上下文里。因此,Agent 的能力很大程度上取决于开发者如何构建上下文。
这就是 Context Engineering 的核心。
优秀的上下文工程不是把更多信息塞给模型,而是把最有价值的信息以最高密度呈现给模型。上下文越长,不一定效果越好。很多时候,短而清晰的上下文比庞杂的历史记录更有效。
现在大多数 Agent 框架使用类似 OpenAI 的 Message 格式:
[
{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{"role": "assistant", "tool_calls": [...]},
{"role": "tool", "content": "..."}
]
这种格式简单统一,也适合普通聊天。但复杂任务中,它未必是最优结构。
因为模型真正需要知道的不是某条消息的 role 是什么,而是:任务目标是什么,已经发生了哪些关键事件,调用了什么工具,工具返回了什么结果,当前还有什么阻塞,下一步有哪些可选行动。
所以,我们可以不把 Context 理解为“聊天记录”,而是理解为“系统状态”。
例如,一个部署 Agent 的上下文不一定要写成连续对话,而可以组织成事件流:
<user_request>
请部署最新版本后端
</user_request>
<list_git_tags_result>
最新标签为 v1.8.3
</list_git_tags_result>
<ci_status>
测试通过
</ci_status>
<human_approval>
负责人已批准部署
</human_approval>
这种形式更接近日志系统或事件流,也更适合让模型快速理解任务状态。
上下文工程的目标,不是还原所有细节,而是帮助模型在当前时刻做出正确判断。该保留的保留,该压缩的压缩,该删除的删除。尤其是工具调用记录、错误堆栈、重复信息,如果不加处理地全部塞进去,很容易污染上下文。
可以说,Prompt 决定模型的行为边界,而 Context 决定模型的判断质量。很多时候,优化 Context 比优化 Prompt 更重要。
五、工具调用本质上只是结构化输出
“Tool Calling”经常被描述成 Agent 的核心能力。很多框架会说,模型能够调用函数、访问数据库、发送邮件、执行代码,仿佛模型真的获得了操作外部世界的能力。
但更准确的理解是:工具调用只是结构化输出。
当用户说“帮我创建一个 Jira 工单”时,模型并没有真的访问 Jira。它只是生成一段结构化数据:
{
"intent": "create_issue",
"title": "Payment API Timeout",
"description": "支付接口出现超时问题",
"assignee": "alice"
}
然后代码读取这段数据:
if result["intent"] == "create_issue":
jira.create_issue(
title=result["title"],
description=result["description"],
assignee=result["assignee"]
)
真正调用 Jira API 的始终是程序。
这个理解能帮助我们摆脱一个常见误区:工具不应该被简单等同于函数。很多框架会把工具和函数强绑定,模型一旦选择某个工具,系统就立即执行对应函数。这种模式在低风险场景下没问题,但在生产环境中很危险。
更好的方式是把 Tool 看成一种“结构化意图”。模型输出的是“想做什么”,不是“立即怎么做”。
比如模型输出:
{
"intent": "deploy_backend",
"version": "v1.8.3"
}
系统可以根据业务规则决定后续动作:立即部署,先检查部署窗口,先发起审批,写入任务队列,创建异步任务,等待人工确认,或者拒绝执行并返回原因。
这就把 Agent 从“直接执行工具的黑盒”变成了“提出行动建议的决策组件”。
一旦接受这个视角,我们就会更自然地加入安全边界。高风险操作不应该由模型说了算,而应该由软件系统根据结构化意图进行判断。模型负责灵活理解,系统负责稳定执行。
六、状态应该回归单一事实来源
复杂 Agent 系统很容易出现两套状态。
一套是执行状态,比如当前执行到哪一步、是否正在等待、重试了几次、下一次什么时候唤醒。另一套是业务状态,比如用户提出了什么请求、模型做了什么决策、工具返回了什么结果、人工审批是否通过。
如果这两套状态分开维护,系统复杂度会迅速上升。你需要保证它们同步,需要处理状态不一致,需要在调试时同时查看日志、数据库、工作流状态机和消息队列。
作者建议尽可能统一执行状态和业务状态,让 Agent 的历史记录成为单一事实来源。
这和 Event Sourcing 的思想很接近。系统不直接保存一个“当前状态”,而是保存所有已经发生的事件。当前状态可以从事件历史中推导出来。
比如一个部署任务的事件流可能是:
[
{"type": "user_requested_deploy", "version": "latest"},
{"type": "agent_selected_action", "intent": "list_git_tags"},
{"type": "tool_result", "tool": "list_git_tags", "result": "v1.8.3"},
{"type": "agent_selected_action", "intent": "request_human_approval"},
{"type": "human_approved", "user": "tech_lead"},
{"type": "agent_selected_action", "intent": "deploy_backend"},
{"type": "tool_result", "tool": "deploy_backend", "result": "success"}
]
从这串事件中,我们可以知道任务已经完成,也可以知道每一步为什么发生。系统不一定需要额外维护一个复杂状态机,因为当前状态可以从事件流里计算出来。
这种统一状态模型有几个明显好处。
它让系统更简单。所有事实都在同一个事件流里,减少状态同步问题。
它也更容易恢复。任务中断以后,只要重新加载事件历史,再追加新的事件,就能继续运行。
它还更容易调试。开发者可以完整回放 Agent 的决策过程,知道它为什么选了某个工具,工具返回了什么,错误发生在哪里。
甚至,它还可以实现“时间旅行”。如果某一步决策错了,可以从历史节点复制一份新线程,让 Agent 基于相同上下文重新尝试。
当然,不是所有数据都应该进入上下文。访问令牌、Session ID、密码、数据库连接信息等敏感数据不应该暴露给模型。但原则上,真正不能进入上下文的数据应该越少越好。否则 Agent 的行为就会越来越依赖外部隐藏状态,降低可恢复性和可解释性。
最理想的情况是:Agent 的历史记录就是 Agent 的状态。
七、Agent 应该像普通程序一样运行:可以暂停,也可以恢复
很多人想象中的 Agent 是一个持续运行的循环:模型思考,调用工具,读取结果,再思考,再调用工具,直到任务完成。
但真实业务并不总是连续完成的。很多任务需要等待:等待人工审批,等待用户回复,等待第三方 API,等待异步任务完成,等待部署窗口,等待定时触发,等待 Webhook 回调。
如果 Agent 在等待期间一直运行,会浪费资源,也会增加复杂度。更合理的方式是让 Agent 能够暂停和恢复。
当 Agent 遇到需要等待的环节时,它应该保存当前事件历史,然后停止运行。等外部事件发生,比如用户在 Slack 里批准了部署,系统再把这个批准事件追加到历史记录中,重新启动 Agent,让它继续判断下一步。
这时恢复 Agent 并不需要复杂的运行时环境。只要 Agent 的状态已经保存在事件流里,恢复过程就很简单:加载历史事件,追加新事件,构建当前上下文,调用模型生成下一步动作,再由系统决定是否执行。
这使 Agent 更像一个普通服务,而不是一个神秘的长时间运行进程。它可以被 Slack 消息触发,可以被 GitHub Webhook 唤醒,可以被审批系统暂停,也可以被 CI/CD 流水线恢复。
换句话说,Agent 不应该是一段持续运行的循环,而应该是一种可以随时启动、暂停和恢复的任务。
这也是 Agent 真正融入企业软件架构的关键。它不需要取代现有系统,而是作为一个标准组件嵌入其中。
八、把人类也视为一种“工具”
在真实业务中,很多关键决策不应该由模型独立完成。比如生产环境部署、客户退款、删除数据、发送重要邮件、修改合同条款等,都需要人类确认。
传统聊天机器人通常是“人问,AI 答”。但生产级 Agent 需要一种更主动的人机协作方式:当 Agent 遇到关键决策时,可以主动联系人类。
这时,人类反馈也可以被抽象成一种特殊工具调用。
比如 Agent 输出:
{
"intent": "request_human_input",
"question": "是否继续部署到生产环境?",
"approver": "tech_lead"
}
系统收到这个结构化意图后,不会继续执行部署,而是暂停 Agent,把问题发送给负责人。负责人通过 Slack、邮件或企业微信回复后,系统把回复作为新事件写入事件流,再恢复 Agent。
这样一来,人类审批、用户确认、业务判断都被纳入了 Agent 的标准工作流。对模型来说,联系人类和查询 API 没有本质区别,都是为了获得下一步决策所需的信息。
这个设计非常重要,因为它突破了聊天界面的限制。Agent 不一定总是等待用户主动提问,它也可以由监控告警、定时任务、业务事件触发,然后在需要人类判断时主动联系相关人员。
这就是所谓的 Outer Loop Agent。它不是被动聊天机器人,而是能在外部业务循环中主动工作的数字同事。
当人类也成为工作流中的节点时,Agent 才能真正处理高价值任务。因为高价值任务往往伴随高风险,而高风险任务必须有人工确认、权限控制和审计机制。
九、不要把工作流控制权交给 Agent 框架
很多 Agent 框架会自动管理 Agent Loop:模型选择工具,框架执行工具,把结果返回给模型,然后继续循环,直到任务结束。
这很方便,但生产环境中不能把控制流完全交给框架。
原因是不同工具调用需要不同处理策略。
比如模型想查询 Git 标签,这种低风险操作可以立即执行;但如果模型想部署生产环境,就应该暂停并请求审批;如果模型要删除数据,就应该经过权限校验和人工确认;如果模型启动了长时间任务,就应该写入队列并等待异步回调。
这些控制逻辑很难完全交给通用框架。因为它们强烈依赖具体业务。
生产级 Agent 的一个关键能力是:系统应该能在“模型选择工具”和“工具真正执行”之间中断。
这一步非常关键。模型可以说“我建议执行部署”,但系统应该有机会检查这个建议,决定是否执行、何时执行、由谁审批、如何记录。
如果框架一旦收到工具调用就立即执行,开发者就失去了最重要的控制点。最终只能在几个不理想的方案里选择:要么让 Agent 只能做低风险任务,要么让高风险任务也自动执行,要么自己绕开框架做复杂补丁。
更合理的设计是:LLM 负责输出下一步行动建议,业务代码负责控制流程。
控制流里可以加入权限校验、人工审批、限流、缓存、上下文压缩、运行日志、评审机制、异步任务、错误重试和风险拦截。
一句话:模型决定“做什么”,系统决定“是否做、什么时候做、如何做”。
十、让错误成为 Agent 学习的一部分
传统程序里,错误通常意味着流程中断。但 Agent 系统里,错误可以成为上下文的一部分。
如果工具调用失败,不一定马上终止任务。可以把错误信息作为事件写入上下文,让模型在下一轮推理时看到失败原因,并尝试修正。
比如 API 返回“参数缺失”,模型可能会补充参数后重新调用;如果返回“资源不存在”,模型可能会先查询资源列表;如果返回“权限不足”,模型可能会请求人工介入。
这种机制让 Agent 具备一定的自我修复能力。
但错误信息不能无限累积。否则模型可能陷入错误循环:不断尝试类似方法,不断失败,不断把错误塞进上下文,最终上下文越来越混乱。
所以,错误也需要工程化处理。
系统可以设置重试阈值,比如连续失败三次后停止自动尝试,升级给人工处理。也可以对错误信息进行压缩,只保留关键原因,而不是把完整堆栈全部塞给模型。还可以把多次失败总结成一句状态描述:
部署尝试已失败 3 次,主要原因是目标服务器权限不足,需要人工确认凭据配置。
错误不是流程终点,而是上下文输入。但错误反馈的目标不是让 Agent 无限重试,而是在有限范围内帮助它恢复。如果超过恢复能力,就应该交给人类或其他确定性流程处理。
十一、让 Agent 专注于一件事
很多人一开始想做万能 Agent:一个 Agent 负责所有任务,拥有所有工具,能处理所有业务流程。
这通常是失败的开始。
因为任务范围越大,Agent 需要处理的上下文越复杂,工具选择越困难,推理步骤越长,出错概率越高。模型会更容易忘记最初目标,也更容易混淆不同任务之间的边界。
更好的方式是构建多个小而专注的 Agent。
比如:
- 部署 Agent 只负责部署;
- 客服 Agent 只负责工单分流;
- 文档 Agent 只负责生成说明文档;
- 故障诊断 Agent 只负责分析监控和日志;
- 财务 Agent 只负责整理报销材料。
每个 Agent 的职责应该清晰,工具集应该有限,工作流程最好保持在 3 到 10 步以内,即使复杂任务也尽量不要超过 20 步。
小型 Agent 的好处很多。
上下文更短,模型更容易聚焦;工具更少,选择错误概率更低;职责更清楚,测试和调试更容易;系统更模块化,可以像微服务一样组合。
与其构建一个什么都会做但经常失败的大 Agent,不如构建多个边界清晰、行为稳定的小 Agent。真正可用的智能系统,往往不是一个大脑控制一切,而是多个专用组件协同工作。
十二、让 Agent 出现在用户工作的地方
很多人把 Agent 设计成一个独立聊天界面,用户需要打开它,输入问题,然后等待回答。
但在企业场景里,用户真正工作的地方可能是 Slack、邮件、企业微信、短信、工单系统、监控平台、CRM 或代码仓库。真正有价值的 Agent,不应该要求用户专门来到某个 AI 应用里,而应该出现在用户原本工作的地方。
例如,开发者可以直接在 Slack 里说“部署最新后端版本”;负责人可以通过邮件批准一次生产变更;客服人员可以在工单系统里让 Agent 总结客户问题;运维系统可以在监控告警触发后自动启动故障诊断 Agent;财务人员可以在企业微信里确认一笔付款申请。
这背后的关键,不只是多渠道接入,而是 Agent 要能融入真实工作流。
在很多场景下,Agent 的触发者甚至不一定是人。它可以由外部事件启动,比如监控系统发现异常、CI/CD 流水线构建完成、定时任务到达执行时间、客户提交高优先级工单、数据指标突破阈值、合同状态发生变更,或者代码仓库出现新的 Pull Request。
Agent 在后台开始工作,完成能自动完成的步骤;当遇到需要判断、审批或补充信息的地方,再主动联系相关人员。用户不需要一直盯着 Agent,也不需要坐在聊天框前陪它一步步执行。
这就让 Agent 更像一个数字同事,而不是一个问答机器人。
问答机器人通常是被动的:用户问一句,它答一句。生产级 Agent 则应该是主动协作的:它能被事件触发,能自己推进流程,也能在需要时找到正确的人。
这种设计与前面提到的暂停恢复、人类作为工具、事件流状态管理是连在一起的。因为只有 Agent 能够暂停,才能等待人类回复;只有状态能被持久化,才能过几个小时甚至几天后恢复;只有人类反馈能被写入事件流,Agent 才能继续基于完整上下文做判断。
所以,Agent 的产品形态不一定是一个聊天窗口。它可能是 Slack 里的一个机器人,是邮件里的一个审批助手,是工单系统里的一个按钮,是监控平台里的一个自动诊断流程,也可能是隐藏在业务系统背后的自动化决策组件。
优秀的 Agent 往往不是用户主动寻找的工具,而是在恰当的时候出现,主动推动事情向前发展的协作者。
十三、把 Agent 看成状态转换器
如果把前面的原则合在一起,会得到一个很简洁的 Agent 定义:
Agent 并不是一个拥有神秘自主意识的智能体,而是一个根据当前上下文生成下一步行动的状态转换器。
这个思想可以借用函数式编程里的 Reducer 来理解。在 Redux 或 Event Sourcing 这类架构中,系统当前状态不是凭空存在的,而是由历史事件一步步计算出来的。Reducer 的工作非常简单:输入当前状态和一个新事件,输出新的状态。
Agent 也可以这样理解。
每一轮执行时,它读取当前上下文,也就是到目前为止发生过的事件,然后输出下一步行动建议。这个行动被系统处理后,又会产生新的事件。新的事件进入历史记录,成为下一轮推理的输入。
可以把它想成这样:
历史事件 + 新事件
↓
构建上下文
↓
LLM 推理
↓
结构化行动建议
↓
业务系统执行或暂停
↓
产生新事件
在这个模型里,Agent 本身不需要保存复杂内部状态。它不需要一直运行,也不需要记住所有事情。它只需要在被调用时,根据输入上下文做一次判断。
这带来了很大的工程优势。
它更容易恢复。因为状态不在 Agent 内部,而在事件历史中。任何时候,只要重新加载事件历史,就能恢复任务。
它更容易测试。你可以给 Agent 一段固定上下文,看它会输出什么行动建议。这样 Agent 的行为就可以被评估,而不是只能靠人工观察。
它更容易扩展。多个 Agent 可以基于同一套事件流工作,也可以各自负责不同类型的状态转换。
它更容易调试。因为每一次输入、输出、工具结果、人工反馈都被记录为事件,开发者可以完整追踪系统行为。
这种设计也能帮助我们摆脱对“Agent 自主性”的过度迷信。Agent 的价值不是它能脱离系统独立运行,而是它能作为软件架构中的一个智能决策组件,在合适的位置完成状态转换。
它不是替代整个软件系统,而是增强某些原本难以编码的环节:自然语言理解、模糊意图判断、复杂信息总结、下一步行动建议。
十四、一个更接近生产环境的 Agent 架构
为了更直观地理解这些原则,可以想象一个生产级部署 Agent 的架构。
用户在 Slack 里输入:
帮我部署最新版本后端。
系统不会让模型直接执行部署,而是把这条消息记录为一个事件:
{
"type": "user_message",
"channel": "slack",
"content": "帮我部署最新版本后端"
}
然后系统构建上下文,把任务目标、可用工具、最近事件和必要约束交给模型。模型输出结构化行动建议:
{
"intent": "list_git_tags"
}
业务代码判断这是低风险查询,于是立即执行工具,得到结果:
{
"type": "tool_result",
"tool": "list_git_tags",
"result": ["v1.8.1", "v1.8.2", "v1.8.3"]
}
系统再次调用模型。模型判断最新版本是 v1.8.3,接下来需要检查 CI 状态:
{
"intent": "check_ci_status",
"version": "v1.8.3"
}
CI 检查通过后,模型提出部署建议:
{
"intent": "deploy_backend",
"version": "v1.8.3",
"environment": "production"
}
但系统不会立刻执行。因为生产部署是高风险操作。业务代码拦截这个意图,生成审批请求:
{
"type": "approval_requested",
"question": "是否批准部署后端 v1.8.3 到生产环境?",
"approver": "tech_lead"
}
Agent 暂停。负责人收到 Slack 消息后点击批准。系统追加新事件:
{
"type": "human_approved",
"approver": "tech_lead",
"content": "批准部署"
}
Agent 恢复,根据新的事件历史继续推理。模型再次输出部署行动建议。系统确认审批已通过,于是执行部署。部署成功后写入事件流,并通知用户。
整个过程中,模型从未直接控制外部系统。它只是不断根据上下文提出下一步建议。真正的执行、审批、暂停、恢复、安全控制和日志记录,都由软件系统完成。
这就是 12-Factor Agents 想强调的工程化思路。
十五、结语:Agent 的成熟,是重新回到软件工程
Agent 的真正价值,不在于让模型摆脱约束、自由行动,而在于让模型在可靠的软件架构中承担最适合它的角色。
它擅长理解自然语言,擅长从复杂信息中提取意图,擅长生成结构化建议,擅长总结错误并尝试修正,擅长在有限上下文中判断下一步。但它不擅长承担系统执行、安全边界、事务一致性、权限控制和长期状态管理。
所以,生产级 Agent 的方向不是“给模型更多权限”,而是“给模型更清晰的边界”。
不要构建一个无所不能、不可预测的超级 Agent。更好的方式是构建一组小而专注、可控可测、能暂停恢复、能与人协作、能嵌入业务系统的 Agent。
从这个角度看,Agent 并不是传统软件工程的终结,而是传统软件工程的新阶段。它让自然语言成为新的交互入口,让 LLM 成为新的决策组件,但系统的可靠性依然来自工程设计。
未来真正成功的 Agent 产品,可能不是那些看起来最“自主”的系统,而是那些在关键时刻最稳定、最可解释、最容易恢复、最懂得向人类求助的系统。
也就是说,Agent 的成熟,不是从软件工程中逃离,而是重新回到软件工程。
更多推荐

所有评论(0)