Agent 架构 · 2025年10月28日 · 4 分钟

Agent Workflow 实战:从零构建一个全栈智能助手

路由 Workflow → 编排 Workflow → 记忆 Workflow → 安全 Workflow——四阶段 Agent Workflow 的完整设计与代码骨架。

前面几篇文章讲了 Function Calling、Agent 设计、MCP 协议、RAG——这文章把所有这些技术组合起来,构建一个完整的 Agent Workflow 系统

传统应用调用是线性的:请求 → 处理 → 响应。Agent Workflow 不一样——它是一个有分支、有循环、有状态管理的编排过程

这个智能助手能做什么:

能联网搜索(搜索 API)
能查本地文档(RAG)
能记住上下文(短期记忆 + 长期记忆)
能执行简单操作(发邮件、创建工单)
能多轮对话(在对话中修正和追问)

Workflow 总体架构

全栈智能助手的 Workflow 由四个阶段组成,每个阶段内部又有多个步骤:

用户消息
    │
    ▼
┌────────────────────────────────────────────────────────────┐
│  阶段 1:路由(Route)                                       │
│                                                            │
│  用户输入 → IntentRouter → 分支决策                          │
│      │          │           │        │                      │
│      ▼          ▼           ▼        ▼                      │
│   search    query     action    chat    ambiguous            │
│   (搜索)    (知识库)    (操作)   (闲聊)  (需澄清)            │
│                                                            │
│  → 输出:路由结果 + 工具列表                                  │
└────────────────────────────────────────────────────────────┘
    │
    ▼
┌────────────────────────────────────────────────────────────┐
│  阶段 2:编排(Orchestrate)                                 │
│                                                            │
│  Agent Loop(最多 10 轮):                                 │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  第 N 轮:                                            │  │
│  │  ① LLM 推理:分析上下文,决定调用工具还是直接回答       │  │
│  │  ② 如果是工具调用 → 批量执行 → 结果反馈 → 继续循环    │  │
│  │  ③ 如果是直接回答 → 结束循环                           │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                            │
│  → 输出:执行结果链                                         │
└────────────────────────────────────────────────────────────┘
    │
    ▼
┌────────────────────────────────────────────────────────────┐
│  阶段 3:记忆(Memory)                                     │
│                                                            │
│  短期记忆:                                长期记忆:       │
│  ├─ 追加本轮交互                              ├─ 检测是否需要检索│
│  ├─ 检查是否溢出                              ├─ 语义检索相关历史 │
│  ├─ 溢出→压缩早期轮次为摘要                    └─ 若需要→注入上下文 │
│  └─ 保留系统提示 + 最近 N 轮                                      │
│                                                            │
│  → 输出:更新后的上下文                                         │
└────────────────────────────────────────────────────────────┘
    │
    ▼
┌────────────────────────────────────────────────────────────┐
│  阶段 4:安全(Safety)                                     │
│                                                            │
│  ├─ 高风险操作检测(删除/修改/发信→需二次确认)               │
│  ├─ 输出内容过滤(PII 检测、有害内容拦截)                    │
│  └─ 审计日志持久化                                           │
│                                                            │
│  → 输出:最终回复                                            │
└────────────────────────────────────────────────────────────┘
    │
    ▼
  最终回复

核心设计原则:路由层做分支,编排层做循环,记忆层做状态,安全层做约束。 每层关注一件事,互不干扰。

阶段一:路由(Route)——Workflow 的第一个分支

路由是整个 Workflow 的入口决策点。不是所有的用户消息都需要进入 Agent Loop——有些可以直接回答,有些需要检索,有些需要反问。

路由 Workflow:
                    ┌─────────────┐
                    │  用户输入    │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │  IntentRouter│
                    │  (LLM 分类) │
                    └──────┬──────┘
                           │
          ┌────────────────┼────────────────┐────────────────┐
          ▼                ▼                ▼                ▼
    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────────┐
    │ search   │    │ query    │    │ action   │    │ ambiguous    │
    │ 暴露搜索 │    │ 暴露 RAG │    │ 暴露操作 │    │ 反问用户澄清 │
    │ 工具     │    │ 工具     │    │ 工具     │    │              │
    └────┬─────┘    └────┬─────┘    └────┬─────┘    └──────────────┘
         │               │               │                   │
         └───────────────┼───────────────┘                   │
                         ▼                                   │
                  ┌──────────────┐                            │
                  │ 进入编排阶段  │ ←──────── 直到明确意图 ────┘
                  └──────────────┘

路由的好处不仅是"减少不必要的工具注册",更重要的是控制了 Workflow 的分支路径——搜索路径只暴露搜索工具,工具列表短、LLM 选择准确率高;操作路径暴露写操作工具,并预先加载安全门控。

歧义处理是路由中关键的一个子 Workflow:

ambiguous 子 Workflow:
  ① LLM 识别出用户意图不明确
  ② 生成候选意图列表(2-3 个)
  ③ 反问用户选择
  ④ 用户确认 → 重新路由
  ⑤ 如果超过 2 次反问都不明确 → 转人工

这个子 Workflow 防止了 Agent 在意图不明确时"瞎猜"。

阶段二:编排(Orchestrate)——Workflow 的核心循环

编排阶段是整个 Agent Workflow 的心脏。它本质上是一个带反馈的 while 循环

Agent Loop Workflow:
                    ┌─────────────┐
                    │ 进入编排阶段  │
                    │ messages:    │
                    │ [系统提示+   │
                    │  用户问题+   │
                    │  历史]       │
                    └──────┬──────┘
                           ▼
               ┌─────────────────────┐
               │ round = 0           │
               └─────────┬───────────┘
                         ▼
               ┌─────────────────────┐
               │ round < max_rounds?  │──→ 否 → 超时退出
               └─────────┬───────────┘
                         │ 是
                         ▼
               ┌─────────────────────┐
               │ LLM.chat(messages,  │
               │      tools)         │
               └─────────┬───────────┘
                         ▼
               ┌─────────────────────┐
               │ 有 tool_calls?      │
               │    ↓        ↓       │
               │   是        否      │
               └────┬────────┬───────┘
                    │        │
                    ▼        └──→ 返回 text,结束
          ┌────────────────────┐
          │ 批量执行工具调用     │
          │ 并行:互相独立      │
          │ 串行:有依赖关系    │
          └─────────┬──────────┘
                    ▼
          ┌────────────────────┐
          │ 结果追加到 messages │
          │ (tool role)        │
          └─────────┬──────────┘
                    ▼
               ┌────────────┐
               │ round++     │──→ 回到循环检查
               └────────────┘

核心代码骨架:

class AgentWorkflow:
    def __init__(self):
        self.llm = LLMProvider("claude-sonnet-4")
        self.tools = ToolRegistry()
        self.max_rounds = 10
    
    async def orchestrate(self, messages: list) -> str:
        """编排阶段:Agent 主循环"""
        for round in range(self.max_rounds):
            response = await self.llm.chat(
                messages=messages,
                tools=self.tools.list()
            )
            
            if not response.tool_calls:
                # 没有工具调用 → 返回最终回复,结束循环
                return response.text
            
            # 批量执行工具调用
            # 步骤 A:检查工具之间是否有数据依赖
            groups = self._group_independent_calls(response.tool_calls)
            
            for group in groups:
                # 同组内的工具互相独立,可以并行
                results = await asyncio.gather(*[
                    self._execute_tool(call) for call in group
                ])
                for call, result in zip(group, results):
                    messages.append({
                        "role": "tool",
                        "tool_call_id": call.id,
                        "content": result
                    })
                # 有依赖的组需要串行,等上一组结果
                # 下一轮 LLM 会看到上一组结果再决定后续调用
        
        return "我已经思考了足够多轮,暂时无法完成这个请求。"
    
    def _group_independent_calls(self, calls: list) -> list:
        """检查工具调用之间的依赖关系,分组执行"""
        # 简单实现:名字相同的工具之间可能有依赖
        # 实际中可以用 DAG 表示依赖关系
        return [[c] for c in calls]  # 默认串行

编排阶段最重要的设计决策是什么时候并行、什么时候串行

并行场景(工具之间无数据依赖):
  工具 Asearch_weather("北京")
  工具 Bsearch_news("AI")
  → 同时执行,各等各的结果

串行场景(工具之间有数据依赖):
  工具 Aget_contact("张三") → 返回 email
  工具 Bsend_email(email, ...)
  → 必须等 A 返回结果后才能调用 B

Workflow 引擎应该自动检测依赖关系,而不是让 LLM 去决策"要不要并行"。LLM 只需要输出该调什么工具,执行顺序是编排层的职责。

阶段三:记忆(Memory)——Workflow 的状态管理

Workflow 的每一轮交互都在改变状态。状态管理不好,上一轮做过的事下一轮就忘了。

短期记忆的管理是一个**"溢出 → 压缩"的 Workflow**:

短期记忆 Workflow:
                    ┌─────────────┐
                    │ 新的消息加入   │
                    │ messages[]   │
                    └──────┬──────┘
                           ▼
                    ┌──────────────┐
                    │ count_tokens()│
                    │ ≤ max_tokens? │
                    │  ↓        ↓  │
                    │ 是        否  │
                    └────┬─────────┘
                         │
                         ▼ 否
               ┌──────────────────────┐
               │ 压缩策略(逐级降级):  │
               │                       │
               │ Level 1:移除最早工具  │
               │ 调用的完整结果         │
               │ → 压缩为摘要          │
               │                       │
               │ Level 2:如果还不够    │
               │ 压缩最早的一轮对话     │
               │ 为 1 句话摘要         │
               │                       │
               │ Level 3:如果还不够    │
               │ 丢弃最早的非系统消息   │
               └──────────────────────┘
                         │
                         ▼
                    ┌──────────────┐
                    │ 继续 Workflow │
                    └──────────────┘

长期记忆的检索也是一个子 Workflow:

长期记忆检索 Workflow:
                    ┌─────────────┐
                    │ 用户新消息    │
                    └──────┬──────┘
                           ▼
                    ┌─────────────────┐
                    │ 需要检索历史?    │
                    │ 关键词匹配或     │
                    │ LLM 快速判断     │
                    │  ↓         ↓    │
                    │ 需要       不需要 │
                    └────┬────────────┘
                         │
                         ▼ 需要
               ┌────────────────────┐
               │ Embedding 检索     │
               │ 向量数据库 Top-5   │
               └─────────┬──────────┘
                         ▼
               ┌────────────────────┐
               │ 相关性重排(Rerank) │
               │ 取 Top-2 最相关的   │
               └─────────┬──────────┘
                         ▼
               ┌────────────────────┐
               │ 注入 System Prompt │
               │ "以下为对话历史摘要"│
               └────────────────────┘

这个子 Workflow 的关键是第一步的判断——不是每次都需要检索。加上关键词触发和 LLM 快速判断的双保险,避免不必要的检索。

阶段四:安全(Safety)——Workflow 的守卫层

安全层拦截那些"不应该被执行的操作"和"不应该被输出的内容"。

安全 Workflow:
                    ┌─────────────┐
                    │ Agent 输出   │
                    └──────┬──────┘
                           ▼
               ┌────────────────────────┐
               │ 高风险操作检测           │
               │ (发送、删除、修改、审批) │
               │  ↓                 ↓  │
               │ 高风险             低风险 │
               └────┬──────────────────┘
                    │
                    ▼ 高风险
               ┌────────────────────────┐
               │ 请求用户二次确认         │
               │ "您确认要发送这封邮件?" │
               │  ↓              ↓      │
               │ 确认             取消   │
               └────┬──────────────────┘
                    │                   │
                    │ 确认               ▼ 取消
                    ▼              ┌────────────┐
               ┌────────────┐      │ 取消操作    │
               │ 执行操作    │      │ 告知用户    │
               └─────┬──────┘      └────────────┘
                     ▼
               ┌────────────────────────┐
               │ 输出内容过滤             │
               │ ├─ PII 检测(身份证等) │
               │ ├─ 敏感内容检测          │
               │ └─ 格式校验             │
               └─────┬──────────────────┘
                     ▼
               ┌────────────────────────┐
               │ 审计日志持久化           │
               │ (完整 trace 写入数据库) │
               └────────────────────────┘

安全层是 Workflow 中最"传统"的部分——它不需要 LLM,全部是确定性的校验逻辑。但恰恰是这部分,决定了 Workflow 能不能上生产。

完整的 Workflow 追踪(Trace)

每一个请求经过 Workflow 的完整路径都应该被记录下来,用于调试和复盘:

Request ID: req_fullstack_001
  Phase 1 (Route):
    - Intent: "query"
    - Confidence: 0.92
    - Tools loaded: [search_kb, web_search]
    - Duration: 0.3s
  
  Phase 2 (Orchestrate):
    - Round 1:
      + LLM call: input 1520t  output 45t (tool_call: search_kb)
      + Tool: search_kb("Redisson 分布式锁", 3)  3 docs
      + Duration: 1.5s
    - Round 2:
      + LLM call: input 2100t  output 256t (final answer)
      + Duration: 1.8s
    - Total rounds: 2
  
  Phase 3 (Memory):
    - Short-term: saved (5120 tokens)
    - Long-term: saved new memory entry
  
  Phase 4 (Safety):
    - Risk check: low risk, no confirmation needed
    - Content filter: passed
    - Audit: logged
  
  Total Duration: 4.1s
  Total Cost: $0.0042

有了这样的 trace,调试和优化 Workflow 就不是靠猜了——可以精确定位哪个阶段、哪一步花的时间长或出错。

Workflow 设计原则

从这套系统里总结几条 Workflow 设计原则:

1. 每个阶段只做一件事

路由只做路由,编排只做循环,记忆只做状态管理,安全只做安全。如果一个阶段既要做路由又要做安全,逻辑耦合后改一个功能会影响另一个。

2. 分支越早越好

路由是 Workflow 中最靠前的分支点。越早做分支决策,后面的路径就越简单、工具列表越短、LLM 调用越便宜。

3. 循环要有上限

Agent Loop 必须设上限(10 轮),设超时(30s),设熔断机制。没有上限的循环就像没有 break 的 while 循环——出 bug 时无限调用 API,成本几分钟内就能追上来。

4. 状态必须可恢复

如果 Agent 在第五轮崩溃了,重启后应该能从上下文中恢复状态。这意味着每轮交互后的消息列表需要持久化(短期记忆存到 KV 或 DB),不能只靠内存。

5. 安全是 Workflow 的一等公民

安全层不是"最后加上的"——它从一开始就是 Workflow 的一个独立阶段。没有安全层的 Workflow 不能进入生产。

总结

全栈智能助手 = 路由 Workflow → 编排 Workflow → 记忆 Workflow → 安全 Workflow

每个 Workflow 内部有多个步骤,Workflow 之间有清晰的输入输出边界。这种设计让系统的每一层都可以独立测试、独立升级、独立降级。

去掉不必要的复杂性,从最简 Workflow 开始——路由 → 编排 → 输出。能力不够时再加记忆,再不够时加安全。Workflow 是应需而生,不是预先设计的天花板。

继续阅读

评论