1754 字
约 5 分钟
9
全流程智能体
2026-10-13
无标签
系列:ToyAgent 智能体知识库 18/26
全流程智能体:把 17 块积木装成一台机器
一句话核心:守卫 → 记忆 → ReAct 循环 → 工具 → 保险丝 → 输出校验 → 回写记忆——零件是前面拆开讲的,这里装回同一台机器;骨架(chat 方法签名)永远不变,变厚的只是策略层。
为什么需要
前面十几章是教学拆解:每次只让你看清一个零件——守卫一章、记忆一章、循环一章。但真实系统里这些零件是同时在线的:
- 守卫在模型之前拦一道(省钱的:不合规请求一次模型调用都不花);
- 记忆在上下文两侧(读时垫进去,答完写回去);
- 循环在中间转起来;
- 保险丝贴着循环盯预算。
缺一个全流程视角,你就只会造零件,不会装机。全流程场景的价值不在发明新东西,而在组装顺序:守卫在最前、记忆在两侧、循环在核心、保险丝贴着循环。
核心机制
生命周期七环节
用户输入
│
▼
① 输入守卫 ── 长度/敏感词拦截(不进模型,不烧钱)
▼
② 读取记忆 ── 历史 + 用户画像,垫进上下文
▼
③ 组装上下文 ── system(角色+画像) + 历史 + 本轮输入
▼
④ ReAct 循环 ── Thought → Action → Tool → Observation ↻(≤ MAX_STEPS 步)
▼
⑤ 保险丝 ──── 超出步数预算,熔断返回
▼
⑥ 输出校验 ── 敏感词替换 + 画像兜底问答
▼
⑦ 回写记忆 ── 本轮对话入档;超阈值触发上下文压缩
sequenceDiagram
participant U as 用户
participant A as Agent
participant G as 守卫
participant M as 记忆
participant L as 大模型
participant T as 工具集
U->>A: 我叫王小明帮我查北京天气
A->>G: ① 输入守卫检查
G-->>A: 放行
A->>M: ② 读历史与画像并提取 profile
M-->>A: 历史 + 画像
A->>L: ③④ 组装上下文并发起推理
L-->>A: Thought 需要天气数据
A->>T: Action get_weather 北京
T-->>A: Observation 晴 12 到 22 度
A->>L: 续步携带观察结果
L-->>A: final 最终回答
A->>A: ⑤ 步数 2/8 未触发保险丝
A->>A: ⑥ 输出校验无敏感词
A->>M: ⑦ 本轮对话回写并判断压缩
A-->>U: 王小明你好 北京明天晴
五参与者时序全景:守卫先拦一道,记忆在读写两侧,模型与工具在循环里往返,保险丝与输出校验收尾——七环节在生产 trace 面板里正是这条链路。
主干代码
public String chat(String input) {
// ① 输入守卫:长度与敏感意图,不通过直接返回,模型预算为零
if (input.length() > MAX_INPUT_LEN) return "输入过长,请精简后重试";
if (SENSITIVE.matcher(input).find()) return "该请求已被输入守卫拦截";
// ② 记忆读取 + 画像提取(「我叫 XX」→ 存档案)
extractProfile(input);
// ③ 组装上下文:系统提示词(含画像)+ 历史 + 本轮输入
List<Message> messages = new ArrayList<>();
messages.add(Message.system(SYSTEM_PROMPT + userPortrait()));
messages.addAll(history);
messages.add(Message.user(input));
// ④⑤ ReAct 循环 + 保险丝(朴素骨架原样复用)
String answer = runReAct(messages);
// ⑥ 输出校验 + 画像兜底(「我叫什么」不问模型,查档案)
answer = outputGuard(answer, input);
// ⑦ 回写记忆,超阈值触发上下文压缩
history.add(Message.user(input));
history.add(Message.assistant(answer));
if (history.size() > COMPRESS_THRESHOLD) compressHistory();
return answer;
}
注意 ① 和 ⑥:守卫不只管输入,也管输出。模型是不可靠的(可能被注入、可能胡说),所以工程上它被夹在两道校验中间——输入侧拦恶意请求,输出侧拦违规内容。这就是"模型聪明,运行时可靠"的落地形态。
画像兜底:容易被忽略的细节
用户问"我叫什么名字"时,这条信息可能已经在上下文压缩时被裁掉了,直接问模型必然失忆。正确做法是画像单独持久化,问答时先查档案:
if (input.contains("我叫什么")) {
String name = profile.get("名字");
if (name != null) return "你叫 " + name + "(来自画像记忆,不依赖模型)";
}
这就是记忆要分两层的原因:对话历史(易失、会压缩)与用户画像(持久、结构化)。生产记忆组件(mem0、LangGraph Store)都遵循这个分层。
举例
一次完整 chat() 的五参与者时序(用户 / Agent / 记忆 / 大模型 / 工具集):
用户:我叫王小明,帮我查一下北京明天的天气
① 输入守卫:长度 OK,敏感词未命中 → 放行(模型预算尚未消耗)
② 记忆读取:history=空;画像提取:profile["名字"]="王小明"
③ 上下文组装:system("你是助手。" + 画像) + 历史 + 本轮输入
④ ReAct 循环:
Thought: 需要天气数据 → Action: get_weather(city=北京)
[Agent → 工具集 → Agent] Observation: 晴,12~22°C
⑤ 步数 2/8,未触发保险丝
⑥ 输出校验:无敏感词
⑦ 回写记忆:本轮两条入档;history=2 < 阈值,不压缩
Agent:王小明你好,北京明天晴,12~22°C。
─── 若干轮后(历史已压缩,画像还在)───
用户:你还记得我叫什么吗?
⑥ 画像兜底:命中「我叫什么」→ 直接查档案,不问模型
Agent:你叫王小明(来自画像记忆,不依赖模型)——即使对话历史早已被压缩。
这个时序对应生产框架的 trace 面板:守卫是 Agent 的自环(A->>A),记忆读写是 Agent 与存储的往返,Thought 从模型流回 Agent,Action→Observation 是与工具集的往返。所谓可观测面板,只是把同样的信息可视化了。
教学版 vs 生产版
| 环节 | 教学版 | 生产版差距 |
|---|---|---|
| 流式输出 | chat() 返回 String |
SSE 流式,边生成边吐字 |
| 并发会话 | 记忆是实例字段 | 按 sessionId 隔离 + 持久化 |
| 工具 | switch 执行器 | MCP 网关 / 插件市场,带超时重试 |
| 上下文预算 | 按条数压缩 | 按 Token 计费裁剪 + 重要度打分 |
| 可观测性 | trace 内存列表 | 结构化日志 + trace 面板 + 告警 |
| 评测回归 | 无 | 用例集 + LLM-as-Judge,改提示词必须跑回归 |
生产化路径
- 先把
chat()的七环节顺序刻进肌肉记忆——任何生产 Agent 框架的请求处理链都能映射回这七步。 - 优先补流式与并发会话:这两个不影响架构,但直接决定产品体验。
- 补评测回归再调提示词:没有用例集的提示词优化等于盲改。
- 进阶方向:RAG、CLI Agent、GUI Agent,以及工业级脚手架源码里这七环节的对应实现。
评论
0 条
还没有评论,先写一条吧。