|
如果你正在: 用LangChain、AutoGen快速拼出一个 Agent demo,但一上线就各种报错、卡死,甚至连 bug 都不知道怎么复现; 想让 Agent 真正融入业务流程,但发现它不是忘记上下文,就是“瞎回答”,更别提让业务同事真正依赖它; 或者,老板已经问过你无数次:“这个 AI 项目什么时候能真正上线,稳定跑在生产环境里?”,你只能模棱两可地说“我们还在调模型”。
救星12-Factor Agent: HumanLayer创始人Dexter Horthy提出的企业级复杂Agent设计12原则(12-Factor Agent),就是你的救星。 12-Factor Agents的价值,就是要填平Agent从“能跑起来”到“能用起来”这中间的巨大鸿沟,让AI在公司内部真正创造价值。它不是炫技的概念,而是一套专门指导企业级 Agent 工程化落地的方法论。目前 12-Factor Agents 已在 GitHub 收获 13.8k+ star、近 1000 个 fork,不仅是一个开源项目,更是一套指导 Agent 工程化的“行业共识”。
12-Factor Agent核心理念: 与 LangChain 等框架不同,12-Factor Agents 不是一个工具箱,而是一套方法论。 它的核心创新点是提出“反框架(Anti-Framework)”的理念: 12个工程化原则也都是围绕这一理念设计的。 Horthy认为,在金融、医疗、供应链等行业,透明度比“开发快”更重要。开发者必须清楚: 这就是12-Factor Agents存在的意义:通过一套工程化原则,让 Agent 从“实验室里的原型”进化为“真正能稳定运行的企业级系统”。 学会它,你会收获: Agent 的逻辑和状态都可控,问题能被快速定位、复现和修复,再也不是“黑盒子里抓瞎”; 你能精细化管理上下文和控制流,确保 Agent 每一步动作都有迹可循,真正适配业务需求; 业务同事会真正信任 Agent,把它当成可靠的团队伙伴,而不是偶尔试试的新鲜玩意; 面对老板提问,你能很有底气地回答:“是的,这个系统稳定、可控、可扩展”,而不是模棱两可地说“我们还在调模型”;
最重要的是,你将跨过那条从“能跑起来”到“能用起来”的鸿沟,让 AI 在公司内部真正创造价值。 从今天开始,我会系统地为大家拆解企业级复杂 Agent 设计的 12 个原则。 今天带来12-Factor Agents 系列·第 1 篇:
原则一:NL→Tool Calls,把自然语言变成工具调用指令集合 一、NL→Tool Calls核心理念NL→Tool Calls的核心设计是把人类的“自然语言”准确转换成结构化的“工具调用任务集”,让它在理解问题后能够调用合适的工具集去解决问题,而不是让Agent直接“回答问题”。比如销售总监说: “查询上季度每个门店的营业额,并分析哪些门店是逐月下降的,最终将数据导出给我,同时生成邮件发给运营部。”
虽然看起来只是一个请求,但实际执行需要多步: 调用数据查询工具 → 获取每个门店营业额 调用分析工具 → 找出逐月下降门店 导出分析结果 → 生成 Excel/PDF 通过邮件工具 → 发送给运营部
如果让模型“自由发挥”,很可能出现数据查询日期解析错误、下降门店分析错误、甚至邮件重复发送。而结构化工具调用,就是把这一切变为可控的过程。 二、如何让LLM实现NL→Tool Calls?以销售总监提问为例,我们来详细拆解NL→Tool Calls的落地过程。 销售总监提问: “查询上季度每个门店的营业额,并分析哪些门店是逐月下降的,最终将数据导出给我,同时生成邮件发给运营部。”
在本案例中,大模型的任务目标就是:把销售总监提问的自然语言,转化成“结构化的工具调用JSON指令集合”,即NL→工具调用JSON指令集合。 本案例,LLM输出的工具调用JSON指令集合,参考如下: [{ "id":"task-001", "tool":"query_store_revenue", "params":{ "period":"2023-Q2", "stores":"all", "metrics":["monthly_revenue","transaction_count"], "currency":"CNY" }, "options":{ "timeout_seconds":60, "retries":2, "priority":"high" }, "auth":{ "user_role":"sales_director", "token":"********" }, "metadata":{ "description":"查询上季度每个门店的营业额数据", "created_at":"2025-09-10T17:00:00Z" }},{ "id":"task-002", "tool":"analyze_declining_stores", "params":{ "metric":"monthly_revenue", "trend":"declining", "min_drop_percentage":5, "report_format":"detailed" }, "options":{ "timeout_seconds":45, "retries":1, "priority":"medium" }, "auth":{ "user_role":"data_analyst", "token":"********" }, "metadata":{ "description":"分析哪些门店的营业额逐月下降超过 5%", "created_at":"2025-09-10T17:01:00Z" }},{ "id":"task-003", "tool":"export_report", "params":{ "input_data_source":"analyze_declining_stores_output", "format":"xlsx", "include_charts":true, "include_summary":true, "file_name":"Q2_declining_stores_report.xlsx" }, "options":{ "timeout_seconds":30, "retries":2, "priority":"medium" }, "auth":{ "user_role":"report_generator", "token":"********" }, "metadata":{ "description":"导出分析报告为 Excel 文件", "created_at":"2025-09-10T17:02:00Z" }},{ "id":"task-004", "tool":"send_email", "params":{ "to":["operations@company.com"], "cc":["sales_director@company.com"], "subject":"Q2 门店逐月下降分析报告", "body":"各位,附件是上季度门店营业额逐月下降分析报告,请查收。", "attachments":["Q2_declining_stores_report.xlsx"], "send_as_html":true }, "options":{ "timeout_seconds":20, "retries":3, "priority":"high" }, "auth":{ "user_role":"sales_director", "token":"********" }, "metadata":{ "description":"将分析报告发送给运营部", "created_at":"2025-09-10T17:03:00Z" }}]
简单来说,LLM的目标就是:NL→工具调用JSON指令集合明确了目标,我们再来拆解一下如何让LLM实现这一目标?1、NL→JSON工具调用指令《LLM实现链路图》收到请求(自然语言)。 RAG 检索:返回 TOOL_SCHEMA、PERMISSIONS、DEFAULTS、EMAIL_MAP。 将检索结果以<<RAG_CONTEXT>>注入 Prompt。 调用模型(single shot),返回 JSON。 本地 validator 校验;若通过 → 交给 Executor 执行或审批;若未通过 → run auto-fix prompt or human review。 2、NL→JSON工具调用指令《LLM实现过程拆解》如何让大模型实现“自然语言—>工具调用JSON指令集合”呢?我们必须要完成以下5个工作:设计清晰的工具接口,统一结构化调用格式; 明确调用模型LLM用来完成哪些任务; 构建支持LLM完成任务所需RAG知识库; 设计支撑LLM完成任务所需Prompt提示词; JSON输出后,必不可少的可用性校验器; 工作1:设计清晰的“工具接口”(统一结构化调用格式)为什么要先定义工具接口?工具接口示例(结合案例)假设企业系统内有以下工具: 结构化调用格式统一为: {"id":"task-001","tool":"query_store_revenue","params":{"period":"2025-Q2","stores":"all","metrics":"monthly_revenue"},"options":{"timeout_seconds":30,"retries":2,"priority":"high"},"auth":{"user_role":"sales_director","token":"<REDACTED>"},"metadata":{ "description":"查询2025-Q2每个门店的月度营业额", "created_at":"2025-09-11T09:00:00Z"}}
模型不该做什么?模型应该做什么?应该完成以下5个任务:任务1:解析自然语言需求将输入语句: “查询上季度每个门店的营业额,并分析哪些门店是逐月下降的,最终将数据导出给我,同时生成邮件发给运营部。”
拆解出需求要素: 时间范围:上季度 对象:每个门店 指标:营业额(月度维度) 任务链:
查询数据 做趋势分析 导出报告 邮件发送
任务2:映射到工具能力假设企业 Agent 系统内有以下工具: 那么刚才的需求就需要映射到 4 个工具调用。 任务3:设计任务链路要保证上下文依赖正确: 数据查询→ 输出原始门店营收数据 数据分析→ 输入上一步结果,输出下降门店清单 报告导出→ 输入分析结果,输出 Excel 文件 邮件发送→ 输入 Excel 文件,发送到运营部
这里其实就是自然语言的顺序动作→数据流的 DAG(有向图)。 任务4:补齐执行参数自然语言里很多信息是模糊的,需要补全: 任务5:生成 JSON Tool Calls将自然语言转为结构化 JSON(就是之前我们输出的完整示例)。 每个任务有: id(task 编号,保证顺序和追踪)
tool(工具名)
params(核心参数)
options(执行参数,如超时、重试)
auth(角色身份,保证权限正确)
metadata(描述、时间戳,便于日志审计)
模型之所以能正确补全参数和映射工具,前提是它“知道”企业里的规则和默认值。 这些信息不能靠大模型记忆,而要通过RAG(检索增强生成)动态提供。 RAG 知识库要包含哪些?
必备(高优先级) 推荐(增强上下文) 格式与元数据 每个文档记录doc_id,type(schema/policy/mapping/example),last_updated。 将 schema 文档以小 chunk 存储(每 chunk <= 500 tokens),并带上tool、version、required_fieldsmetadata 以便精确检索。
RAG 检索策略(工程实现建议)Vector DB(e.g., Pinecone/FAISS)保存文档 embeddings;检索时 top_k=3。 精确匹配字段(tool 名、schema version)优先走 metadata index(keyword lookup),再做 semantic retrieval。 文档 chunk 建议 200–400 tokens(使检索上下文更精确)。 定期(例如每周)刷新权限表与 schema 版本。每次 schema 变更都要触发模型微调/提示更新或更新<<RAG_CONTEXT>>的模板。 工作4:构建支持LLM完成任务的“Prompt提示词”Prompt 是“规范模型行为”的关键杠杆。 我们要让模型在一次请求里完成:解析 → 映射 → 补全 → 输出 JSON。 Prompt 设计要点:强制输出 JSON:要求“仅输出 JSON,不加解释”。 注入 RAG 上下文:把工具 schema、权限、默认值拼进去。 明确任务拆解规则:要求拆解为最少的顺序任务,id 依次递增。 参数不确定时用 null,或使用默认值。 增加 few-shot 示例,提升稳定性。
案例 对应的 Prompt 示例:SYSTEM:你是企业级Agent的JSON生成器。你的输出必须**严格**是一个JSON数组(仅JSON,不允许任何额外文本/解释)。每个数组元素表示一个“工具调用任务”,必须遵守下列结构:{"id":"task-XXX",//按顺序task-001,task-002..."tool":"<tool_name>","params":{...},//详细参数,参照schema"options":{"timeout_seconds":int,"retries":int,"priority":"low|medium|high"},"auth":{"user_role":"<role>","token":"<REDACTED|OP_TOKEN>"},"metadata":{"description":"...","created_at":"ISO8601"}}严格要求:-输出只是一段合法JSON(数组),utf-8编码。-id必须唯一且按顺序递增。-时间用ISO8601UTC(例如2025-09-10T17:00:00Z)。-若字段不确定,请使用null或使用从<<RAG_CONTEXT>>里定义的默认值(明确标注来源)。-attachments/input_data_source必须明确引用前一步的id或file名称。<<RAG_CONTEXT>>#在这里插入检索到的结构化关键信息,例如:#-TOOL_SCHEMA:query_store_revenue{required:[period,stores],optional:[metrics,currency],defaults:{stores:"all"}}#-PERMISSIONS:role'sales_director'allowed_tools:[query_store_revenue,send_email]#-DEFAULTS:default_report_format:xlsx,default_ops_email perations@company.com#-BUSINESS_DEF:"上季度"->2023-Q2(ifcurrentdateis2025-09-10,computeautomatically)#-ANALYSIS_DEF:"逐月下降"->每个月比前月下降,且总下降幅度>=min_drop_percentageUSER:将下面的自然语言请求解析并输出严格符合上面结构的JSON数组(只输出JSON):"查询上季度每个门店的营业额,并分析哪些门店是逐月下降的,最终将数据导出给我,同时生成邮件发给运营部。"规则:1)将任务拆成最少的线性步骤(但保持正确的依赖关系)。2)每个分析任务应指明输入来源(例如"input_data_source":"task-001.output")。3)权限:根据<<RAG_CONTEXT>>的PERMISSIONS指定合适的user_role(若请求人是销售总监,则使用'sales_director')。4)若某字段在<<RAG_CONTEXT>>明确了默认值,请使用该默认值;若没有,请填null。5)包含metadata.description,简短描述该任务做什么。6)最终JSON必须可由schemavalidator校验(后端将执行严格校验,若校验失败会返回错误码进行二次修正)。小结:LLM实现NL→工具调用JSON指令序列 工具接口:先把调用格式定死,避免混乱。 任务边界:让 LLM 只做解析和 JSON 生成,不直接执行。 RAG 知识库:提供 schema、权限、默认值、规则,保障生成正确性。 Prompt 设计:明确输出格式、调用规则、示例,提升稳定性。 可用性校验器:对输出json进行校验,确保能够调用对应工具API。
📌 最终,企业就能实现这样的效果:销售总监一句话,就能触发查询 + 分析 + 导出 + 发邮件的完整闭环,而且可控、可追溯、可监控。 |