> Wise玩转AI
AI 不是用来玩的,是用来提升效率的;AI 不是玩具,是你的数字员工和助手。
> 在 `Stage 1`,我们构建了一个"导诊顾问",它能聊天,能给出建议。但真正的智能体,核心是"做事",而不是"回答问题"。要让它做事,就必须让它具备两项核心能力:规划(Planning) 和行动(Action)。
这就是 `Stage 2` 的核心:引入 Planner(规划器) 和 Tools(工具)。
> 没有 Planner,智能体只是在长篇大论,它无法将一个复杂目标拆解成可执行的步骤。
> 没有 Tools,智能体就没有"手"和"脚",它无法与真实世界交互,无法执行任何有意义的操作。
Stage 2是我们从"聊天机器人"迈向"数字员工"的关键一步,是整个智能体工程的基石。
1. 产品背景:从"顾问"到"助理"的进化
在 Stage 1 中,我们的导诊助手像一个"顾问",你问一句,它答一句,所有的知识和逻辑都固化在 Prompt 里。这在简单场景下可行,但暴露了三个核心问题:
-
逻辑僵化:一旦就诊流程变化,就需要去修改非常复杂的 Prompt,维护成本极高。
-
能力受限:它无法执行任何实际操作,比如查询医生排班、确认挂号费用等,因为它没有"手脚"(工具)。
-
黑盒决策:我们不清楚它给出建议的具体步骤,整个过程不可控,不可观测。
Stage 2 的目标就是解决这些问题。我们把导诊 Agent 的角色从"顾问"升级为"助理",它不仅能给建议,更能**"自动去做事"**。
本次升级的核心是:
-
引入 Planner(规划器):在执行任何任务前,Agent 会先生成一个清晰的、结构化的行动计划(Plan),明确"先做什么,后做什么"。
-
引入 Tools(工具):我们将核心业务能力(如"推荐科室"、"查询挂号流程")从 Prompt 中剥离,封装成独立的、可复用、可维护的 Python 函数(工具)。
通过这次改造,我们的智能体将首次具备"规划与执行"分离的架构,这是构建任何高级智能体的基础。
2. 技术背景与实现流程
2.1 技术升级:autogen 0.7 的 Planner 与 Tool Calling
本阶段我们将在 Stage 1 的基础上,重点利用 autogen 框架的以下核心能力:
-
多 Agent 协作:我们将引入两个角色的 Agent:
-
Planner Agent:一个专门负责任务规划的智能体,它接收用户需求,输出结构化的 JSON 行动计划。 -
Worker Agent:一个带有工具的执行者,它理解 Planner 的计划,并按步骤调用正确的工具来完成任务。 -
Tool Calling(工具调用):通过 `AgentTool` 将我们的 Python 函数(如 `recommend_department`)封装起来,让 `Worker Agent` 能够像调用 API 一样使用它们。
2.2 核心实现流程
本阶段的智能体工作流程可以用下面这张图清晰地展示:
graph TD
A[用户输入症状] --> B{Planner Agent};
B --"生成任务规划 (JSON)"--> C{Worker Agent};
C --"步骤1:分析症状"--> D[调用 recommend_department 工具];
D --"返回推荐科室"--> C;
C --"步骤2:查询流程"--> E[调用 registration_guide 工具];
E --"返回挂号流程"--> C;
C --"综合所有信息"--> F[生成最终导诊建议];
F --> G[流式输出给用户];
这个流程清晰地体现了"规划与执行分离"的思想:Planner 负责高层次的"怎么做",Worker 和 Tools 负责底层的"具体执行"。
3. 环境准备与运行
3.1 环境要求
-
Python 版本: `3.11`
-
核心依赖: `autogen-agentchat`, `autogen-ext[openai]`, `python-dotenv`
3.2 安装与配置
-
安装依赖:
pip install -U "autogen-agentchat>=0.7.0" "autogen-ext[openai]>=0.7.0" python-dotenv
-
配置环境变量:
在项目根目录创建 .env 文件,填入你的 DeepSeek API 密钥。模型我们将统一使用 deepseek-chat。
DEEPSEEK_API_KEY="你的_DeepSeek_API_密钥"
DEEPSEEK_MODEL="deepseek-chat"
DEEPSEEK_BASE_URL="https://api.deepseek.com"
LOG_LEVEL="INFO"
3.3 运行 Stage 2
执行以下命令,启动"会干活的"导诊 Agent:
python -m hospital_agent_course.stage2_agent_tools_planner.main
3.4 新功能使用示例
双参数工具的使用非常自然,Agent会自动从用户输入中提取年龄信息:
示例1:包含年龄信息的询问
你:我今年30岁,最近经常头痛,有时候还有点头晕,请问应该挂什么科?
Agent会自动调用:comprehensive_triage_recommendation("经常头痛头晕", 30)
示例2:不包含年龄信息的询问
你:最近咳嗽发热,需要挂哪个科室?
Agent会调用:comprehensive_triage_recommendation("咳嗽发热", None)
工具会自动使用35岁作为默认年龄进行推荐
示例3:儿童患者
你:我家小孩8岁,发热咳嗽,有点喘不过气,应该看什么科?
Agent会自动调用:comprehensive_triage_recommendation("发热咳嗽喘不过气", 8)
优先推荐儿科相关的科室
4. 预期结果展示
当你运行程序并输入症状后,你将看到与 Stage 1 完全不同的交互过程。
输入示例:
我这两天一直咳嗽,还有点低烧,需要挂哪个科室?
============================================================
Wise玩转AI · Stage 2:会干活的导诊智能体(Planner + Tools)
============================================================
请输入您的症状描述 (输入 "exit"、"quit" 或 "q" 退出): 我这两天一直咳嗽,还有点低烧,需要挂哪个科室?
[调试信息] 当前任务规划:
{
"goal": "根据患者症状推荐科室并给出挂号流程说明",
"steps": [
{
"id": 1,
"action": "分析症状,为患者推荐合适的科室",
"tool": "recommend_department",
"input": "患者的原始症状描述"
},
{
"id": 2,
"action": "根据推荐的科室,为患者提供详细的挂号流程",
"tool": "registration_guide",
"input": "上一步骤推荐的科室名称"
}
]
}
------------------------------------------------------------
[导诊 Agent]:
您好,根据您"咳嗽、低烧"的症状,我为您做出以下导诊建议:
【1】推荐科室
- **建议挂号科室**:呼吸内科、发热门诊。
- **说明**:这些科室专门处理与呼吸系统相关的疾病,如咳嗽、发烧等。
【2】挂号流程
- **科室**:呼吸内科 / 发热门诊
- **挂号流程**:
1. 前往医院门诊大厅的挂号窗口或使用自助挂号机。
2. 选择"呼吸内科"或直接前往"发热门诊"分诊台。
3. 告知工作人员您的症状,完成挂号。
4. 根据挂号单上的指引,前往对应诊区候诊。
【温馨提示】
- 这仅为教学示例,不构成医疗建议。
- 如果症状加重或出现呼吸困难,请立即前往医院急诊或拨打120。
------------------------------------------------------------
结果解析:
-
任务规划先行:在 Agent 回答之前,程序首先打印出了 `Planner` 生成的 JSON 任务计划。这清晰地展示了 Agent 的"思考过程",让整个决策过程变得"透明、可观测"。
-
工具调用驱动:最终的回答内容,是由 `Worker Agent` 先后调用 `recommend_department` 和 `registration_guide` 两个工具,并将结果组合、润色后生成的。这证明 Agent 真正具备了"干活"的能力。
-
结构化输出:回答内容条理清晰,分为"推荐科室"、"挂号流程"、"温馨提示"三部分,体验远超 `Stage 1`。
5. 关键代码解析
5.1 Planner Agent (`planner/task_planner.py`)
这是 `Stage 2` 的大脑。我们通过一个精心设计的 `_PLANNER_SYSTEM_MESSAGE` 来约束它的行为,让它只做一件事:生成 JSON 格式的行动计划。
-
角色定义:明确告诉模型,你是一个"医院导诊任务规划器"。
-
任务约束:要求它把任务拆解成 2-4 个步骤,并且每一步都必须包含 `id`, `action`, `tool`, `input` 四个字段。
-
输出格式约束:强制要求输出严格的 JSON,`tool` 字段只允许从我们提供的工具列表(`recommend_department`, `registration_guide`)中选择。
摘自 planner/task_planner.py
_PLANNER_SYSTEM_MESSAGE = """
你是"医院导诊任务规划器"(Triage Task Planner)。
你的唯一职责是:根据患者的症状描述,生成一个结构化的、可执行的 JSON 任务计划。
...
输出严格为 JSON 格式,不包含任何额外的解释或Markdown标记。
"""async def generate_plan(planner: AssistantAgent, user_input: str) -> Dict[str, Any]:
# ...
# 调用 planner.run() 获取规划
# ...
try:
plan = json.loads(raw_plan)
except (json.JSONDecodeError, TypeError):
# 如果 LLM 输出的不是合法 JSON,则使用预设的兜底计划
plan = _fallback_plan(user_input)
# ...
return plan
Wise玩转AI观点 :对于工程级 Agent,永远不要完全相信模型的输出。如此处的 `_fallback_plan`,当 Planner 输出的格式异常时,系统能自动切换到预设的兜底方案,保证了流程的健壮性。这是智能体工程化中"异常处理"和"容错设计"的体现。
5.2 Tools(`tools/` 目录)
这是 Stage 2 的"手和脚"。我们将业务逻辑封装成独立的 Python 函数。
5.2.1 基础工具
- `department_tools.py`:`recommend_department` 函数。它内部通过简单的关键词匹配规则,实现从症状到科室的推荐。这是一个纯函数(Pure Function),相同的输入永远对应相同的输出,非常易于测试和维护。
registration_tools.py:registration_guide函数。它根据科室名称,从一个字典中查找预设的挂号流程。
5.2.2 双参数综合导诊工具(🆕 新功能)
为了提供更精准的科室推荐,我们在 Stage 2 中引入了支持双参数输入的综合导诊工具:
-
comprehensive_triage_tools.py:comprehensive_triage_recommendation函数。这是本阶段的核心新增功能,支持两个参数输入:-
symptom_description: str- 患者的自然语言症状描述 -
patient_age: int = None- 患者年龄(可选,未提供时使用默认值35岁)
-
双参数工具的核心优势:
-
智能年龄分组:将患者分为儿童(0-12)、青少年(13-18)、青年(19-45)、中年(46-65)、老年(65+)等不同年龄段,针对不同年龄段的相同症状给出差异化推荐。
-
精准匹配策略:
-
首先应用年龄段特殊规则(如儿童的发热优先推荐儿科)
-
然后应用基础科室匹配规则
-
避免重复推荐,提供最优的3个科室选择
-
结构化输出:返回完整的导诊建议,包括:
患者信息:<年龄组>,<年龄>岁
推荐科室:
- <科室名称>
推荐理由:<具体理由> - <科室名称>
推荐理由:<具体理由>
就诊建议:
- 携带证件建议
- 病史准备建议
- 紧急情况处理建议
- <科室名称>
实际应用示例:
输入:我今年30岁,最近经常头痛,有时候还有点头晕
输出:推荐科室:神经内科(成人头痛头晕症状)
输入:我今年8岁,发热咳嗽,有点喘不过气
输出:推荐科室:儿科(儿童发热咳嗽症状)
5.2.3 工具注册与优先级
系统现在支持3个工具,按优先级排序:
# 摘自 tools/__init__.py
def get_function_map() -> Dict[str, Callable]:
"""返回工具名称到实现函数的映射。"""
function_map: Dict[str, Callable] = {
"comprehensive_triage_recommendation": comprehensive_triage_recommendation, # 🥇 主要推荐工具
"recommend_department": recommend_department, # 🥈 备选推荐工具
"registration_guide": registration_guide, # 🥉 挂号流程工具
}
return function_map
在Agent的系统消息中,我们明确标注了使用优先级:
1)comprehensive_triage_recommendation(...) 【主要推荐工具,支持症状+年龄双参数,推荐科室更精准】
2)recommend_department(...) 【备选工具,仅基于症状的单参数推荐】
3)registration_guide(...) 【挂号流程说明工具】
# 摘自 tools/__init__.py
def get_function_map() -> Dict[str, Callable]:
"""返回工具名称到实现函数的映射。"""
function_map: Dict[str, Callable] = {
"comprehensive_triage_recommendation": comprehensive_triage_recommendation, # 🥇 主要推荐工具
"recommend_department": recommend_department, # 🥈 备选推荐工具
"registration_guide": registration_guide, # 🥉 挂号流程工具
}
return function_map
def get_tools_schema() -> List[dict]:
"""基于函数签名构建 OpenAI Tools Schema 列表。"""
function_map = get_function_map()
schemas: List[dict] = []
for name, func in function_map.items():
if name == "comprehensive_triage_recommendation":
desc = "根据患者症状描述和年龄提供综合导诊建议,包括科室推荐、推荐理由和就诊建议。支持双参数输入(症状+年龄),年龄为可选参数,未提供时使用通用推荐。仅用于教学示例,不构成医疗建议。"
elif name == "recommend_department":
desc = "根据患者自然语言症状描述,推荐 1~2 个可能需要挂号的科室。仅用于教学示例,不构成医疗建议。"
elif name == "registration_guide":
desc = "根据科室名称返回挂号窗口、排队顺序等流程说明。仅用于教学示例,不构成真实流程承诺。"
else:
desc = "医院导诊相关工具函数。"
schema = ag_tools.get_function_schema(func, name=name, description=desc)
schemas.append(schema)
return schemas
Wise玩转AI观点 :工具必须是原子化的。每个工具只做一件小事。`recommend_department` 只负责推荐科室,`registration_guide` 只负责挂号流程。LLM 非常擅长组合这些小而美的原子工具来完成复杂任务,但很难操控一个大而全的"巨型工具"。
6. Stage 2 新功能对比与升级
6.1 功能对比矩阵
|-----------|--------------|------------------|------------------|
| 特性维度 | Stage 1 单智能体 | Stage 2 基础版 | Stage 2 双参数增强版 |
| 架构 | 单智能体 | Planner + Worker | Planner + Worker |
| 规划能力 | ❌ 无 | ✅ JSON任务规 | ✅ JSON任务规 |
| 工具调用 | ❌ 无 | ✅ 单参数工具 | ✅ 双参数+单参数工具 |
| 推荐精度 | 基于Prompt知识 | 基于症状关键词 | 基于症状+年龄智能匹配 |
| 个性化程度 | 低 | 中 | 高 |
| 年龄感知 | ❌ 无 | ❌ 无 | ✅ 智能年龄分组 |
| 容错能力 | 低 | 中 | 高 |
| 可观测性 | 黑盒 | 任务规划可见 | 任务规划+推荐逻辑可见 |
6.2 双参数工具的核心价值
- 精准度提升:结合年龄因素的推荐比单纯基于症状的推荐准确率显著提升
-
儿童"发热" → 优先推荐儿科
-
成人"发热" → 推荐呼吸内科/发热门诊
-
老年"胸闷" → 考虑心血管内科
- 用户体验优化:
-
自然语言输入:用户可以像平时说话一样提及年龄
-
智能提取:Agent自动从"我今年30岁"中提取年龄
-
容错处理:未提供年龄时使用合理默认值
- 工程化进步:
-
工具原子化:每个工具职责单一,易于测试维护
-
参数灵活性:支持可选参数,向下兼容
-
扩展性强:可以轻松添加更多参数(如性别、既往病史等)
6.3 实际应用场景
场景1:儿童就诊
用户:我家宝宝5岁,突然起皮疹很痒,怎么办?
系统:调用 comprehensive_triage_recommendation("起皮疹很痒", 5)
推荐:儿科、皮肤科
理由:儿童出现皮肤症状,优先考虑儿科,同时皮肤科也是合适选择
场景2:中年体检
用户:我45岁了,最近体检发现血压有点高,需要挂什么科?
系统:调用 comprehensive_triage_recommendation("血压高", 45)
推荐:心血管内科、内科
理由:中年高血压患者,建议心血管内科专科就诊
场景3:老年症状
用户:我爷爷70岁,经常头晕走路不稳,要看哪个科?
系统:调用 comprehensive_triage_recommendation("头晕走路不稳", 70)
推荐:神经内科、老年病科
理由:老年患者头晕症状,需要考虑神经内科和老年病科
7. 总结与下一步
通过 Stage 2 的改造,特别是双参数工具的引入,我们的导诊助手已经从一个简单的"聊天机器人"进化成了一个具备"**规划-执行**"能力和"**个性化推荐**"的高级智能体。
7.1 Stage 2 核心成就
-
✅ 架构升级:实现了"规划-执行"分离的企业级智能体架构
-
✅ 工具化改造:将业务逻辑从Prompt中解耦,提升可维护性
-
✅ 个性化突破:引入双参数工具,实现基于年龄的精准推荐
-
✅ 工程化完善:具备容错、可观测、可扩展等特性
7.2 下一步展望
下一步, Stage 3我们将挑战一个更有趣的问题:如何让 Agent 拥有记忆?
基于 Stage 2 的坚实基础,Stage 3 将在以下方向继续演进:
-
记忆能力:让Agent记住患者的就诊历史和偏好
-
知识库集成:接入真实的医学知识库和医院信息
-
多轮对话:支持连续的上下文对话,而非单次问答
这些功能将使我们的智能体从"工具使用者"进化为"知识工作者"。