← [ BACK_TO_NOTES ]

Agent 项目系统架构设计

比较 Agent 的抽象层级与逻辑分层,说明运行时、工具、状态、授权和可观测性如何协作,以及原型向生产系统演进时的取舍。

#ai #synthesis #growing #agent

[!info] related notes

Agent 项目系统架构设计

一句话定义

Agent 项目系统架构是把 LLM、工具、状态、记忆和可观测性组装成一个可运行、可维护、可演进的生产系统的分层设计方法——核心决策不是”用什么框架”,而是”从哪一档抽象开始”。

第一步决策:三档抽象选型

在画架构图之前,先回答一个问题:你的任务需要多复杂的 agent?

档位抽象层级代表实现适用场景
Raw API直接调用 LLM API + tool schemaOpenAI Responses API, Anthropic Messages API单轮任务、最大控制权、快速验证
Managed Loop框架管理执行循环 + 工具分发OpenAI Agents SDK, Vercel AI SDK标准 tool-use agent、多轮对话
Graph显式状态机 + 条件路由LangGraph StateGraph复杂分支、循环、human-in-the-loop

[!note] 选型依据 Anthropic 建议从能解决问题的最简单方案开始,直接 API 调用是一个起点。是否引入框架取决于控制流、恢复、工具编排与团队维护成本,没有“超过 5 个工具才升级”的通用阈值;Graph 也适合无环的复杂分支。

先用代表性任务验证方案,再比较框架引入的收益与调试成本。下文六层是设计视角,不是必须逐项部署的产品清单,也不表示本仓库已有这些服务。

六层架构(当需要完整系统时)

一旦确定了抽象档位,完整的 agent 项目可以拆成六层:

┌─────────────────────────────────────────────────────┐
│                   API Gateway                        │
│         认证 · 限流 · 请求路由 · 响应格式化            │
├─────────────────────────────────────────────────────┤
│               Agent Orchestrator                     │
│       多 agent 路由 · 任务拆分 · 协作编排               │
├─────────────────────────────────────────────────────┤
│                 Agent Runtime                        │
│     执行循环 · 状态机 · 停止条件 · 审批检查点             │
├─────────────────────────────────────────────────────┤
│              LLM Provider Layer                      │
│     模型抽象 · 流式输出 · 重试 · token 计量             │
├─────────────────────────────────────────────────────┤
│           Tool & Integration Layer                   │
│      工具注册 · 执行沙箱 · 结果标准化 · 错误映射          │
├─────────────────────────────────────────────────────┤
│          State & Persistence Layer                   │
│    会话状态 · 工作记忆 · 对话历史 · 长期记忆 · 向量存储    │
└─────────────────────────────────────────────────────┘
         ↕ 所有层依赖 ↕
    Observability Layer (tracing · 指标 · 评估 · 成本)

关键约束:这张图表达职责,不定义严格的栈式调用顺序。Runtime 通过接口依赖模型、工具和持久化能力;业务规则避免耦合具体传输或供应商 SDK。Observability 是横切关注点,可通过适配器或注入接入。

各层职责与设计要点

1. API Gateway

职责:对外暴露 HTTP/WebSocket/SSE 端点,处理认证、限流、请求分发。

设计要点:

  • Gateway 可以统一认证,但下游必须验证身份上下文的可信来源,并在业务与工具边界检查租户、资源和动作授权;不能信任可由外部伪造的用户 header
  • 对已获准公开的事件,Gateway 可透传 SSE 并避免无必要缓冲;必须完整校验的内容不能提前泄露
  • 请求体做 schema 校验,非法请求在入口就拒绝,不要让脏数据深入 runtime
  • 每个请求带 request ID,贯穿所有下游日志

反模式:

  • ❌ 把 agent 逻辑直接写在 HTTP handler 里
  • ❌ gateway 层做业务决策(应该路由到哪个 agent)

2. Agent Orchestrator

职责:决定”这个任务交给谁”——单 agent 直接执行,多 agent 时做任务拆分和协作编排。

五种编排模式及选型:

模式机制最佳场景复杂度
Manager-as-tools主 agent 把子 agent 当 tool 调用层级任务、专家分工低
Handoffsagent A 主动移交控制权给 agent B客服分流、多领域助手中
Sequential pipeline固定顺序,前一个的输出是后一个的输入研究→起草→审核→发布低
Orchestrator-workers中央 LLM 动态拆解任务、分配给 worker无法预先确定子任务中
Graph有向图 + 条件边,支持循环和分支复杂状态机、HITL 检查点高

[!tip] 选型建议 固定依赖可先用顺序调用;中心协调者调用专家时可考虑 Manager-as-tools;需要交接对话控制权时考虑 Handoffs;动态拆解适合 Orchestrator-workers;复杂条件分支、恢复与人工介入可考虑 Graph。

单 Agent 与多 Agent 应在相同任务上比较质量、成本和失败恢复。工具数量本身不能证明“1 个 15 工具的 Agent 优于 3 个各 5 工具的 Agent”;状态共享、职责隔离和协调成本都影响结果。

什么时候不需要这层:

  • 只有一个 agent,没有路由需求
  • 用 Dify/LangGraph 等框架,框架本身承担了编排职责

3. Agent Runtime

职责:驱动单个 agent 的执行闭环——接收输入、调用 LLM、分发工具调用、收集结果、判断是否继续。

设计要点:

  • runtime 是一个状态机,不是一段线性代码。核心循环:

以下接口与消息转换是伪实现,用于说明循环;实际 SDK 的消息格式、持久化和错误处理需由适配层实现。

async def run(self, input: AgentInput) -> AgentOutput:
    state = self.init_state(input)
    for turn in range(self.max_turns):
        llm_output = await self.call_llm(state.context)
        state.context.append(self.assistant_message(llm_output))
        if llm_output.has_tool_calls:
            tool_results = await self.execute_authorized_tools(llm_output.tool_calls)
            state.context.extend(self.tool_result_messages(tool_results))
        else:
            return self.finalize(llm_output)
    return self.force_stop(state)  # 硬上限,不依赖模型自行停止
  • 停止条件必须显式定义:模型输出了最终回答?达到最大轮次?token 预算耗尽?触发安全阀?
  • 需要审批的动作在执行前等待批准,批准绑定动作和参数;工具或领域服务仍需在产生副作用时执行资源授权与状态校验
  • runtime 不应该知道具体工具的实现细节,只通过 Tool Layer 的统一接口调用
  • 交互任务可用 streaming 改善等待体验;后台任务、原子结果或需完整校验的内容也可一次性交付。先定义哪些事件与内容允许公开

关键接口:

class AgentRuntime:
    async def run(self, input: AgentInput) -> AgentOutput:
        """一次完整的 agent 执行,从输入到最终输出"""
        ...

    async def step(self, state: AgentState) -> AgentState:
        """单步执行:调 LLM → 处理输出 → 更新状态"""
        ...

4. LLM Provider Layer

职责:抽象底层模型调用,屏蔽不同 provider 的 API 差异。

设计要点:

  • 统一接口:不管用 OpenAI、Anthropic 还是本地模型,调用方式一致
  • 需要流式交互时保留增量事件接口;首事件延迟与完整任务完成时间是不同指标
  • 按 provider 的事件协议,以调用 ID、item ID 或 index 关联工具增量,完整组装并校验参数后再执行;不能只取最后一块或混合不同调用
  • 按错误码、错误类型与 Retry-After 决定重试:临时限流/5xx 可有界退避,配额耗尽或无效参数需先修复;遵守总预算,避免重复副作用
  • token 计量在这一层完成,向上层暴露 usage 信息,让 runtime 可以做上下文裁剪决策
  • model fallback chain:仅在已批准且兼容的候选中切换,记录实际模型、能力差异与 fallback 原因

关键接口:

class LLMProvider(Protocol):
    def chat_stream(
        self,
        messages: list[Message],
        tools: list[ToolDef] | None = None,
    ) -> AsyncIterator[StreamChunk]:
        """流式返回,每个 chunk 可能是 text delta 或 tool_call delta"""
        ...

5. Tool & Integration Layer

职责:注册、执行、标准化所有外部工具的调用。

设计要点:

  • 工具注册是声明式的:name、description、parameters schema、execute function
  • 工具执行结果标准化为统一格式:{success, data, error, metadata}
  • 每个工具有独立的超时和重试配置,不是全局一刀切
  • 工具依赖通过显式参数或受限上下文传入,避免读取 Agent 隐式全局状态;写库、发消息等工具有副作用,并非纯函数
  • 对 policy 要求审批的动作,由 runtime 协调审批;工具执行边界仍校验授权、参数与当前状态,不因上游曾批准就跳过检查

[!warning] Tool 接口设计是 agent 的可靠性表面 Anthropic 记录过 SWE-bench 工具路径案例:目录切换后相对路径容易误用,要求绝对路径消除了该案例中的路径歧义。该案例说明接口消歧有助于可靠调用,不能推导出所有任务的成功率只由工具文档决定。

人类工程师能否理解描述是初步检查,模型的实际调用成功率仍需单独评测。

错误处理分层:

错误类型谁处理怎么处理
参数校验失败Tool Layer返回结构化错误,让 LLM 自行修正
外部服务超时Tool Layer核实结果是否未知与是否可安全重试,再返回结构化状态
权限不足Tool Layer返回明确错误信息
工具不存在Runtime停止执行,报告给用户

关键原则:可恢复错误可以回填 context,让模型在运行时预算与权限内选择下一步。鉴权失败、数据损坏或副作用结果未知等情况应按 policy 停止、查询或升级;不能把所有恢复决策交给模型。

6. State & Persistence Layer

职责:管理 agent 运行所需的所有状态——短期会话、工作记忆、对话历史、长期记忆。

四层状态分离:

层级内容存储位置生命周期特征
运行状态当前轮次、tool call、中间结果内存或持久存储一次 run 及其恢复期是否 checkpoint 由恢复契约决定
工作记忆模型当前使用的任务信息与摘要内存或持久快照任务/会话内不代替权威执行状态
对话历史多轮消息、agent 输出按产品需要持久化按保留策略消息记录不代替业务数据库的事实
长期记忆用户偏好、项目知识、实体记忆数据库、文档、向量索引等跨会话,受 TTL/删除策略控制经作用域与相关性筛选后注入上下文

显式上下文设计:区分 run 范围的共享依赖与工具可访问的有限上下文。旧文中的 runtimeContext / toolsContext 可作为应用自定义命名,不应当作所有 Vercel AI SDK 版本都提供的标准 API;typed 字段本身也不形成权限隔离。

LangGraph 模式:StateGraph 的节点读取状态并返回更新,由 reducer 决定合并。配置 checkpointer 后,在 super-step 边界持久化状态,并通过 thread_id 关联恢复;这不是任意代码行或每次节点内部操作都可恢复。内存 saver 不能保证进程重启后恢复,外部副作用仍需去重与恢复设计。见 LangGraph Checkpointers。

[!tip] 状态管理核心原则

  1. 持久化消息历史用于恢复对话;关键执行与业务事实各有权威状态,不能从聊天摘要推断
  2. 长期记忆可由工具或运行时检索后选择性注入;不要把所有记忆塞进 prompt
  3. Runtime/上下文构建器管理窗口预算,状态层保存必要来源与快照;裁剪时保留工具调用与结果的对应关系
  4. 用明确类型和受限作用域减少隐式耦合,必要的访问边界仍由运行时执行

数据流:一次请求的完整路径

用户发送消息
    │
    ▼
API Gateway ── 认证、限流、schema 校验、注入 request ID
    │
    ▼
Orchestrator ── 判断路由(哪个 agent)
    │
    ▼
Runtime ── 加载对话历史 + 检索长期记忆 → 组装 context
    │
    ├──▶ LLM Provider ── 调用模型,流式返回
    │        │
    │        ▼
    │    模型输出:text / tool_call / handoff
    │        │
    │        ├── text → 输出策略校验 → 交付获准公开的内容
    │        │
    │        ├── tool_call → 执行前检查 approval → Tool Layer 执行
    │        │        │
    │        │        ▼
    │        │   结果(成功/错误)回填到 context
    │        │        │
    │        │        └── 回到 LLM Provider(下一轮)
    │        │
    │        └── handoff → 交给目标 agent(Orchestrator 层处理)
    │
    ▼
State Layer ── 持久化必要运行状态、消息与业务结果
    │
    ▼
确认终态(执行中可先交付获准的进度/内容事件)
    │
    ▼
Observability ── 异步发送 trace(LLM 调用、tool 调用、耗时、token、成本)

关键观察:

  1. LLM 和 Tool 的调用是交替进行的,不是一次性的——runtime 必须支持循环
  2. 流式内容经过权限与输出策略后交付;需要完整验证的内容必须等待验证,不直接透传 raw proposal
  3. 可恢复工具错误可回填 context;runtime 决定恢复范围、预算与终态
  4. observability trace 可异步导出,但有采集、序列化和队列开销;关键业务审计记录不能只依赖可能丢失的后台导出

错误恢复与容错

Agent 系统的错误比普通 web 应用复杂得多,因为涉及 LLM 的不确定性。

分层容错策略

LLM 层:

  • 模型超时 → 重试(指数退避),超过阈值后降级到备用模型(model fallback chain)
  • 模型输出格式异常 → 重试一次(在 prompt 中强调格式要求),仍失败则报错
  • 上下文溢出 → 触发上下文裁剪,重试
  • 降级:只交付在已批准范围内的有限结果;完整分析失败不自动意味着摘要仍有效

Tool 层:

  • 工具超时 → 区分确定失败与结果未知;在状态查询、幂等和预算约束内恢复
  • 工具返回异常 → 结构化错误信息回填 context,模型可以理解并调整
  • 工具不可用 → 注册时标记 degraded,runtime 跳过不可用工具
  • 同时设置工具调用超时与整个 run 的总时限,重试和 fallback 也消耗总预算

Runtime 层:

  • 超过最大轮次 → 记录明确停止原因;只返回可交付的部分结果,并说明任务未完成
  • token 预算耗尽 → 强制停止(比轮次限制更精确,因为某些轮次 token 很少)
  • 审批被拒绝 → 记录拒绝原因,让模型调整策略

Session 层:

  • 内存历史丢失 → 从持久记录重建;可恢复范围取决于提交频率与 checkpoint,不能保证只丢最后一轮
  • 长期记忆不可用 → 根据任务契约决定降级或停止;必要用户事实或权限信息不能用猜测补齐

死循环防护

一种常见的 agent 失效模式是无进展循环:模型反复调用同一个工具、反复得到同样的错误、反复重试。

三层防护:

1. 硬上限:max_turns(runtime 层,不依赖模型自觉)
2. 工具去重:连续 N 次相同 tool + 相同参数 → 强制中断
3. Stagnation 检测:如果 agent 在 K 轮后的输出与 K-2 轮无实质差异 → 终止

[!tip] Stagnation 检测(Evaluator 模式) 用一个轻量 evaluator 比较 agent 近 N 轮的输出。可以用文本相似度或 token 重叠率寻找疑似无进展的步骤。它只能作为启发式信号:重复文本可能对应有效轮询,新措辞也可能毫无进展;应结合工具状态、任务里程碑和预算判断。

OpenAI Agents SDK 的 Guardrail 模式:输入检查可采用 blocking 或 parallel 执行;并行检查降低等待,但主流程可能在结果返回前已开始工作。必须先验证的操作需使用阻塞检查或工具执行边界的授权,不能靠事后中断撤销已发生的副作用。见 Guardrails and human review。

可观测性

Trace 适合关联调用路径,Logs 提供细节,Metrics 用于趋势、SLO 和告警;三者互补。

Trace 模型(Langfuse 参考架构)

Session(多轮对话)
  └── Trace(一次完整请求)
        ├── Observation: LLM 调用(prompt, completion, model, tokens, latency, cost)
        ├── Observation: Tool 执行(name, args, result, duration, success/fail)
        ├── Observation: 路由决策(为什么选这个 tool/agent)
        └── Observation: 状态快照(每个决策点的 context)

关键设计:

  • tracing 可以在后台批量导出;需控制采样、队列、背压和关闭时 flush,不能承诺零开销
  • 按调用类型记录必要的输入/输出引用、耗时与成本;敏感 payload 需要脱敏、采样或受控保存
  • session 维度聚合,可以回溯整个多轮对话的决策链

值得追踪的 5 类信息

  1. 每次 LLM 调用(prompt, completion, model, tokens, latency)
  2. 每次 tool 调用(name, arguments, result, duration, success/failure)
  3. 路由决策(为什么选这个 tool/agent)
  4. 决策相关状态的版本、引用与必要快照;敏感内容按访问和保留策略处理,不无限复制完整状态
  5. 成本累积(per-trace cost tracking)

可观测性 → 评估闭环

最好的系统把 tracing 和评估连起来:

  • LLM-as-a-Judge:自动评估 agent 输出质量
  • Prompt 管理:prompt 变更触发 eval suite
  • 成本告警:异常成本自动通知
  • A/B 测试:对比不同 prompt / model 的效果

工具选型:可比较 Langfuse、LangSmith 与已有的 OpenTelemetry/APM 链路。LLM 专用工具便于查看 token、Prompt 和评测结果;通用平台也可通过语义属性与集成扩展,是否足够取决于已有栈和需求。

Debug 工作流

1. 从 trace 复现失败
2. 检查每个决策点的状态
3. 检查工具名称、参数、结果与实际调用失败证据,判断接口是否存在歧义
4. 检查 prompt 是否充分
5. 添加测试用例到 eval suite

从 Prototype 到 Production 的架构演进

阶段一:单文件 Prototype(Raw API)

main.py
├── prompt + LLM API 调用
├── 几个 tool 函数
└── print 输出

目标:验证 agent 能不能完成核心任务。

关键原则:从能验证任务的最小方案开始;已有熟悉的框架也可复用,但应理解实际执行路径。

退出条件:代表性成功任务、关键失败场景和成本/延迟达到预先定义的验收条件;通过几个示例仅是初步证据。

阶段二:加入 Runtime 和 API(Managed Loop)

app/
├── api/          # FastAPI / Gin 路由
├── runtime/      # 执行循环 + 停止条件
├── tools/        # 工具注册 + 执行
└── llm/          # 模型调用抽象

目标:能通过 HTTP 调用,有基本的错误处理和流式输出。

新增:LLM Provider 抽象、Tool 注册机制、Runtime 执行循环。

阶段三:加入状态管理和可观测性

app/
├── api/
├── runtime/
├── tools/
├── llm/
├── state/          # 会话管理 + 工作记忆 checkpoint + 对话历史持久化
├── memory/         # 长期记忆 + 向量检索(通过 tool 接入)
└── observability/  # tracing + 成本追踪 + eval

目标:支持多轮对话,重启后能恢复上下文,出问题能回溯。

[!tip] 状态管理和可观测性应该同阶段引入 状态持久化支持恢复,可观测记录帮助解释上下文、工具与成本变化。二者通过 run/session identity 关联;tracing 可以独立关联多次请求,但不能代替业务执行状态。

阶段四:生产化

这个阶段解决的是真实痛点,不是架构完整性:

痛点解法
Tool 接口失败率高根据失败样本改进参数、路径基准、错误返回与文档,再评测效果
成本失控per-trace 成本追踪、请求级 token 预算、easy task 降级到便宜模型
延迟太高测量瓶颈;按需 streaming、并行独立调用、缓存,并设置预算
输出不确定定义行为契约、schema 校验与回归评估;temperature=0 不保证确定性,JSON mode 不等于严格 schema 约束
状态丢失外部状态存储(Redis/Postgres)、checkpoint 工作记忆

阶段五:多 Agent 和扩展

app/
├── gateway/        # API Gateway
├── orchestrator/   # 按任务需要选择多 agent 编排
├── agents/         # 多个独立 agent
│   ├── research/
│   ├── coding/
│   └── review/
├── shared/         # 共享工具、LLM Provider、状态层
└── ...

关键:只有出现实际的路由、协调或并行分工需求时才增加 Orchestrator;演进阶段不是所有项目都必须按顺序经过的清单。

常见反模式

反模式问题正确做法
一锅炖runtime、工具、API 逻辑全在一个文件分层,每层只做一件事
隐式状态依赖工具函数内部读 agent 的全局变量显式传递依赖、限定作用域并约束副作用
硬编码模型换模型要改所有调用处LLM Provider 抽象,运行时可切换
无限循环靠模型自觉模型说”我停止了”就停runtime 设硬上限 + stagnation 检测
对话历史存在内存里重启丢数据,多实例不共享持久化到数据库,内存只是缓存
日志只打 print上线后无法排查问题结构化 tracing + request ID 贯穿
过早引入框架LangGraph/CrewAI 的概念比你的需求复杂先用 Raw API 跑通,复杂度到了再引入框架
单大 dict 传状态任何 tool 都能读写任何状态,隐式耦合typed + scoped 状态容器(runtimeContext / toolsContext)
多 agent 解决单 agent 问题增加复杂度但效果没提升在相同任务与预算下比较方案,仅保留有证据的复杂度收益

边界与易混淆点

  • 这不是微服务架构:六层是逻辑分层,不是物理分层。单体部署时它们是模块,分布式部署时它们才变成服务
  • Orchestrator 不是必须的:单 agent 项目只需要 Runtime + Tool Layer + LLM Provider,不要为了架构完整性硬加编排层
  • Runtime ≠ 框架:LangGraph、CrewAI 是框架,它们帮你实现 runtime。但你需要先理解 runtime 该做什么,才能判断框架是否合适
  • 记忆 ≠ 上下文:上下文是本次模型调用可见的信息;记忆按需提供其中一部分,既有工作记忆也有跨会话记忆,详见 agent-memory-types-and-policies
  • 工作记忆 ≠ 对话历史:对话历史是消息记录,工作记忆是当前推理工作集;可恢复的执行进度需显式保存为 run state
  • Guardrail ≠ Stopping condition:guardrail 验证规则,stopping condition 定义终止条件;一次 guardrail 触发可以成为停止原因,二者并非必须独立或并行运行
创建于 2026/6/25 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本