Agentic 设计模式:写好指令、管好输出、拆好任务

前面两篇文章讲了 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 能在生产环境跑多久。