从理论到实践:我用12要素方法重构了AI 智能体,效果惊人! 原创 精华

发布于 2025-9-1 09:07
浏览
0收藏

本文为 12-Factor Agents[1] 内容的翻译整理,并结合个人实践补充了部分笔记和理解。内容涵盖 LLM 代理开发的 12 项核心原则。

“12 要素代理” 框架受著名的 12 要素应用方法论启发,为构建可靠、可维护且可扩展的 LLM 应用提供了全面的指南。这些原则解决了将非确定性 AI 组件集成到生产系统的独特挑战,强调明确的控制、托管上下文以及无缝的人机协作。

1. 自然语言工具调用

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

这是Agent架构中最基础但也最关键的转换层。当用户说"帮我创建一个5000元的付款链接给琳达,用于赞助三月AI技术沙龙"时,系统需要将这句话精确转换为结构化的API调用。以下是一个真实的转换示例:

输入的自然语言:

"帮我创建一个5000元的付款链接给琳达,用于赞助三月AI技术沙龙"

转换后的结构化工具调用:

{
  "function":{
    "name":"create_payment_link",
    "parameters":{
      "amount":5000,
      "customer":"cust_128934ddasf9",
      "product":"prod_8675309",
      "price":"prc_09874329fds",
      "quantity":1,
      "memo":"这是三月AI技术沙龙的赞助付款链接"
    }
}
}

随后,确定性代码可以获取有效载荷并进行处理。

# LLM 接收自然语言并返回结构化对象
nextStep = await llm.determineNextStep(
  """
  create a payment link for $750 to Jeff 
  for sponsoring the february AI tinkerers meetup
  """
  )

# 根据结构化输出的 function 字段进行处理
ifnextStep.function == 'create_payment_link':
    stripe.paymentlinks.create(nextStep.parameters)
    return  # 或者你想要的其他处理,见下文
elifnextStep.function == 'something_else':
    # ... 其他情况
    pass
else:  # themodeldidn'tcallatoolweknowabout
    # 模型未调用已知工具,执行其他操作
    pass

必须深刻理解这一转换过程的每个环节:从自然语言解析到实体识别,再到参数映射和 API 构造。切勿将其视为黑盒,因为任何细节的偏差都可能导致用户意图被误解,进而影响系统的可靠性和用户体验。

理解笔记

构建 Agent 过程中,工具方面,需要做的内容是:

  • 工具(Tool)说明文档要结构化、清晰,确保 LLM 能准确理解每个工具的用途、参数和限制,减少歧义。
  • 需要为 LLM 返回的结构化调用结果设计健壮的处理流程,包括成功、失败、异常等分支,保证系统的可预测性和安全性。
  • 工具接口和文档应与代码保持同步,避免文档不同步,导致 LLM 理解偏差。

2. 拥有自己的提示词

提示词是 LLM 交互的核心,决定了交互的行为和输出。本原则强调应将提示词视为关键、受版本控制的代码资产,而非一次性输入。提示应纳入代码库统一管理,便于系统性测试、部署和回滚。掌控自己的提示词有助于保证一致性、可复现性,并能以可控方式持续优化代理行为。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

应将你的提示词视为一等公民的代码:

function DetermineNextStep(thread: string) -> DoneForNow | ListGitTags | DeployBackend | DeployFrontend | RequestMoreInformation {
  prompt #"
    {{ _.role("system") }}
    
    你是一个负责前端和后端系统部署的高效助手。
    你会遵循最佳实践,确保部署安全且成功。
    并遵循正确的部署流程。
    
    在部署任何系统前,你应检查:
    部署环境(测试/生产)
    部署的正确标签/版本
    当前系统状态
    
    你可以使用 deploy_backend、deploy_frontend、check_deployment_status 等工具
    管理部署。对于敏感部署,使用 request_approval 获取人工确认。
    human verification.
    
    始终先思考下一步该做什么,比如:
    检查当前部署状态
    校验部署标签是否存在
    如有需要,申请人工批准
    先部署到测试环境再到生产环境
    监控部署进度
    
    {{ _.role("user") }}

    {{ thread }}
    
    下一步应该做什么?
  "#
}

拥有自己的提示词的主要好处:

  • 完全控制:可精确编写代理所需指令,无需依赖黑盒抽象
  • 测试和评估:可像测试其他代码一样测试和评估提示
  • 快速迭代:可根据实际表现快速修改提示
  • 透明度:清楚了解代理正在执行的指令
  • 角色技巧:可利用支持非标准用户/助手角色的 API

请记住:提示词是你的应用逻辑与 LLM 之间的主要接口。

完全掌控提示词,为生产级代理提供了必要的灵活性和提示控制。

我无法断言什么是最优提示,但你一定需要灵活性以便不断尝试。

理解笔记

构建 Agent 过程中,提示词需要自主可控。提示(Prompt)应作为代码资产进行版本管理和测试,确保每次变更都可追溯和回滚。

  • 推荐采用结构化、可维护的提示工程工具,提升提示的可读性和可复用性。
  • 保持提示的灵活性和可控性,有助于快速迭代和适应业务需求变化。

3. 拥有自己的上下文窗口

上下文窗口是 LLM 对过往交互的有限记忆。本原则主张主动且有策略地管理上下文,确保只保留与当前任务直接相关的信息,及时剔除无关内容,防止"上下文漂移"或信息冗余。高效的上下文管理不仅能提升 LLM 的性能,还能减少 token 消耗,让模型聚焦于关键细节。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

无需总是采用标准消息格式向 LLM 传递上下文。

标准消息格式 vs 自定义格式对比:

标准消息格式:

[
  {
    "role":"system",
    "content":"You are a helpful assistant..."
  },
  {
    "role":"user",
    "content":"Can you deploy the backend?"
  },
  {
    "role":"assistant",
    "content":null,
    "tool_calls": [
      {
        "id":"1",
        "name":"list_git_tags",
        "arguments":"{}"
      }
    ]
  },
  {
    "role":"tool",
    "name":"list_git_tags",
    "content":"{\"tags\": [{\"name\": \"v1.2.3\", \"commit\": \"abc123\", \"date\": \"2024-03-15T10:00:00Z\"}, {\"name\": \"v1.2.2\", \"commit\": \"def456\", \"date\": \"2024-03-14T15:30:00Z\"}, {\"name\": \"v1.2.1\", \"commit\": \"abe033d\", \"date\": \"2024-03-13T09:15:00Z\"}]}",
    "tool_call_id":"1"
  }
]

优化的自定义格式(更高效):

[
  {
    "role":"system",
    "content":"You are a helpful assistant..."
  },
  {
    "role":"user",
    "content":|
            Here's everything that happened so far:

        <slack_message>
            From:@alex
            Channel:#deployments
            Text:Canyoudeploythebackend?
        </slack_message>

        <list_git_tags>
            intent:"list_git_tags"
        </list_git_tags>

        <list_git_tags_result>
            tags:
              -name:"v1.2.3"
                commit:"abc123"
                date:"2024-03-15T10:00:00Z"
              -name:"v1.2.2"
                commit:"def456"
                date:"2024-03-14T15:30:00Z"
              -name:"v1.2.1"
                commit:"ghi789"
                date:"2024-03-13T09:15:00Z"
        </list_git_tags_result>

        what'sthenextstep?
    }
]

实现上下文构建的Python代码:

class Thread:
  events: List[Event]

classEvent:
type: Literal["list_git_tags", "deploy_backend", "deploy_frontend", 
                "request_more_information", "done_for_now", 
                "list_git_tags_result", "deploy_backend_result", 
                "deploy_frontend_result", "request_more_information_result", 
                "done_for_now_result", "error"]
  data: Union[ListGitTags, DeployBackend, DeployFrontend, RequestMoreInformation,  
              ListGitTagsResult, DeployBackendResult, DeployFrontendResult, 
              RequestMoreInformationResult, str]

defevent_to_prompt(event: Event) -> str:
    data = event.data ifisinstance(event.data, str) \
           else stringifyToYaml(event.data)
    returnf"<{event.type}>\n{data}\n</{event.type}>"

defthread_to_prompt(thread: Thread) -> str:
    return'\n\n'.join(event_to_prompt(event) for event in thread.events)

不同阶段的上下文窗口示例:

初始钉钉群请求:

<dingtalk_message>
    From: @小张
    Channel: #部署群
    Text: 可以帮我部署最新的后端到生产环境吗?
</dingtalk_message>

错误处理和恢复后:

<slack_message>
    From: @alex
    Channel: #deployments
    Text: Can you deploy the latest backend to production?
</slack_message>

<deploy_backend>
    intent: "deploy_backend"
    tag: "v1.2.3"
    environment: "production"
</deploy_backend>

<e>
    error running deploy_backend: Failed to connect to deployment service
</e>

<request_more_information>
    intent: "request_more_information_from_human"
    question: "I had trouble connecting to the deployment service, can you provide more details and/or check on the status of the service?"
</request_more_information>

<human_response>
    data:
      response: "I'm not sure what's going on, can you check on the status of the latest workflow?"
</human_response>

关键好处包括:

  • 信息密度:以最大化LLM理解的方式构建信息
  • 错误处理:以帮助LLM恢复的格式包含错误信息
  • 安全性:控制传递给LLM的信息,过滤敏感数据
  • 灵活性:随着您了解最佳实践而调整格式
  • Token效率:优化上下文格式以提高token效率和LLM理解

在任何时刻,你给代理 LLM 的输入都是"这是目前为止发生的事情,下一步是什么?"

一切都关乎上下文工程。LLM 是无状态函数,只负责将输入转化为输出。要获得最佳输出,必须为其提供最优输入。

营造良好上下文环境包括:

  • 明确的提示和指令
  • 检索到的文档或外部数据(如 RAG)
  • 过往状态、工具调用、结果或其他历史
  • 相关但独立的历史/对话信息(记忆)
  • 输出结构化数据类型的说明

理解笔记

构建 Agent 过程中,为了使 LLM 拥有更好的效果,从提示词工程向情景工程转换,利用工程方法和系统思维,提供 LLM 所需的:

  • 当前情况:当前背景、当前进展;
  • 目标任务:分解目标、最终目标;
  • 已有工具、手段;
  • 已有信息:历史资料、过往记忆、过往案例;

上下文管理不仅仅是拼接历史消息,更要有选择性地组织和压缩信息,确保 LLM 只接收到与当前任务最相关的内容。

建议建立上下文构建的标准流程和模板,便于团队协作和复用。

4. 工具只是结构化的输出

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

​工具调用被过度神秘化了,其实质就是LLM输出的结构化JSON数据,您的代码解析这些数据并执行相应操作。让我们看一个具体的例子,假设您有两个工具​​CreateIssue​​​和​​SearchIssues​​,让 LLM "在几种工具中选择一种"其实就是让它输出 JSON,我们可以将其解析为对应的对象。

class Issue:
title: str
description: str
team_id: str
assignee_id: str

classCreateIssue:
intent: "create_issue"
issue: Issue

classSearchIssues:
intent: "search_issues"
query: str
what_youre_looking_for: str

模式很简单:

  • LLM 输出结构化 JSON
  • 确定性代码执行相应操作(如调用外部 API)
  • 捕获结果并反馈到上下文

这样就能清晰区分 LLM 的决策和应用操作。LLM 决定做什么,代码决定如何执行。LLM 所谓"工具"并不意味着每次都要严格映射到某个函数。

理解这一本质后,你可以更灵活地设计工具接口:定义清晰数据结构、处理异常、优化性能,甚至支持非原子复杂操作。不要被"function calling"等术语迷惑,工具调用只是 LLM 决策与应用逻辑之间的桥梁,关键是保持决策层和执行层的清晰分离。

重要的是,"下一步"未必像"运行纯函数并返回结果"那样原子。当你把"工具调用"视为模型输出、描述确定性代码应做什么的 JSON 时,就获得了极大灵活性。这与原则八(拥有控制流)完美结合。

理解笔记

构建 Agent 过程中,采用关注点分离方式,设计工具使用方式:

  • LLM 进行决策,分析当前情景,决定下一步要干什么;
  • 工具进行确定性执行;
  • 工具调用的本质是 LLM 输出结构化数据(如 JSON),由后端代码解析并执行,LLM 不直接操作外部世界。
  • 关注点分离有助于系统的可维护性和安全性,LLM 负责"决策",代码负责"执行"。

5.统一执行状态和业务状态

传统系统往往分离执行状态(当前步骤、等待状态、重试次数等)和业务状态(用户数据、处理历史等),这种分离会增加系统复杂度并带来一致性问题。在AI应用之外的许多基础设施系统也试图分离"执行状态"和"业务状态",这种分离可能是有价值的,但对您的用例来说可能过于复杂。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

概念梳理:

  • 执行状态:当前步骤、下一步、等待状态、重试次数等。
  • 业务状态:代理工作流程中到目前为止发生的事情(例如 OpenAI 消息列表、工具调用和结果列表等)

如果可能的话,简化——尽可能统一这些状态。实际上,您可以设计应用程序,使得所有执行状态都可以从上下文窗口推断出来。在许多情况下,执行状态(当前步骤、等待状态等)只是关于到目前为止发生了什么的元数据。

这种方法有几个好处:

  • 简单:所有状态的真相来源
  • 序列化:线程可以轻松序列化/反序列化
  • 调试:整个历史记录在一个地方可见
  • 灵活性:只需添加新的事件类型即可轻松添加新状态
  • 恢复:只需加载线程即可从任何点恢复
  • 分叉:可以通过将线程的某些子集复制到新的上下文/状态 ID 中来随时分叉线程
  • 人机界面和可观察性:将线程转换为人类可读的 Markdown 或丰富的 Web 应用程序 UI

理解笔记

构建 Agent 过程中,构建上下文过程中,将执行状态和业务状态进行统一,一定程度上想起来规则引擎和状态机,构建规则,然后交给事件或者数据。

  • 明确区分并统一 LLM 的"执行状态"与应用的"业务状态",避免信息不一致导致的误判。
  • 为每个状态变更建立可追溯日志,提升系统可观测性和可维护性。

6. 使用简单的 API 启动/暂停/恢复

LLM 代理应设计为拥有清晰、简洁的编程接口,便于生命周期管理。这意味着应提供简单的 API 来启动新的代理实例、暂停执行,以及从特定状态恢复。这对于调试、测试和管理长期运行或复杂代理流程至关重要,使开发者能够随时介入和检查代理行为。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

用户、应用、管道或其他代理都应能便捷地通过 API 启动代理。

当遇到长时间运行的操作时,代理及其协调的确定性代码应能优雅地暂停。

像 webhook 这样的外部触发器应允许代理从中断点恢复,无需与代理编排器深度集成。

核心要求:

  • 简单启动:用户、应用、管道和其他 Agent 应能通过简单 API 启动 Agent
  • 优雅暂停:Agent 及其编排代码应能在需要时暂停
  • 外部恢复:如 webhook 等外部触发器应能让 Agent 从中断点恢复,无需深度集成

这种设计对生产环境尤为重要,为 AI 系统提供必要的安全网和控制机制,使其能处理更高价值任务。最关键的能力是:我们需要能中断正在工作的 Agent 并稍后恢复,尤其是在工具选择和调用之间。

理解笔记

构建 Agent 过程中,采用简单原则,触发后寻找上下文,开始执行后通过状态 ID 进行跟进。

  • 设计简单、清晰的 API,支持 Agent 的启动、暂停和恢复,便于生命周期管理。
  • 为每个 Agent 实例分配唯一状态 ID,方便追踪和恢复。
  • 为长流程任务设计中断点和恢复机制,提升系统健壮性和用户体验。

7. 使用工具呼叫联系人类

虽然 LLM 功能强大,但总有需要人类判断、专业知识或干预的场景。本原则主张通过工具调用,将"人机交互"明确纳入代理功能。当 LLM 遇到模糊、不确定或需外部批准的情况时,应能触发结构化通知或上报给人类操作员,确保关键决策在需要时获得人工监督。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

默认情况下,LLM API 依赖于关键的"高风险"令牌选择:我们是返回纯文本,还是结构化数据?

你对第一个标记的选择非常重视,在这种 ​​the weather in tokyo​​ 情况下,它是:

但在 ​​fetch_weather​​ 情况下,它是一些特殊标记,表示 JSON 对象的开始:

JSON>

让 LLM 始终输出 json,并用自然语言标记(如 ​​request_human_input​​​ 或 ​​done_for_now​​​,而不是像 ​​check_weather_in_city​​ 这样的具体工具)声明意图,往往能获得更好效果。

不会直接提升性能,但你应大胆实验,确保有自由尝试各种方式以获得最佳结果。

class Options:
urgency: Literal["low", "medium", "high"]
format: Literal["free_text", "yes_no", "multiple_choice"]
choices: List[str]

# 人机交互工具定义
classRequestHumanInput:
intent: "request_human_input"
question: str
context: str
options: Options

# 代理循环中的用法示例
ifnextStep.intent == 'request_human_input':
  thread.events.append({
    type: 'human_input_requested',
    data: nextStep
  })
  thread_id = await save_state(thread)
  await notify_human(nextStep, thread_id)
  return
  # 跳出循环,等待带 thread ID 的响应返回
else:
  # ... 其他情况

稍后,你可能会从处理 slack、邮件、短信等事件的系统收到 webhook:

@app.post('/webhook')
def webhook(req: Request):
  thread_id = req.body.threadId
  thread = await load_state(thread_id)
  thread.events.push({
    type: 'response_from_human',
    data: req.body
  })
  # ... 简化处理,实际不建议阻塞 web worker
  next_step = await determine_next_step(thread_to_prompt(thread))
  thread.events.append(next_step)
  result = await handle_next_step(thread, next_step)
  # todo - 循环、跳出或其他自定义逻辑

return {"status": "ok"}

以上包括因素 5(统一执行状态和业务状态)、因素 8(拥有自己的控制流)、因素 3(拥有自己的上下文窗口)和因素 4(工具只是结构化输出)的模式,以及其他几个。

如果我们使用因素 3 - 用自定义格式化的上下文窗口,几轮后上下文可能如下:

(snipped for brevity)

<slack_message>
    From: @alex
    Channel: #deployments
    Text: Can you deploy backend v1.2.3 to production?
    Thread: []
</slack_message>

<request_human_input>
    intent: "request_human_input"
    question: "Would you like to proceed with deploying v1.2.3 to production?"
    context: "This is a production deployment that will affect live users."
    options: {
        urgency: "high"
        format: "yes_no"
    }
</request_human_input>

<human_response>
    response: "yes please proceed"
    approved: true
    timestamp: "2024-03-15T10:30:00Z"
    user: "alex@company.com"
</human_response>

<deploy_backend>
    intent: "deploy_backend"
    tag: "v1.2.3"
    environment: "production"
</deploy_backend>

<deploy_backend_result>
    status: "success"
    message: "Deployment v1.2.3 to production completed successfully."
    timestamp: "2024-03-15T10:30:00Z"
</deploy_backend_result>

这样做的好处:

  • 明确指示:针对不同类型人机交互的工具让 LLM 输出更具体
  • 内外循环:支持在传统 ChatGPT 风格界面之外的代理流程,控制流和上下文初始化不必总是 Agent->Human
  • 多人协作:结构化事件便于跟踪和协调多方输入
  • 多代理:简单抽象可扩展为 Agent->Agent 请求与响应
  • 持久性:结合简单 API 启动/暂停/恢复,打造持久、可靠、可自省的多方工作流

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

理解笔记

构建 Agent 过程中,将"人机交互"标签,明确地加入到代理的功能。

  • 明确将"请求人工输入"作为一种标准工具调用,便于 LLM 在不确定或高风险场景下主动寻求人类协助。
  • 为人机交互设计结构化事件和回调机制,支持多渠道通知和响应。
  • 为人工干预流程建立审计和追踪,提升系统安全性和合规性。

8. 拥有自己的控制流

尽管 LLM 具备"推理"能力,但应用的整体控制流应由确定性代码明确管理。LLM 只应引导控制流的路径,而不应成为控制流本身。这样才能保证可预测性、强健的错误处理,并让开发者为 LLM 的自主性设定清晰边界,防止意外或不良行为。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

如果你拥有自己的控制流,就能做很多有趣的事情。

可以为特定用例构建专属控制结构。例如,某些工具调用可能需要跳出循环,等待人工或其他长时间任务(如训练管道)响应。你还可以实现:

  • 工具调用结果的汇总或缓存
  • LLM 结构化输出
  • 上下文窗口压缩或其他内存管理
  • 日志、追踪和指标
  • 客户端速率限制
  • 持久睡眠/暂停/“等待事件”

这种模式允许你根据需要中断和恢复代理流程,打造更自然的对话和工作流。

例如:我对每个 AI 框架的首要诉求是,必须能在"选择工具"与"调用工具"之间中断代理并稍后恢复。

如果没有这种粒度的可恢复性,就无法在工具调用前进行人工审核或批准,这会导致:

  • 长任务只能暂停在内存中,进程中断就得重头再来
  • 只能让代理处理低风险任务,如研究和总结
  • 让代理做更大更有用的事,但只能祈祷它不会出错

理解笔记

构建 Agent 过程中,使用上下文或者 code 控制流程,LLM 引导控制流的路径,而不是成为控制流本身。

  • 控制流应由确定性代码主导,LLM 只负责决策建议,避免"黑盒"自动化带来的不可控风险。
  • 为关键节点(如工具调用前)设计人工审核或中断机制,提升安全性。
  • 将控制流逻辑与 LLM 推理解耦,便于测试和维护。

9. 将错误压缩到上下文窗口中

当 LLM 驱动的应用发生错误时,应向 LLM 提供简明、相关的故障信息。本原则建议将错误详情以结构化方式压缩进上下文窗口,让 LLM 更好地理解问题,并可能建议恢复策略或替代方案。这样可避免直接传递冗长原始日志,防止模型困惑。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

这一点虽短,但很重要。代理的优势之一是"自我修复"——对于短任务,LLM 可能会调用失败的工具。优秀的 LLM 能很好地读取错误消息或堆栈跟踪,并判断后续工具调用需要如何调整。

class ErrorHandler:
    def__init__(self):
        self.error_counts = defaultdict(int)
        self.max_retries = 3

    def handle_error(self, error: Exception, context: str) -> str:
        error_type = type(error).__name__
        self.error_counts[error_type] += 1

        if self.error_counts[error_type] > self.max_retries:
          returnf"<critical_error>Too many {error_type} errors. Human intervention required.</critical_error>"

        # 压缩错误信息
        compressed_error = self.compress_error(error, context)
        returnf"<e>{compressed_error}</e>"

    def compress_error(self, error: Exception, context: str) -> str:
        # 提取关键信息,过滤掉冗余的堆栈跟踪
        if isinstance(error, ConnectionError):
          returnf"Connection failed to {context}: {str(error)[:100]}"
        elif isinstance(error, ValidationError):
          return f"Validation error in {context}: {error.args[0] if error.args else 'Unknown validation issue'}"
        else:
          return f"{type(error).__name__} in {context}: {str(error)[:100]}"

上下文窗口中的错误表示:

<deploy_backend>
    intent: "deploy_backend"
    tag: "v1.2.3"
    environment: "production"
</deploy_backend>

<e>部署服务连接失败: 无法连接到 https://deploy.company.com</e>

<request_more_information>
    intent: "request_more_information_from_human"
    question: "部署服务似乎不可用,我应该尝试其他方案还是等待?"
    context: "由于连接问题,v1.2.3部署失败。"
</request_more_information>

在有限的上下文窗口中表示错误信息是一门艺术:既要保留足够的诊断信息让AI理解问题所在,又不能占用过多宝贵的上下文空间。您需要实现智能的错误分类、压缩和优先级排序机制,甚至可以在错误解决后将其从上下文中移除以节省空间。同时,建立错误计数器和阈值机制防止AI陷入重复错误的循环,当达到阈值时及时升级给人类处理。

** 错误恢复策略:**

def smart_error_recovery(thread: Thread, error: Exception):
if isinstance(error, RateLimitError):
    # 对于速率限制,等待并重试
    thread.events.append({
      'type': 'rate_limit_hit',
      'data': {'wait_time': error.retry_after}
    })
    return'wait_and_retry'

elif isinstance(error, AuthenticationError):
    # 对于认证错误,请求人类干预
    thread.events.append({
      'type': 'auth_error',
      'data': {'service': error.service}
    })
    return'request_human_help'

else:
    # 对于其他错误,让LLM决定下一步
    compressed_error = compress_error(error)
    thread.events.append({
      'type': 'error',
      'data': compressed_error
    })
    return 'let_llm_decide'

理解笔记

构建 Agent 过程中,智能错误处理机制是构建可靠AI系统的关键组成部分。

  • 错误信息应结构化、简明地反馈给 LLM,避免冗长日志导致模型困惑。
  • 为常见错误类型设计标准化摘要模板,便于 LLM 理解和自我修复。
  • 记录每次错误及其处理过程,持续优化错误处理策略。

10.小型、专注的 Agent

本原则主张将复杂 LLM 应用拆分为更小、更专业的代理,每个代理专注于不同任务或领域。模块化方法能减轻单个 LLM 的认知负担,通过缩小关注范围提升准确性,也让系统更易开发、测试和维护。小型代理也更易于微调和优化。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

与其构建包揽一切的单体代理,不如构建小型、专注、专业的代理。代理只是更大、更确定性系统的一个构件。

关键洞察在于 LLM 的局限性:任务越大越复杂,步骤越多,上下文窗口越长,LLM 越容易迷失或分心。让代理专注于特定领域,并将流程控制在 3-10 步、最多 20 步内,有助于保持上下文可控,提升 LLM 性能。

随着上下文扩大,LLM 更容易迷失或分心

小型、专注代理的优势:

  • 可控上下文:窗口越小,性能越好
  • 职责明确:每个代理有清晰范围和目标
  • 更高可靠性:减少复杂流程中迷失的风险
  • 易于测试:便于验证特定功能
  • 调试友好:更易定位和修复问题

理解笔记

构建 Agent 过程中,设计专注于特定领域的小型 Agent。

  • 将复杂任务拆分为多个小型、专注的 Agent,每个 Agent 只负责单一领域或流程。
  • 为每个 Agent 明确职责边界,便于独立开发、测试和优化。
  • 通过组合多个 Agent 实现复杂业务,提升系统灵活性和可维护性。

11. 随时随地触发,随时随地与用户见面

可靠的 LLM 应用应能从多种入口和界面访问和触发。无论网页聊天、API、移动端还是内部仪表盘,代理都应能无缝接收输入并响应。这种灵活性确保代理能集成进现有流程,并在用户偏好的平台上互动,提升可用性和采纳率。

从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

允许用户通过 Slack、邮件、短信等任意渠道触发客服,客服也可通过同样渠道响应。

这样做的好处:

  • 在用户所在的地方与其见面:有助于打造"像真人或数字同事"的 AI 应用
  • 外环代理:支持代理被事件、定时任务等非人类触发,关键节点可寻求人类协助
  • 高风险工具:快速整合多方人员,让代理能安全执行高风险操作(如发外部邮件、更新生产数据),提升可审计性和信心

理解笔记

构建 Agent 过程中,使用多渠道接入用户,似乎对国内的封闭系统存在一定的冲击。

  • 支持多种入口和渠道(如 Web、API、IM、定时任务等)触发和交互,提升系统可用性。
  • 为每个渠道设计统一的接入和认证机制,确保安全和一致体验。
  • 关注国内外不同生态的集成方式,提升系统兼容性。

12. 让你的 Agent 成为无状态 Reducer

本原则鼓励将 LLM 代理设计为无状态处理器,每次只依赖输入和外部状态生成输出,无需在交互间维护内部长期记忆。所有持久状态应交由外部存储(如数据库、消息队列)管理。这种模式提升了可扩展性、弹性和水平扩展能力,任何代理实例都能处理任意请求,单实例故障也不会丢失数据。


从理论到实践:我用12要素方法重构了AI 智能体,效果惊人!-AI.x社区

理解笔记

构建 Agent 过程中,采用无状态的纯函数的转换器方式,更能满足扩展、测试等需求。

  • Agent 应设计为无状态的"Reducer",每次只依赖输入和外部状态,输出结果。
  • 将所有持久化状态交由外部存储系统管理,提升系统弹性和可扩展性。
  • 无状态设计有助于水平扩展和故障恢复,是生产级 Agent 的基础。

这些原则如何落地

电商平台的智能客服

假设您在构建一个电商平台的智能客服系统。按照12-Factor原则,您不会直接使用某个Agent框架,而是:首先设计清晰的工具调用接口(查询订单、处理退款等),然后精心设计提示词来处理常见的客服场景,接下来实现状态管理来跟踪每个对话的进展,最后设计简单的API来让人工客服能够随时介入。这样构建的系统不仅更可靠,也更容易维护和扩展。

内容创作助手的设计思路

如果您在开发内容创作助手,可以这样应用这些原则:将不同的创作任务(标题生成、大纲规划、段落写作)设计为独立的小Agent,每个Agent专注于自己的领域。用户可以从任何平台(Web、移动App、浏览器插件)触发这些Agent,系统会根据用户的创作进度智能地组织上下文信息。这种设计既提高了创作效率,又保持了用户的创作控制权。

参考资料

[1] 12-Factor Agents: ​​​https://github.com/humanlayer/12-factor-agents​


本文转载自​AI 博物院​ 作者:longyunfeigu


©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-9-1 09:42:04修改
收藏
回复
举报
回复
相关推荐