Agent 系列02|Agent 架构全景图

02|Agent 架构全景图

Agent 工程实战系列 · 设计篇 ·

一、问题场景:50 行代码跑通之后,需求接踵而至

01 篇的最小 Agent 跑起来了。紧接着,需求一个接一个提出来:

  • "能不能记住用户上次说过什么?"
  • "它别乱退款,超过 200 元必须人工确认。"
  • "我怎么知道这周的版本比上周更好,还是更差?"
  • "昨天有个回复错了,能不能查到它当时是怎么想的?"

多数团队的做法是一个问题补一个补丁。三个月后,agent.py 膨胀到三千行:提示词、重试、日志、权限判断、记忆读写全缠在同一个循环里。症状很典型:改一处坏三处,没人能说清"这段逻辑应该放哪",出了问题不知道该找谁。

解决办法不是更小心地写,而是先有一张架构地图:把 Agent 拆成职责清晰的模块,每个模块知道自己管什么、不管什么。这就是本篇要画的全景图,后面 26 篇都会在这张图上找到自己的位置。

二、核心概念:八大模块一张图

2.1 参考架构图

这张图的重要三点:

  1. 纵向是一条请求的路径:从用户进来,经编排驱动,由模型决策,通过工具落地,再回到用户。
  2. 护栏有三道关口:入口(输入)、动作(工具调用前)、出口(输出)。写操作的风险主要靠中间这一道。
  3. 评估、可观测、护栏是横切的:它们不属于某一层,而是要在每一层留出接入点。后补成本最高。

2.2 用公司来类比

模块 公司里的对应物
模型 + 上下文 员工的大脑,加上他此刻手边的资料
工具 业务系统和对应的操作权限
记忆 笔记本和档案柜
规划 项目计划表
编排 项目经理和流程制度
护栏 合规与内控
评估 绩效考核
可观测 监控录像和工单系统

2.3 输出 ①:模块职责与边界表

"边界"一列比"职责"更重要,大多数架构问题都出在模块越界。

模块 核心职责 边界:不归它管
模型 + 上下文 理解意图,决定下一步(调用哪个工具或直接回答),生成内容;装配提示词、历史、检索结果 不负责执行动作,不保存长期状态,不承担权限判断
工具 把外部能力封装成可调用、可校验、可审计的动作 不做业务决策;不信任模型传来的参数,一律校验
记忆 跨轮、跨会话保存和取回信息(会话状态、用户偏好、历史案例) 不决定"做什么";不能无限堆进上下文
规划 把目标拆成步骤,跟踪进度,偏离时重规划 不执行动作;简单任务可以没有它
编排 驱动循环与流程:状态流转、预算、超时、终止、重试、人工介入点 不理解语义,不写业务提示词
护栏 在输入、动作、输出三个关口拦截、改写或升级 不替代最小权限,不是唯一防线
评估 用测试集和指标判断"好不好、改没改坏" 不在线上主流程里阻塞请求;不等于线上监控
可观测 记录每次运行的过程:Trace、日志、指标、成本 只记录不判断,也不能影响主流程运行

2.4 这些模块在 SupportPilot 里长什么样

模块 SupportPilot 中的落地 详见
模型 + 上下文 客服角色提示词;订单与政策片段按需装配 05、12
工具 get_order、get_logistics、create_reship(需审批) 06、13、14
记忆 本次工单状态;用户历史工单摘要 07、16
规划 包裹丢失:查订单 → 查物流 → 判定异常 → 给出建议 08、15
编排 步数与时间上限、审批中断、checkpoint 08、11、15
护栏 订单号格式校验;补发和退款需确认;输出脱敏 23、24、26
评估 100 条历史工单做回归测试 18
可观测 每次工单一条 Trace;成本与延迟看板 19

2.5 其他篇目在图中的位置

  • RAG(09、16):既是工具层里的一个"检索工具",也是记忆里的知识库。
  • MCP(14):工具层接入外部能力的协议。
  • 多 Agent(10、17):在编排之上再加一层编排,相当于把整张图复制多份,由编排者调度。
  • 人机协同(11):编排里的中断点,加上出口处的交互设计。
  • 安全(22--27):横切的护栏、权限与审计,贯穿所有层。
  • 性能与成本(20) 、部署(21):前者跨模块优化,后者是整张图外面的运行环境。

三、一次请求如何流过这些模块

用户说:"我的包裹好像丢了,订单 A1001。"

步 发生了什么 涉及模块
1 检查输入长度,识别订单号格式,过滤明显恶意内容 入口护栏
2 创建本次运行,设置预算(最多 8 步、60 秒),加载状态 编排
3 取出该用户最近的工单摘要 记忆
4 装配提示词 + 摘要 + 用户消息,模型决定先调用 get_order 模型 + 上下文
5 校验参数;只读工具直接放行并执行 动作护栏、工具
6 结果回到上下文,模型决定再查物流,进入下一轮循环 编排、模型、工具
7 发现物流停滞超过 5 天,判定异常,更新子目标进度 规划
8 需要补发,属于写操作,触发人工审批中断,保存 checkpoint 动作护栏、编排
9 审批通过后执行补发,输出脱敏,回复用户 工具、出口护栏

全程,可观测记录每一步的 Trace;评估则在上线前,用历史工单回放这条链路,验证没有改坏。

注意第 8 步:模型提出的是"建议补发",决定能不能执行的,是动作护栏和人工审批,不是模型自己。 这是后面所有安全设计的基础。

四、设计取舍

4.1 三条分层原则

  1. 决策与执行分离。 模型负责提议,代码负责校验与执行。模型的输出是"建议",不是"命令"。
  2. 控制流归编排,语义归模型。 预算、终止、审批这些必须保证的事,写在代码里,不要写在提示词里指望模型自觉。
  3. 横切关注点独立。 护栏、可观测、评估做成可插拔的拦截点,不要散落在业务代码里。

4.2 模块按复杂度逐级实现

八个模块不是一开始就要全上。按风险和复杂度分档:

档位 典型场景 需要的模块
L1 原型 内部小工具、01 篇的 50 行 模型、工具、最简编排、日志
L2 面向用户的单 Agent 对外客服问答 加:会话记忆、基础护栏、小测试集、结构化 Trace
L3 有写操作、涉及资金或隐私 退款、补发、改订单 加:动作分级、人工审批、完整审计、权限隔离、红队测试
L4 多 Agent 或长流程 跨系统的复杂工单 加:规划、checkpoint、跨 Agent 协议、成本治理

模块可以暂时空缺,但要"心中有数":在设计文档里写清楚为什么现在不需要。评估和可观测,我们建议 L2 以上都不要省略。

4.3 常见做法与后果

做法 后果
所有逻辑塞进一个超长 System Prompt 改一句崩一片,无法单独测试
把"退款不得超过 200 元"写在提示词里 模型可能违反。应该在工具层或动作护栏里校验
用 print 代替 Trace 出错后无法还原当时的上下文和决策
最后才补评估 没有基线,无法判断每次改动是变好还是变坏

不同框架在这张图上覆盖的面不同:有的主要提供编排,有的把循环、护栏、追踪一起打包。选型时用这张图逐项核对,看它覆盖哪几块、哪几块要自己补。具体方法在第 12 篇。

五、动手实现:把 01 篇的代码按模块分层

本篇不新增功能,只做一件事:把 01 篇的代码按模块拆开,给每个模块留出位置和接口。这是 SupportPilot 后续所有篇目的脚手架。

复制代码
supportpilot/
├── agent/
│   ├── orchestrator.py   # 编排:循环、预算、终止、审批中断
│   ├── model.py          # 模型调用与上下文装配
│   ├── tools/            # 工具层:每个工具一个文件 + 注册表
│   │   ├── registry.py
│   │   └── order.py
│   ├── memory.py         # 记忆(先放接口)
│   ├── planner.py        # 规划(先留空)
│   ├── guardrails.py     # 入口 / 动作 / 出口三个拦截点
│   └── tracing.py        # 可观测:记录每一步
├── evals/                # 评估:测试集与脚本
│   └── cases.jsonl
├── tests/
└── README.md

核心是编排文件,它只管流程,不管业务语义。下面是骨架,用来展示模块之间的调用关系,各函数的完整实现在后续篇目补齐:

python 复制代码
# agent/orchestrator.py:只管流程,不懂业务语义
import time
from dataclasses import dataclass
from . import guardrails, tracing
from .model import call_model, text_of, tool_calls
from .tools.registry import execute, schemas
from .memory import load_context, save_context

@dataclass
class Budget:
    max_steps: int = 8
    max_seconds: int = 60

def run(user_msg: str, user_id: str, budget: Budget = Budget()) -> str:
    trace = tracing.start(user_id)
    ok, reason = guardrails.check_input(user_msg)                    # 入口护栏
    if not ok:
        return trace.finish(f'无法处理:{reason}')
    messages = load_context(user_id) + [{'role': 'user', 'content': user_msg}]
    deadline = time.time() + budget.max_seconds
    
    for step in range(budget.max_steps):
        if time.time() > deadline:
            break
        msg = call_model(messages, tools=schemas(), trace=trace)     # 模型 + 上下文
        messages.append(msg)                                         # 整条消息原样放回
        calls = tool_calls(msg)
        if not calls:                                                # 模型认为可以收尾
            save_context(user_id, messages)
            return trace.finish(guardrails.check_output(text_of(msg)))    # 出口护栏
        
        for c in calls:
            verdict = guardrails.check_action(c)                     # 动作护栏
            if verdict == 'need_approval':
                return trace.finish('已提交人工审批') #第 11、15 篇:保存 checkpoint 并中断
            messages.append(execute(c, allowed=(verdict == 'allow'), trace=trace))   # 工具层
    return trace.finish('达到预算上限,已转人工处理')

对照 01 篇,有四处变化:

  • 预算从一个常量变成 Budget,增加了时间上限。
  • 工具调用前多了一道 check_action,被拒绝的动作不会被执行,而是把"不被允许"作为结果回传给模型。
  • 输入、输出各加了一道护栏。
  • 每一步都带着 trace,为第 19 篇的回放留好了接入点。

其余文件先放接口和空实现,例如 guardrails.py 的三个函数默认全部放行,tracing.py 先打印日志。接口稳定了,后面逐篇往里填内容,不需要再改编排。

六、踩坑清单:典型失败模式

失败模式 现象 预防(对应篇目)
大范围循环 一个文件几千行,改一处坏三处 按本篇模块拆分,编排只管流程(12)
模块越界 工具里写提示词,提示词里写权限 对照职责表的"边界"列做评审(03、06)
过早抽象 一上来搭全套八大模块,原型迟迟跑不起来 按档位逐级点亮,空缺要写理由(03)
护栏只在入口 输入过滤做了,写操作却没有动作护栏 三道关口都要有(23、24)
评估与可观测空缺 出了问题只能靠猜,改动无法验证 L2 起就要建,别等上线后补(18、19)
把多 Agent 当架构起点 还没跑通单 Agent 就拆成五个角色 先单 Agent,有证据再拆(04、10)

七、重点与自测

本篇重点

  1. 参考架构图(2.1 节):贴在项目 Wiki 上,评审时对着它提问。
  2. 模块职责与边界表(2.3 节):写设计文档和做代码评审时的依据。
  3. 项目骨架(第五节):可以直接作为 SupportPilot 的起始目录。
  4. 练习:给你手头的一个项目填下面这张"模块点亮表",找出缺失和越界。
模块 已有 / 缺失 / 有意缺席 现在放在哪个文件 越界问题
模型 + 上下文
工具
记忆
规划
编排
护栏
评估
可观测

自测 5 题

  1. "退款金额不得超过 200 元"这条规则,应该放在哪里?
  2. 想知道改了提示词之后,Agent 是变好了还是变坏了,应该靠哪个模块?
  3. 用户反馈昨天有一次回复是错的,怎么定位当时发生了什么?
  4. 一个固定 5 步、每步调用一次模型的流程,需要"规划"模块吗?
  5. "最多执行 8 步"这条限制,写在系统提示词里和写在编排里,有什么区别?

参考答案

  1. 放在工具层或动作护栏,由代码校验。提示词只做引导,不能当作保证。
  2. 评估模块,用固定测试集做前后对比。可观测记录的是单次运行,回答不了"整体变好还是变坏"。
  3. 靠可观测的 Trace 回放(第 19 篇),还原当时的上下文、工具调用和每一步决策。
  4. 不需要。规划用于运行时动态拆解目标;固定流程由编排里的流程定义承担。
  5. 写在提示词里,模型有概率不遵守;写在编排里,由代码强制执行。控制流归编排。

八、下一篇预告

03|从业务需求到 Agent 设计 :架构图回答的是"有哪些部件",设计文档要回答的是"做什么、做到什么程度、出错了怎么办"。下一篇我们学习任务拆解、成功标准、自治级别、ROI 估算,以及"失败模式先行"和状态机设计,并设计一份Agent 设计文档模板,填好 SupportPilot 版。

相关推荐
冬奇Lab2 小时前
开源项目第236期:PentAGI — 全自主渗透测试多智能体系统,自带 20+ 专业工具箱
人工智能·开源·资讯
YangYang9YangYan2 小时前
2027供应链计划校招:数学与应用数学专业如何把预测模型讲成业务决策?
人工智能·sql·算法
jsjzsl22 小时前
递归自改进系统(RSI)的本质是自发对称破缺:智能爆炸有界—— AI研发切记盲目投入
人工智能·python·算法
天远数科2 小时前
零信任架构实战:基于天远贷前风险报告构建自动化消金信贷风控与多头共债排查网关
运维·人工智能·架构·自动化
冬奇Lab3 小时前
LLM 驱动的自动化测试系列(12):像素 diff 之外,谁真的在让 LLM「看懂」UI 变化
人工智能·测试
派小心.3 小时前
小程序分享链路中来源参数的传递校验
大数据·前端·数据库·人工智能·小程序·数据分析
AI创界者3 小时前
摆脱云端API限制!Index-Translate 本地化部署整合包与实战:6GB显存畅跑150种语言,支持超长文本、字幕与API调用
人工智能·aigc
一条大祥脚3 小时前
【CS336】lecture7 多卡|通信架构|集合通信|NCCL|通信原语|torch.distributed
分布式·架构·nccl·多机多卡·集合通信