前面两篇文章讲了 Agent 的本质(tool calling 循环)和框架的四层抽象(Client → Agent → Tools → Session)。这些是"能用"的基础。
但这只是起点。从"能用"到"好用",差的是设计。
本文讲解三种最基础的 Agentic 设计模式。每种模式解决一个具体问题,配有代码对照——左边是原生 SDK 手写,右边是 qwen-agent 框架复现。
📦 相关链接
🎯 三种模式解决什么问题
flowchart LR
A[用户需求] --> B[模式1:<br/>清晰指令]
B --> C[模式2:<br/>结构化输出]
C --> D[模式3:<br/>单一职责]
D --> E[可用的 Agent 系统]
B -.- B1["问题: Agent 行为不稳定"]
C -.- C1["问题: 下游代码无法消费自由文本"]
D -.- D1["问题: 单个 Agent 太复杂难维护"]模式 1:清晰的 Agent 指令
问题
给 Agent 写指令时,最常见的错误是写这种:
"你是一个有帮助的旅行助手,帮用户规划旅行。"
这种指令太模糊。LLM 会自行发挥,结果每次行为不一致——有时候积极查工具,有时候凭训练数据编造信息。
解法:指令三要素
好的 system prompt 必须定义三件事:
| 要素 | 要回答的问题 | 示例 |
|---|---|---|
| 角色 | 我是谁? | "你是一位名叫 Alex 的奢华旅行礼宾专员" |
| 流程 | 我该按什么步骤做事? | "先查2-3个目的地 → 筛选 → 推荐" |
| 约束 | 我不能做什么? | "不要凭空编造,必须基于工具返回的数据" |
关键技巧:给模型示范工作流
对于 qwen-flash 这类小模型,抽象的角色描述不如具体的操作示范。对比一下:
❌ 模糊指令:
"根据用户偏好推荐目的地,使用工具查询信息。"
✅ 带示范的指令:
推荐流程: 步骤1:查询 get_destination_details("巴黎") 步骤2:查询 get_destination_details("东京") 步骤3:查询 get_destination_details("巴塞罗那") 步骤4:基于返回结果筛选推荐 如果目的地不可用(返回"暂无信息"),必须查询其他目的地
这就是 Few-Shot Prompting 的核心思想——不给模型讲道理,给模型看"怎么做一遍"。
工程实践
在原生 SDK 中,指令就是一个精心构造的字符串:
INSTRUCTIONS_TRAVEL_CONCIERGE: 你是一位名叫 Alex 的奢华旅行礼宾专员。你必须使用 get_destination_details 工具查询目的地信息;如果查询的目的地不可用,你应该查询其他目的地;不要凭空编造信息,始终基于工具返回的真实数据做推荐;推荐的流程:先查询2-3个可能的目的地 → 根据返回结果筛选 → 推荐最佳选择。可用目的地包括巴黎、东京、巴塞罗那、开普敦。回复使用中文,保持温暖专业的语气。
在 qwen-agent 中,映射到 Assistant(system_message=...)——同样的指令内容,只是传递方式不同。
指令写得好不好,看什么
跑同样的问题三次,观察:
- Agent 是否每次都先调工具再回答?(不是直接编)
- 遇到不可用目的地时,是否自动查下一个?(不是放弃)
- 推荐的语气和风格是否一致?(不是忽冷忽热)
模式 2:结构化输出
问题
自由文本适合人读,不适合代码读。如果下游系统需要用 Agent 的输出做决策(比如:自动预订、生成报表、触发工作流),自由文本就是灾难——你需要写正则、做 NER、处理各种意外格式。
解法:Pydantic + JSON 约束
用一个 DestinationRecommendation 的 Pydantic 模型来展示核心思路:
DestinationRecommendation
destination: str — 目的地名称available: bool — 是否可用best_season: str — 最佳旅行季节highlights: list[str] — 亮点列表estimated_budget_usd: int — 预算估算(美元)
TravelRecommendations
recommendations: list[DestinationRecommendation] — 推荐列表personalized_note: str — 个性化备注
然后在 system prompt 中指定输出格式:
最终回复必须是严格的 JSON,不要包含 markdown 代码块标记。格式示例:
{"recommendations": [{"destination": "...", "available": true, "best_season": "...", "highlights": ["..."], "estimated_budget_usd": 2200}], "personalized_note": "..."}
整个流程
flowchart TD
A[System Prompt:<br/>角色 + 流程 + JSON格式要求] --> B[LLM 调用工具]
B --> C[获取目的地数据]
C --> D[LLM 按 JSON schema 输出]
D --> E{Pydantic 验证}
E -->|成功| F[返回类型安全的对象]
E -->|失败| G[捕获异常,<br/>回退处理]关键设计决策:
- 在 prompt 中指定格式,而不是依赖框架的
response_format参数。这让你能控制格式的细粒度,也方便跨框架迁移 - Pydantic 做最后一道验证。LLM 偶尔会输出格式偏差(多一个逗号、少一个引号),
model_validate_json()会立刻报错,你可以在 catch 块里做 fallback
为什么不用框架的 response_format
qwen-agent 的 Assistant 没有 response_format 参数。MAF 和 LangChain 有,但每个框架的 API 不同。把格式要求写在 prompt 里,代码在任何框架间都能复现——改框架不改 prompt。
模式 3:单一职责 Agent
问题
一个 Agent 做所有事 → prompt 越来越长 → 行为越来越不可控 → 调试越来越难。
这和软件工程的"上帝对象"反模式一样。
解法
把复杂任务拆给两个(或多个)专注的 Agent,每个只做一件事:
flowchart LR
A["🙋 用户:<br/>我想度一周的文化美食之旅"] --> B["📋 DestinationExpert<br/>只管: 研究推荐目的地<br/>工具: get_destination_details"]
B -->|"推荐结果"| C["✈️ LogisticsPlanner<br/>只管: 行程规划<br/>工具: 无(纯推理)"]
C --> D["📄 完整旅行方案"]拆分的核心原则是"关注点分离"——不是按功能模块拆,而是按职责边界拆:
| Agent | 职责 | 工具 | 不知道的事 |
|---|---|---|---|
| DestinationExpert | 目的地研究和推荐 | get_destination_details | 航班、酒店、行程 |
| LogisticsPlanner | 行程规划和后勤 | 无 | 目的地的原始数据 |
每个 Agent 的 system prompt 明确写了"不要讨论 XX——那由另一个 agent 负责"。这不是废话——这是在防止 Agent 越界。LLM 天然有"多说一点"的倾向,必须用否定句约束。
编排方式
sequenceDiagram
participant User
participant DestinationExpert
participant LogisticsPlanner
User->>DestinationExpert: 我想度一周文化美食之旅,预算$2500
DestinationExpert->>DestinationExpert: 调用 get_destination_details
DestinationExpert-->>User: 推荐:东京(可用,$2500/周)
User->>LogisticsPlanner: 基于推荐,规划一周行程
LogisticsPlanner-->>User: Day1: 浅草寺 + 筑地市场<br/>Day2: 明治神宫 + 原宿...在原生 SDK 中,编排就是 Python 函数顺序调用。在 qwen-agent 中,同样是函数顺序调用——Python 本身就是编排层,不需要 WorkflowBuilder。
💡 三种模式怎么组合
三种模式不是互斥的,而是叠加的:
flowchart TD
subgraph "单一职责 Agent 1"
A1[清晰指令] --> A2[结构化输出]
end
subgraph "单一职责 Agent 2"
B1[清晰指令] --> B2[自由文本输出]
end
A2 -->|"结构化数据"| B1
B2 --> C[最终结果]- Agent 1(DestinationExpert):清晰指令 + 结构化输出
- Agent 2(LogisticsPlanner):清晰指令 + 自由文本输出
- 两者通过 Python 函数串联,Agent 1 的输出作为 Agent 2 的输入
这是实际项目中最常见的组合方式:有数据产出的 Agent 用结构化输出,有文字产出的 Agent 用自由文本。
🚀 运行
cd 03-agentic-design-patterns/code_samples
# 原生版
python 03-python-agent-framework.py
# 框架版
python 03-qwen-agent-framework.py🔮 下一篇
有了"怎么设计一个 Agent"和"怎么设计多个 Agent",下一篇进入工具使用的核心——多工具组合和工具审批模式:当 Agent 同时有 3 个以上的工具时,LLM 怎么编排调用顺序?副作用操作(预订、扣款)怎么加人工审批?
✍️ 结语
三种模式的核心思想一句话概括:
- 指令:不给模型发挥空间,给模型示范步骤
- 输出:不让下游猜格式,用 Schema 做契约
- 职责:不让一个 Agent 干所有事,按边界拆分
框架决定你能多快写出第一个 Agent。设计模式决定你的 Agent 能在生产环境跑多久。