12-Factor Agents:构建生产级AI Agent的十二个核心原则 Best Partners TV 2025-09-08

引言:从概念到落地的Agent挑战

AI行业的朋友们普遍感觉到,Agent(Agent: 一种能够感知环境、进行决策并执行行动以达成特定目标的软件实体)正在从概念走向实际落地。然而,要让Agent从“能跑起来”进化到“能稳定用起来”,中间仍存在一道巨大的鸿沟。今天将探讨的“12-Factor Agents”(12-Factor Agents: 一套用于构建生产级AI Agent的设计原则),即Agent的十二个设计原则,正是为了填平这道鸿沟而生。

这套原则并非像LangChain(LangChain: 一个用于开发基于大语言模型的应用程序的开源框架)那样可以直接使用的“工具箱”,而是一套“方法论”。它类似于多年前软件开发领域的“12-Factor App”(12-Factor App: 一套用于构建可靠、可伸缩、可维护的SaaS应用程序的方法论)原则,创造性地将传统软件工程中经过验证的最佳实践应用到大模型驱动的Agent开发中。其核心目标明确:弥补Agent从原型开发到生产级应用的鸿沟,使Agent在可靠性、可伸缩性、可维护性、可调试性和安全性上达到企业级应用的标准。

项目背景与核心理念

在详细介绍这十二个原则之前,有必要了解一些项目背景。12-Factor Agents项目的发起者是一家名为HumanLayer(HumanLayer: 一家由德克斯特·霍西创立的旧金山创业公司,专注于Agent领域)的旧金山创业公司。其创始人是德克斯特·霍西(Dexter Horthy: HumanLayer创始人,资深软件工程师和产品经理),他17岁便在NASA喷气推进实验室(NASA Jet Propulsion Laboratory: 美国国家航空航天局下属的一个研究中心,负责行星探测器和地球观测任务)编程,后在开发工具公司Replicated(Replicated: 一家专注于开发工具和本地软件交付的公司)工作七年,从工程师晋升至产品经理和高管,曾协助Hashicorp(Hashicorp: 一家提供多云基础设施自动化软件的公司)、DataStax(DataStax: 一家提供企业级Apache Cassandra数据库解决方案的公司)等知名企业交付本地K8s(Kubernetes: 一个开源的容器编排系统,用于自动化部署、扩展和管理容器化应用程序)产品。这段经历使他对“生产级系统”的严谨性有着深刻理解,因为分布式系统的可靠性和容器编排的可扩展性是他多年来日常面对的问题。

真正促使他转向Agent领域的是2023年的一个项目。当时他正在构建一个基于Slack(Slack: 一款团队协作和即时通讯工具)的Agent,旨在管理SQL(Structured Query Language: 结构化查询语言,用于管理关系型数据库)数据库。开发过程中,他发现了一个常被忽视的关键问题:Agent在执行任务时,如何有效地将人类“拉进来”?例如,当Agent需要执行一条可能影响生产数据的SQL语句时,它不能自行决定,而需要人类审核同意。然而,传统的Agent设计模式是让人类坐在屏幕前等待AI的反馈,AI不会主动寻求人类的反馈。这个“Human-in-the-Loop”(Human-in-the-Loop: 人类在环,指将人类的判断和决策融入到自动化系统或AI系统中,以提高系统的准确性、可靠性和安全性)的痛点,不仅促使他创立了HumanLayer,更直接催生了12-Factor Agents的核心理念。霍西的愿景是推动Agent从人机问答聊天的“第一代”和框架驱动的“第二代”,进化到“第三代”自主Agent,即能自主处理事务,在无法处理时及时寻求人类帮助,实现高效与安全并存。

目前,12-Factor Agents项目在GitHub(GitHub: 一个基于Web的代码托管平台,用于软件开发和版本控制)上拥有13.8k星标和978个fork。其代码主要由TypeScript(TypeScript: 一种由微软开发的开源编程语言,是JavaScript的超集,提供了静态类型检查)编写,辅以少量Python(Python: 一种广泛使用的高级编程语言,以其简洁的语法和丰富的库而闻名)。整个项目最核心的创新是其提出的“反框架理念”(Anti-Framework Philosophy: 一种软件设计理念,强调开发者应完全控制核心组件,而非依赖黑盒框架,以提升透明度、可调试性和可维护性)。与传统框架不同,它不提供“黑盒式”解决方案,而是坚持让开发者“完全控制核心组件”。这种设计的原因在于,在企业级应用中,透明度、可调试性和可维护性比“开发速度”更为重要。例如,在金融领域的Agent中,开发者必须清楚每一步的逻辑和数据流向,不能将核心逻辑交给框架的黑盒处理。

12-Factor Agents的十二个设计原则

以下是12-Factor Agents的十二个设计原则,每个原则都对应着生产级Agent的一个关键需求。

原则1:自然语言到工具调用 (Natural Language to Tool Calls)

Agent的核心能力应是将人类的自然语言指令准确转换为结构化的工具调用。这意味着Agent并非直接“回答问题”,而是在理解问题后,能够调用合适的工具去解决问题。例如,用户指令“帮我查一下上个月的销售总额,然后生成一个Excel报表”,Agent不会直接输出数字,而是将其转换成两个结构化的工具调用:第一个调用“销售数据查询工具”,参数为“时间范围:上个月”;第二个调用“Excel生成工具”,参数为“数据来源:销售数据查询结果”。

这个原则之所以重要,是因为自然语言具有模糊性,而工具调用则是精确的。结构化的工具调用使Agent的行为变得“可预测”,也方便开发者调试。例如,如果Agent未生成报表,开发者可以检查“是不是工具调用的参数错了”,而非猜测“是不是Agent没有理解自然语言”。落地此原则的关键在于设计清晰的工具接口,明确每个工具的功能、所需参数和返回结果,以便大语言模型能够准确调用。

原则2:拥有你的提示词 (Own your prompts)

提示词是大模型应用的“灵魂”,但许多框架将提示词隐藏在内部。例如,LangChain在调用时,用户无法看到它向模型发送了什么提示词。因此,此原则强调开发者必须能完全控制提示词的设计、修改和版本管理。

“拥有提示词”的重要性在于,不同业务场景需要不同的提示词。例如,同为客服Agent,销售电子产品的和销售服装的,其提示词的语气和所需包含的产品知识均不同。如果提示词隐藏在框架中,开发者无法根据业务需求进行调整,Agent的效果将大打折扣。此外,在生产环境中,提示词的版本管理也至关重要。例如,修改提示词后Agent准确率下降时,需要能够回滚到之前的版本。若提示词在框架内部,则无法进行版本控制。落地此原则的建议是,将提示词像“代码”一样管理,存储在代码仓库中,进行版本控制,并可进行A/B测试,以选择效果最佳的提示词版本。

原则3:拥有你的上下文窗口 (Own your context window)

大模型的上下文窗口长度是有限的,如何管理上下文直接影响Agent的性能。此原则的核心是,框架不能替开发者决定哪些内容应保留在上下文里,哪些应删除。开发者需要自己控制上下文窗口的管理逻辑。

例如,一个处理长对话的Agent,用户聊天半小时后上下文窗口将满,此时应删除什么?是删除最早的对话、重复信息,还是不重要的细节?不同场景的答案各异。如果是客服对话,可能需要保留用户的核心诉求(如“要退款”),删除无关闲聊;如果是技术支持对话,可能需要保留之前提到的报错信息,删除无关产品介绍。如果框架替你做决策(如默认删除最早的对话),可能导致Agent忘记用户的核心诉求,进而做出错误判断。因此,开发者必须自己设计上下文管理逻辑,例如基于“信息的重要性”排序,优先保留关键信息,删除次要信息。落地此原则需要开发者定义信息重要性的评分标准,将用户需求、系统关键参数、工具调用结果等视为高重要性信息予以保留,将礼貌用语、重复提问等视为低重要性信息予以删除。

原则4:工具只是结构化输出 (Tools are just structured outputs)

此原则有些反直觉,它将“工具调用”理解为模型的一种结构化输出,而非“Agent去调用一个外部工具”。例如,模型输出的不是“我要调用销售数据查询工具”,而是一个JSON(JavaScript Object Notation: 一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成)格式的结构化数据。

这种理解方式能够简化系统架构。Agent无需“知道如何调用工具”,只需“输出结构化的工具调用指令”,再由专门的“工具执行模块”去解析此指令并调用对应的工具。这样一来,Agent的逻辑和工具执行的逻辑便相互分离,更易于维护。例如,如果“销售数据查询工具”的接口发生变化,开发者只需修改“工具执行模块”的代码,无需更改Agent的逻辑。若要新增工具,也只需在“工具执行模块”中增加新的工具实现,Agent只需输出对应的结构化指令即可。落地此原则的关键是统一工具的调用格式,例如所有工具调用都采用JSON格式,包含工具名和参数两个字段,以便“工具执行模块”能够使用统一的逻辑进行解析。

原则5:统一执行状态和业务状态 (Unify execution state and business state)

在传统的Agent设计中,执行状态(如“正在调用工具”或“等待用户反馈”)和业务状态(如“用户的订单号”或“销售数据查询结果”)通常是分开管理的。执行状态存储在Agent内存中,业务状态存储在数据库中。这很容易导致“状态不一致”问题。例如,Agent以为自己已调用工具,但实际上工具调用失败,业务状态未更新,从而导致Agent后续行为出错。

此原则的核心是将执行状态和业务状态统一管理,形成一个“全局状态”。例如,全局状态中既包含当前步骤等待工具返回结果的执行状态,也包含工具调用参数为时间范围等于上个月的业务状态,以及工具返回结果为销售额100万的业务状态。这样做的好处是,一旦Agent出错(如进程崩溃),重启后可以从全局状态中恢复,因为它知道自己之前执行到哪一步,有哪些参数和结果,从而无需重新开始。例如,Agent在调用工具后崩溃,重启后通过查看全局状态,发现已调用工具但尚未处理结果,便可直接获取工具返回结果并继续执行后续步骤。落地此原则建议使用专门的状态存储服务,如Redis(Redis: 一个开源的内存数据结构存储系统,可用作数据库、缓存和消息代理)或其他数据库来管理全局状态。每个Agent任务都有一个唯一的ID,通过ID查询对应的全局状态。

原则6:使用简单的API来完成启动、暂停和恢复 (Launch/Pause/Resume with simple APIs)

在生产环境中,Agent可能需要处理长时间运行的任务,例如生成一份年度销售报告,这可能涉及调用多个工具、处理大量数据,耗时数小时。此时,如果需要暂停任务或任务失败后进行恢复,就需要简单的API进行操作。

此原则的核心是Agent的启动、暂停、恢复都应通过简单的API来实现,而非通过修改代码或配置。例如,调用“POST /agent/start”启动任务,调用“POST /agent/pause?task_id=xxx”暂停任务。需要简单API的原因在于,在生产环境中,这些操作可能由运维人员通过自动化工具执行,而非开发者手动操作。例如,系统维护时,运维工具可以批量调用“暂停API”暂停所有正在运行的Agent任务;维护结束后,再批量调用“恢复API”让任务继续执行。落地此原则需要设计符合RESTful(Representational State Transfer: 一种基于HTTP协议的软件架构风格,用于设计网络应用程序接口)风格的API,明确每个API的参数、返回值和错误处理逻辑。例如,对于“暂停API”,如果任务已暂停,可以返回“400 Bad Request”并提示“任务已经处于暂停状态”。

原则7:通过工具调用来联系人类 (Contact humans with tool calls)

这是12个原则中最具创新性的一个,其核心是让Agent通过“调用人类工具”的方式主动联系人类获取反馈,而非等待人类主动找它。这里的“人类工具”可以理解为专门用于和人类交互的工具,例如“发送审批通知工具”或“获取人类反馈工具”。

例如,Agent要执行一条修改生产数据库的SQL语句,它会先调用“发送审批通知工具”,参数为“审批内容:执行SQL语句xxx,审批人:数据库管理员”。数据库管理员通过审批后,Agent会调用“获取人类反馈工具”,拿到“审批通过”的结果,再执行SQL语句。传统的人机交互是“从人类到AI”,而此原则将其变为“从AI到人类”,让Agent主动发起交互,人类被动响应。这种方式特别适合生产环境,因为人类无需时刻盯着Agent,Agent只在需要时才寻求人类帮助,既高效又安全。落地此原则需要设计专门的“人类交互工具”,例如集成企业微信、Slack、邮件等通知渠道,让Agent可以通过这些渠道联系人类;同时设计一些反馈收集机制,例如人类点击通知中的“同意或拒绝”按钮后,反馈结果能够自动回到Agent的全局状态中。

原则8:拥有你的控制流 (Own your control flow)

控制流即Agent的执行逻辑,例如先调用工具A,再根据工具A的结果决定调用工具B还是工具C。此原则的核心是开发者必须完全控制控制流,不能让框架替开发者决定执行逻辑。

许多框架提供预定义的控制流,如“顺序执行链”或“条件分支链”,但企业级应用的控制流往往非常复杂。例如,一个供应链Agent需要先调用“库存查询工具”,若库存充足则调用“发货工具”;若库存不足,则调用“采购工具”,采购完成后再调用“发货工具”,中间还要考虑采购失败的情况,此时可能还需要调用“通知采购经理工具”。如果采用框架的预定义控制流,很难覆盖如此复杂的逻辑,即使能覆盖,后续修改也十分麻烦。因此,开发者必须自己设计控制流,例如用代码写清楚“每一步该做什么,遇到什么情况该如何处理”。落地此原则建议采用“状态机”(State Machine: 一种数学计算模型,表示一个系统在不同状态之间转换的抽象模型)的思想来设计控制流,每个状态对应Agent的一个执行步骤,状态之间的转换由“条件”触发。例如,“库存查询完成”状态的转换条件包括“从库存充足到发货状态”,或从“库存不足到采购状态”,以及从“查询失败到重试状态”。

原则9:将错误压缩到上下文窗口 (Compact Errors into Context Window)

Agent在生产环境中必然会出错,例如工具调用失败、模型返回错误信息、参数格式不正确等。此原则的核心是把错误信息以紧凑的形式放入模型的上下文窗口中,让模型能够根据错误信息调整行为。

例如,Agent调用“销售数据查询工具”时,如果因参数“时间范围”格式错误导致工具返回错误,提示参数的时间范围必须符合日期格式。此时,Agent应将此错误信息压缩后放入上下文窗口中,再让模型重新生成工具调用。模型看到错误信息后,便会将错误的日期格式修正为正确的。为何需要“压缩”错误信息?因为上下文窗口长度有限,如果错误信息过长(如几百行的堆栈信息),将占用大量窗口空间,导致其他重要信息被删除。因此,需要将错误信息提炼为“关键信息”,包括错误类型、错误原因、解决方案提示等。落地此原则需要设计错误信息的压缩逻辑,例如提取错误类型、错误来源、错误原因、修复建议,并去除无关的堆栈信息和日志ID等。

原则10:小而专注的Agent (Small, Focused Agents)

此原则借鉴了微服务(Microservices: 一种软件架构风格,将应用程序构建为一组松散耦合的、可独立部署的服务)的思想:不应构建一个“无所不能”的超级Agent,而是构建多个“小而专注”的Agent,每个Agent只负责一个特定的任务域。例如,不应构建一个“企业管理Agent”,而是拆分为“销售数据Agent”(负责查询、分析销售数据)、“库存管理Agent”(负责查询库存、触发采购)和“客服Agent”(负责处理客户咨询)。每个Agent只关注自己的任务域,这样逻辑更简单,维护也更方便。

“小而专注”的重要性在于,超级Agent的逻辑过于复杂,容易出错且难以调试。例如,一个超级Agent既能处理销售数据,又能处理库存,还能处理客服,一旦出现问题,开发者需在几百行代码中查找原因;而小的Agent出现问题,只需查看对应任务域的代码,效率会高很多。此外,小的Agent也便于团队分工,销售团队可负责维护“销售数据Agent”,库存团队负责维护“库存管理Agent”,无需所有人都关注一个超级Agent。落地此原则的关键在于“任务域的拆分”,根据业务模块、职责范围来拆分Agent,明确每个Agent的输入、输出和工具调用,避免功能重叠。例如,“销售数据Agent”的输出是“销售报表”,“库存管理Agent”的输入是“销售报表”,两者通过结构化的数据交互,无需共享内部逻辑。

原则11:从任何地方触发 (Trigger from anywhere)

企业中的系统多种多样,包括Slack、企业微信等沟通工具,CRM(Customer Relationship Management: 客户关系管理,一种管理公司与客户交互的方法)、ERP(Enterprise Resource Planning: 企业资源规划,一种管理公司核心业务流程的系统)等业务系统,以及定时任务、Webhook(Webhook: 一种HTTP回调机制,当特定事件发生时,系统会自动向预设的URL发送HTTP POST请求)等自动化工具。此原则的核心是Agent应能从任何这些地方触发,而非只能从某个固定的界面触发。

例如,“销售数据Agent”可以通过以下几种方式触发:一是运营人员在Slack中发出“查询上月销售数据”的消息;二是CRM系统在新订单超过100万时自动触发Agent生成报表;三是每周一早上8点,定时任务触发Agent生成周报表。之所以需要“从任何地方触发”,是因为这样才能让Agent“融入”到企业的现有工作流中,而非让员工去“适应”Agent。例如,习惯使用Slack工作的运营人员无需特意打开Agent界面,即可在Slack中触发任务。业务系统发生事件时,Agent也能自动响应,无需人工干预。落地此原则需要设计“触发适配器”,针对不同的触发来源开发对应的适配器。例如,Slack适配器负责监听Slack消息并解析触发指令;CRM适配器负责监听CRM事件触发Agent;定时任务适配器负责按时间触发Agent。每个适配器的输出都应是统一的“触发指令”,这样Agent无需关心指令来源。

原则12:让你的Agent成为无状态的归约器 (Make your agent a stateless reducer)

此原则借鉴了函数式编程(Functional Programming: 一种编程范式,将计算视为数学函数的求值,并避免使用可变状态和副作用)中的“归约器”(Stateless Reducer: 一种函数式编程概念,指一个纯函数,接收当前状态和输入,计算并返回新的状态,但不修改原始状态或依赖任何外部可变状态)概念,即Agent本身是无状态的,其行为仅由“当前的输入”和“外部的状态”所决定,不依赖内部存储的状态。归约器的逻辑是接收“当前状态”和“输入”,处理后输出“新的状态”。例如,Agent接收的“当前状态”是已调用销售数据查询工具但未生成报表,输入是销售数据查询结果为100万,处理后输出的新状态是已生成报表,等待发送给运营人员。

实现“无状态”的重要性在于,无状态的Agent更易于扩展。例如,当有100个用户同时触发销售数据Agent时,服务器可以启动100个Agent实例,每个实例处理一个用户的任务,无需共享状态。如果某个实例崩溃,其他实例也不受影响,新的实例可以从外部状态中恢复任务。落地此原则的关键是把所有状态都放在外部存储中,Agent只负责处理输入、生成新的状态,不自己保存任何状态。例如,Agent处理任务时,先从外部存储获取当前状态,处理后再将新状态写回外部存储,然后即可释放资源,下次处理时再重新获取状态。

原则的协同作用与框架互补

这12个原则并非孤立存在,而是相互配合的。例如,原则12“无状态归约器”需要原则5“统一执行状态和业务状态”来支撑;原则11“从任何地方触发”需要原则1“自然语言到工具调用”来保证指令的统一;原则7“通过工具调用联系人类”需要原则10“小而专注的Agent”来降低交互复杂度。它们共同构成了一套完整的生产级Agent设计体系,解决了可靠性、可扩展性、可维护性等核心问题。

许多朋友可能会问,有了LangChain、LlamaIndex(LlamaIndex: 一个用于构建基于大型语言模型的应用的数据框架)、AutoGen(AutoGen: 微软开发的一个开源框架,用于通过多个可定制、可对话的Agent来开发LLM应用)这些框架,为何还需要12-Factor Agents?需要明确的是,它们并非竞争关系,而是一种互补关系。12-Factor Agents是“设计原则”,框架是“实现工具”,原则可以用来指导工具的使用。简单来说,12-Factor Agents是球队的“教练”,告诉队员们“怎么打才能赢”,而框架是“球员”,负责执行具体的战术。例如,你可以用LangChain来搭建Agent,但要遵循12-Factor Agents的原则,自己控制提示词(原则2)、自己管理上下文窗口(原则3)、统一状态管理(原则5)等。这样搭建出来的Agent才能符合生产级的标准。因此,12-Factor Agents并非要取代任何框架,而是要让框架的价值最大化。许多开发者使用框架进行原型开发很顺手,但一到生产环境就出问题,本质上是因为没有遵循工程化的原则。12-Factor Agents正是为了填补这个空白,让框架从“原型工具”变成“生产工具”。

总结与展望

对于从事Agent开发的朋友们,12-Factor Agents的启发不仅限于技术层面,更多体现在思维层面。

首先是“设计思维的转变”,即从“追求快速原型”转向“注重生产质量”。过去我们可能认为“先跑起来再说”,但现在必须意识到在生产环境中,“稳定”比“快”更重要。前期投入更多时间设计提示词、控制流、状态管理,后期能节省大量调试时间。

其次是“架构意识的提升”,不要只关注模型的调参、提示词的优化,还要关注系统架构,例如状态如何存储、工具如何解耦、错误如何恢复。这些架构层面的问题才是决定Agent能否落地的关键。

第三是“用户体验的创新”,不要局限于“聊天界面”的交互模式,要思考“Agent如何能主动融入用户的工作流”。例如,用户在处理订单时,Agent主动提示“这个客户有退款记录,需要注意”;用户在撰写报告时,Agent主动调用数据工具提供最新的销售数据。这种“主动协作”的体验才是Agent的核心价值。

第四是“跨学科整合”,Agent开发并非纯AI工作,而是需要结合分布式系统、DevOps(DevOps: 一组文化理念、实践和工具,旨在提高软件开发和交付的速度、质量和可靠性)、产品设计等多个领域的知识。例如,分布式系统的“无状态设计”和“容错机制”能够帮助Agent实现高可用;而DevOps中的“CI/CD”(Continuous Integration/Continuous Delivery: 持续集成/持续交付,一种软件开发实践,通过自动化流程实现频繁、小批量的代码集成、测试和部署)和“监控告警”能够帮助Agent实现快速迭代和稳定运行;包括产品设计中的“用户旅程”,也能帮助Agent设计更友好的人机交互。

最后,引用HumanLayer的一句话:“不要重复传统软件开发早期的弯路,而是从一开始就建立良好的工程文化和设计原则。”Agent是AI落地的重要载体,而12-Factor Agents正是帮助我们把Agent做“好”、做“稳”的关键一步。

📌 文中提及的人物和组织

媒体/书籍: best-partners-tv

关键字: ai-agent-development design human software-engineering system