一、引言:AI Agent 需要自己的工程原则
12-Factor App 是 Heroku 团队在 2011 年提出的一套 Web 应用开发最佳实践,深刻影响了现代 云原生应用 的构建方式。如今,随着 AI Agent 从实验性原型走向 生产级部署,我们面临同样的挑战:如何构建 可维护、可扩展、可观测 的 Agent 系统?
AI Agent 与传统 Web 应用 有本质区别。Agent 不是简单的 请求-响应 模型,而是具有 自主决策能力、工具调用能力 和 持续状态 的智能体。传统的 MVC 架构 和 RESTful API 设计 无法直接套用到 Agent 系统中。
2026 年 5 月,社区涌现出多个 Agent 框架(Mastra AI 23,784 星、ElizaOS 18,346 星、humanlayer/12-factor-agents 19,754 星),标志着 AI Agent 工程化进入了 标准化阶段。这些框架背后的设计理念,与 12-Factor App 的精神高度一致:通过约定而非配置来降低复杂度。
为什么需要 12-Factor Agents? 因为当前的 Agent 开发存在严重问题:硬编码 API Key、会话状态无法持久化、工具调用缺乏审计、多 Agent 协作没有明确边界。这些问题在传统软件工程中都已有成熟解决方案,但 Agent 领域尚未形成共识。
本文将基于 12-Factor App 的核心思想,结合 AI Agent 的独特需求,提出适配 Agent 系统的 12 条工程原则。这些原则不是理论推导,而是来自 生产级 Agent 系统 的实战经验总结。
如果你正在构建面向生产环境的 AI Agent,建议在项目启动阶段就将这些原则纳入架构设计,而非事后补救。重构 Agent 的状态管理远比重构 Web 应用的数据库连接更困难。
不要盲目照搬 12-Factor App 的所有条目。Agent 系统有独特的需求(如 prompt 版本管理、工具调用的幂等性),需要针对性调整。本文的原则是经过 AI 场景适配的版本。
二、Factor 1:单一代码库(Codebase)
每个 AI Agent 应该维护在 一个版本控制仓库 中。这与 12-Factor App 的第一条原则一致,但在 Agent 语境下有特殊含义。
Agent 的代码库 包含三个核心部分:Agent 逻辑代码(决策树、状态机)、Prompt 模板(系统提示词、Few-shot 示例)和 工具定义(Function Calling schema、API 封装)。这三者必须 同步版本化,因为它们是 强耦合 的。
错误实践 是将 Agent 代码、Prompt 和工具定义分散存储:代码在 Git 仓库,Prompt 模板在 Notion 文档,工具定义在飞书表格。正确做法是全部在同一个 Git 仓库中版本化管理。
多 Agent 协作 场景下,每个 Agent 应该有 独立的仓库,通过 定义清晰的接口契约 进行通信。这类似于 微服务架构 中的服务边界划分。每个 Agent 的接口应该 版本化,当接口变更时,通过 版本号 标识兼容性(如 v1、v2)。
版本兼容性 是关键问题。当 Agent A 的 工具调用接口 升级时,Agent B 必须能够 优雅降级。这要求在 接口设计阶段 就考虑 向后兼容性。新增字段应该是 可选的,删除字段应该有 过渡期,接口变更应该有 弃用通知。
# 推荐的 Agent 项目目录结构
my-agent/
src/agents/ # Agent 逻辑代码
src/prompts/ # Prompt 模板
src/tools/ # 工具定义
src/config/ # 配置文件
tests/ # 测试使用 monorepo 管理多 Agent 项目时,确保每个 Agent 有独立的目录和 package.json,通过 workspace 协议共享公共依赖。这样既能独立部署,又能共享工具库。
避免将所有 Agent 的 Prompt 放在同一个文件中。当项目规模扩大时,Prompt 文件会变得难以维护。应该按 Agent 和功能模块拆分。
三、Factor 2:显式声明依赖(Dependencies)
AI Agent 的依赖管理 比传统应用更复杂,因为 Agent 不仅依赖 代码库,还依赖 模型版本、工具 API 和 外部数据源。
第一层依赖:代码依赖。这部分与传统 Node.js 项目相同,通过 package.json 和 lock 文件 管理。
第二层依赖:模型依赖。Agent 的行为高度依赖 底层模型版本。同一个 Prompt 在 gpt-4o 和 Claude 3.5 上可能产生 完全不同的输出。必须 显式声明 所用模型的 版本号 和 提供商。
不要使用模糊的模型标识,因为上游更新后会改变行为。应该精确到具体版本,如 gpt-4o-2024-11-20。
第三层依赖:工具 API 依赖。Agent 调用的 外部工具(搜索 API、数据库、浏览器)是 关键依赖链。每个工具必须声明 API 版本、速率限制(如 100 次/分钟)和 降级策略(如超时后使用缓存)。
依赖锁定文件 应该包含所有三层依赖的 精确版本。推荐使用 dotenv 加 .env.lock 记录模型版本和 API 端点。
const MODEL_CONFIG = {
provider: 'openai',
model: 'gpt-4o-2024-11-20',
temperature: 0.1,
maxTokens: 4096,
} as const;为 Agent 项目创建 dependency-audit 脚本,定期检查模型版本是否过期、工具 API 是否有 breaking change。AI 领域的 API 变更频率远高于传统软件。
不要在代码中硬编码 API Key 或模型配置。使用环境变量注入,确保同一套代码可以在不同环境(开发、测试、生产)中运行。
四、Factor 3:配置与代码分离(Config)
配置分离 是 12-Factor App 的核心原则之一,在 AI Agent 中有 更丰富的含义。Agent 的配置分为四类:
第一类:环境变量。包括 API Key、数据库连接串、服务地址 等敏感信息。
第二类:运行时配置。包括 模型参数(temperature、topP)、超时设置、重试策略 等。
第三类:Prompt 参数。Prompt 中的 可变部分(如语言、角色、输出格式)应该 参数化,而非硬编码。Prompt 是代码的一部分,应该和代码一起版本化。
第四类:行为开关(Feature Flags)。控制 Agent 的 实验性功能,如是否启用自我反思、工具链模式、最大调用深度等。
配置验证 是重要环节。Agent 启动时应 校验所有必需配置,缺失时 立即报错退出,而非在运行时抛出难以调试的错误。
export const runtimeConfig = {
llm: { temperature: 0.1, topP: 0.9, maxRetries: 3, timeoutMs: 30000 },
tools: { search: { maxResults: 10 } },
} as const;使用 Zod 或 Joi 对配置文件进行运行时校验。Agent 的配置错误通常在运行时才暴露,导致难以复现。启动时校验可以在部署阶段就拦截配置错误。
不要把 Prompt 模板当作配置项存在 .env 文件中。Prompt 是代码的一部分,应该和代码一起版本化。只有 Prompt 中的变量(如角色名、语言)才通过配置注入。
五、Factor 4:后端服务即绑定资源(Backing Services)
在 12-Factor App 中,后端服务(数据库、缓存、消息队列)被视为 绑定资源。Agent 系统中,这个概念扩展为 AI 绑定资源:
AI 绑定资源包括:LLM API(OpenAI、Anthropic、Google 等)、向量数据库(Pinecone、Weaviate、Milvus)、工具 API(搜索、浏览器、代码执行)、消息队列(任务调度、Agent 间通信)。
关键原则:Agent 代码不应该对 具体提供商 做出假设。通过 抽象层 实现 可替换性:定义 LLMProvider 接口,不同提供商各自实现该接口,Agent 代码只依赖接口。
连接管理 方面,Agent 应该使用 连接池 和 重试机制。对于 LLM 调用,设置最大连接数、重试次数(如 3 次)、指数退避策略(1 秒、2 秒、4 秒、8 秒)和重试状态码(429、500、503)。
本地开发 和 生产环境 应该使用 相同类型的服务,只是配置不同。例如,开发环境使用 本地 Ollama,生产环境使用 OpenAI API,但通过同一个 LLMProvider 接口 调用。这种设计确保了代码在不同环境中的 行为一致性。
interface LLMProvider {
generate(prompt: string): Promise<string>;
embed(text: string): Promise<number[]>;
}
class OpenAIProvider implements LLMProvider { }
class AnthropicProvider implements LLMProvider { }
const agent = new Agent({ llm: new OpenAIProvider() });为每个后端服务实现一个 mock 版本,用于单元测试。Agent 的测试比传统应用更依赖 mock,因为真实 LLM 调用既慢又贵。
不要将 Agent 与特定 LLM 提供商深度耦合。如果代码中到处是 openai.chat.completions.create() 调用,更换提供商将变成噩梦。使用抽象层隔离。
六、Factor 5-6:构建/发布/运行分离与无状态进程
Factor 5:严格分离构建、发布、运行
构建阶段(Build):将代码仓库转换为 可执行包。在 Agent 项目中,这包括编译 TypeScript 为 JavaScript、安装依赖、构建 Prompt 模板(合并模板与参数)、生成工具注册表。
发布阶段(Release):将构建产物与 运行时配置 结合,创建 发布版本。
运行阶段(Run):在 执行环境 中启动 Agent。运行命令应该是只读文件系统的。
三个阶段必须严格分离:构建阶段不应该访问任何运行时配置;发布阶段不应该执行任何代码;运行阶段不应该修改任何文件。不可变发布 是核心原则。
Factor 6:无状态进程
无状态进程 是 12-Factor App 的标志性原则:每个请求都是 独立的,不依赖 进程内存中的状态。这对 AI Agent 提出了 独特挑战。
Agent 的状态 包括 对话历史、上下文窗口、工具调用记录。如果将这些状态存在 进程内存 中,一旦进程重启,所有状态都会丢失。
正确的做法 是将状态外置到持久化存储。使用 Redis 存储短期会话状态,PostgreSQL 存储长期对话历史。Agent 进程本身不持有任何持久状态。
上下文窗口的管理 是 Agent 特有的挑战。由于 LLM 的 上下文窗口有限(如 128K tokens),Agent 必须在每次调用时 重建上下文:从数据库加载最近的对话历史和长期摘要,合并系统提示词。
水平扩展 的前提是无状态。只有当 Agent 进程无状态时,才能在 多个实例间负载均衡。
使用 Redis 存储短期会话状态,PostgreSQL 存储长期对话历史。Redis 的 TTL 功能可以自动清理过期会话,避免存储无限增长。
不要把 LLM 的上下文窗口当作状态存储。每次调用 LLM 时都应该从持久化存储重建上下文。如果依赖进程内存中的对话历史,进程重启后所有对话都会丢失。
七、Factor 7-9:端口绑定、并发模型、可快速启停
Factor 7:端口绑定服务暴露
Agent 通过 端口绑定 暴露服务。端口配置应该通过 PORT 环境变量 注入,而非硬编码。在容器化环境中,端口是动态分配的。
Agent 必须提供健康检查端点。健康检查不仅要检查数据库连接,还要检查 LLM API 的可达性。一个无法调用 LLM 的 Agent 本质上是不可用的。
Factor 8:并发模型
Agent 的并发模型涉及 CPU 密集(LLM 推理)、I/O 密集(工具调用)和 内存密集(上下文管理)三种工作负载。
进程级并发:通过多个 Agent 实例处理独立请求。任务级并发:单个 Agent 实例内部并行处理多个子任务。多 Agent 协作:多个 Agent 协同工作,通过规划器分配和编排。
并发控制 的关键是 资源限制。使用信号量控制 LLM API 的并发调用数。大多数 LLM 提供商有每分钟请求数(RPM)限制,超出会被限流。
背压机制:当 Agent 的请求队列超过处理能力时,应该拒绝新请求而非无限排队。返回 503 状态码和重试建议。
Factor 9:可快速启动和优雅终止
Agent 进程应该能够 快速启动(秒级)和 优雅终止(不丢失状态)。
快速启动 的关键是 延迟初始化(Lazy Initialization):LLM 客户端、数据库连接等资源在首次使用时才初始化。
优雅终止 需要处理 SIGTERM 信号:停止接收新请求、等待进行中的请求完成(设置超时,如 30 秒)、保存未持久化的状态、关闭所有连接。
Agent 特有的终止挑战:LLM 调用可能持续 数十秒。优雅终止必须 等待进行中的 LLM 调用完成,或者通过 AbortController 安全取消。
设置合理的优雅关闭超时时间(30-60 秒)。太短会导致 LLM 调用被强制中断,太长会导致部署时等待过久。
不要在 Agent 中使用无限制的 Promise.all。如果某个工具调用挂起,所有后续调用都会被阻塞。始终设置超时时间和并发上限。
八、Factor 10-12:环境等价、日志事件流、管理进程
Factor 10:开发环境与生产环境等价
开发、测试、生产环境的一致性 是减少部署问题的关键。Agent 项目面临三大挑战:
模型差异:开发环境使用小型本地模型(如 Ollama),生产环境使用大型云端模型(如 GPT-4o)。解决方案 是抽象模型层加配置驱动,通过环境变量切换模型。
工具 API 差异:开发环境使用 mock 工具,生产环境使用真实 API。解决方案 是统一接口加不同实现,根据环境变量注入不同实现。
数据差异:生产环境有真实用户数据,开发环境只有测试数据。解决方案 是数据脱敏加种子数据。
CI/CD 管道 应该在 与生产相同的环境 中运行测试,至少使用真实的小型 LLM 调用验证 Agent 的基本功能。
Factor 11:日志作为事件流
日志 在 Agent 系统中比在传统应用中 更重要,因为 Agent 的行为 更难预测。传统 Web 应用的行为是确定性的,但 Agent 的行为具有 非确定性。
Agent 需要记录三类事件:LLM 交互日志(Prompt 输入、模型输出、token 数、延迟、成本)、工具调用日志(参数、结果、耗时、是否成功、重试次数)、决策日志(决策内容、推理依据、替代方案、置信度)。
日志输出格式 应该是 结构化的 JSON。为每个 Agent 会话生成唯一的 traceId,贯穿所有日志条目。
日志级别 设计:DEBUG(详细 Prompt 和响应,开发环境)、INFO(工具调用、决策点)、WARN(重试、降级)、ERROR(工具调用失败、LLM 超时)。
Factor 12:管理进程作为一次性任务
管理任务 应该作为 一次性进程 运行,而非在 应用进程中 执行。包括:Prompt 优化(分析 LLM 交互日志)、工具性能分析(统计调用频率、成功率)、会话清理(清理过期会话)、成本分析(统计 LLM 调用成本)。
调度方式 推荐使用 cron 表达式。例如,每天凌晨 2 点清理过期会话,每周一早上 6 点生成成本周报。
将管理任务打包为独立的 npm script(如 npm run analyze、npm run cleanup),这样团队成员可以手动运行,CI/CD 也可以自动调度。
不要在日志中记录敏感信息(API Key、用户隐私数据)。LLM 的 Prompt 可能包含用户的个人信息,日志记录前应该进行脱敏处理。
九、Agent 系统架构全景图
一个完整的生产级 Agent 系统包含 多个层次,每个层次对应不同的 12-Factor 原则。理解整体架构有助于在设计和审查时 全面覆盖 所有原则。
架构分层:
接入层(端口绑定):HTTP API、WebSocket、消息队列订阅。负责接收请求、负载均衡、健康检查。
处理层(无状态进程 + 并发模型):Agent 核心逻辑、决策引擎、工具调度器。无状态设计确保水平扩展能力。
依赖层(显式依赖 + 后端服务绑定):LLM Provider 抽象、向量数据库、外部工具 API。通过抽象层实现可替换性。
配置层(配置分离 + 环境等价):环境变量、运行时配置、Prompt 参数、Feature Flags。配置驱动确保环境一致性。
持久层(无状态进程):Redis(短期会话)、PostgreSQL(长期对话历史)、日志存储(ELK/Datadog)。
管理层(管理进程):一次性脚本,负责 Prompt 优化、性能分析、会话清理、成本报告。
在架构设计评审时,按照这六个层次逐一检查,确保每个层次都遵循了对应的 12-Factor 原则。
不要将接入层和处理层耦合在一起。如果 Agent 逻辑直接绑定到 HTTP 框架,后续添加 WebSocket 或消息队列支持时将需要大量重构。
十、实战:12-Factor Agents 检查清单
在开始一个新的 AI Agent 项目时,使用以下检查清单确保 12 条原则 都被遵循:
启动阶段检查清单:所有代码、Prompt、工具定义在同一仓库;package.json 加模型版本锁定;环境变量注入加 Prompt 参数化;LLM 抽象层加连接池;Docker 多阶段构建加不可变发布;状态外置数据库加上下文重建;PORT 环境变量加健康检查端点;并发限制器加背压机制;延迟初始化加 SIGTERM 处理;开发测试生产配置矩阵加 CI smoke test;结构化 JSON 加 traceId;独立一次性脚本加 cron 调度。
代码审查检查清单:代码中是否有硬编码的 API Key?模型版本是否精确到具体日期?Prompt 模板是否与代码一起版本化?状态是否存储在外部数据库而非进程内存?是否有健康检查端点?工具调用是否有超时和重试机制?日志是否包含敏感信息?是否有 SIGTERM 处理函数?管理任务是否独立于应用进程?
持续改进检查清单(每月执行一次):审查 LLM 交互日志,优化低质量 Prompt;分析工具调用性能,替换慢速工具;检查成本报告,优化 token 使用;更新模型版本(评估新模型的效果提升);审查安全策略(API Key 轮换、权限最小化)。
| # | 原则 | 检查项 | 优先级 |
|---|---|---|---|
1 | 单一代码库 | 代码+Prompt+工具在同一仓库 | P0 |
2 | 显式依赖 | 模型版本锁定+API版本声明 | P0 |
3 | 配置分离 | 环境变量注入+Prompt参数化 | P0 |
4 | 后端服务绑定 | LLM抽象层+连接池 | P1 |
5 | 构建/发布/运行 | Docker多阶段构建 | P1 |
6 | 无状态进程 | 状态外置+上下文重建 | P0 |
7 | 端口绑定 | PORT环境变量+健康检查 | P1 |
8 | 并发模型 | 并发限制+背压+超时 | P0 |
9 | 可快速启停 | 延迟初始化+SIGTERM处理 | P1 |
10 | 环境等价 | 配置矩阵+CI smoke test | P2 |
11 | 日志事件流 | 结构化JSON+traceId | P0 |
12 | 管理进程 | 独立脚本+cron调度 | P2 |
将检查清单集成到 CI/CD 管道中,作为 PR 审查的自动检查项。例如,代码扫描工具可以检测硬编码的 API Key、不精确的模型版本标识等问题。
检查清单不是一次性的。随着 Agent 系统的演进,检查清单也应该更新。建议每月回顾一次,根据实际情况增减检查项。
十一、12-Factor Agents 原则总结
本文系统讲解了 12-Factor Agents 的 12 条工程原则,从代码库管理到管理进程,覆盖了 AI Agent 开发的 全流程。这些原则是 生产级 Agent 系统 的基石,也是 团队协作 和 代码审查 的标准。
核心要点回顾:单一代码库确保所有资产同步版本化;显式依赖声明覆盖代码、模型和工具三个层级;配置分离让 Agent 在不同环境中一致运行;后端服务抽象实现可替换性;构建发布运行三阶段严格分离保证可复现性;无状态进程是水平扩展的前提;端口绑定让 Agent 自包含;并发模型确保资源受控;快速启停支持优雅部署;环境等价减少部署问题;结构化日志提供可观测性;独立管理进程避免干扰主服务。
不要试图一次性将所有 12 条原则应用到新项目中。建议从最关键的 5 条开始(配置分离、无状态进程、日志事件流、显式依赖、可快速启停),逐步完善。
将这 12 条原则打印出来贴在工位上,每次代码审查时对照检查。习惯养成后,这些原则会成为你的直觉。
原则不是教条。如果你的场景确实需要打破某条原则(例如边缘部署场景无法做到完全无状态),请记录原因并评估风险,而非盲目遵守。
十二、扩展阅读
12-Factor App 原始文档:12factor.net — Heroku 团队提出的原始 12 条原则,是理解本文的基础。
12-Factor Agents 社区:humanlayer/12-factor-agents(19,754 星)— 将 12-Factor 原则适配到 AI Agent 领域的开源项目。
AI Agent 框架对比:
- Mastra AI(23,784 星):Gatsby 团队打造的企业级 Agent 框架,内置 12-Factor 原则支持
- ElizaOS(18,346 星):面向所有人的自主 Agent 框架,强调可访问性
- LangGraph:LangChain 生态的 Agent 编排层,适合复杂工作流
相关知识库文章:
- [aieng-001] 模型训练基础设施
- [aieng-008] AI 系统架构设计
- [agent-001] AI Agent 入门:从概念到实现
- [agent-002] Multi-Agent 系统设计与协作
推荐阅读顺序:先阅读 12-Factor App 原始文档,理解核心思想;再阅读 agent-001 了解 Agent 基本概念;最后阅读本文,将 12-Factor 原则应用到 Agent 场景。
推荐阅读 12-Factor Agents 社区仓库的源码,了解他们如何将每条原则转化为具体的代码实践。理论加源码结合是最高效的学习方式。
不要试图一次性将所有 12 条原则应用到新项目中。建议从最关键的 5 条开始(配置分离、无状态进程、日志事件流、显式依赖、可快速启停),逐步完善。