1. 从大语言模型到垂直领域智能体:为什么“套壳对话”远远不够
大语言模型(LLM)这两年的热度不用我多说,从写文案、写代码到做翻译、做总结,几乎每个团队都在试。但如果你真的在医疗、金融、法律、工业这些垂直领域落地过,就会发现一个很尴尬的现实:能聊天的大模型,和能干活、能决策、能扛责任的智能体,中间隔着一整套工程体系。标题里说的“30 个智能体”,本质上不是让你去训练 30 个模型,而是让你围绕不同业务场景,把同一个或几个基座模型,包装成具备自主决策能力的垂直领域 Agent。
我先把这个概念说透。所谓智能体(Agent),在工程语境下,通常包含四个核心部件:感知(Perception)、规划(Planning)、记忆(Memory)、行动(Action)。大语言模型本身只解决了“理解和生成语言”这一层,它既没有长期记忆,也不能主动调用外部工具,更不会在失败后自己重试。你直接拿一个聊天窗口去问“帮我查一下这个客户最近的交易流水并判断是否有洗钱风险”,它要么编一个答案,要么告诉你“我无法访问实时数据”。这就是为什么必须构建智能体——智能体是 LLM 的“手脚和大脑皮层”,把模型的推理能力接到真实业务系统上。
那为什么是“30 个”?这个数字不是拍脑袋来的。我自己的经验是,一个中等规模的垂直领域(比如消费金融风控),从贷前审核、贷中监控到贷后催收,能拆出 8 到 12 个独立决策节点;医疗领域从分诊、问诊、辅助诊断到用药审核、随访管理,也能拆出 10 个以上。30 个智能体,基本覆盖了一个行业从入口到出口的完整链路。而且每个智能体的职责必须足够窄——窄到可以用一句话说清楚它的输入、输出和成功标准。比如“医疗分诊智能体”的职责就是:接收患者主诉文本,输出科室推荐和紧急程度分级,准确率要求 90% 以上,响应时间小于 2 秒。如果你把职责写成“帮医生看病”,那这个智能体永远做不好。
这篇文章适合谁看?如果你是 AI 工程师、算法工程师、技术负责人,正在考虑把 LLM 从 Demo 推进到生产环境,那这篇内容就是给你写的。我会从整体设计思路讲起,然后拆解核心细节、实操步骤、常见坑,最后给出一套可以直接抄作业的智能体构建框架。全文会围绕医疗和金融两个领域展开,但方法论适用于任何垂直行业。
2. 智能体整体架构设计:别一上来就写 Prompt
2.1 先定边界:什么该交给智能体,什么不该
我见过太多团队一上来就写 System Prompt,然后发现模型要么不听话,要么乱调工具。问题出在没有先做任务边界划分。一个垂直领域智能体,不是把所有事情都塞给 LLM,而是要把任务分成三类:
- 确定性任务:比如计算利息、校验身份证号、查询数据库。这类任务交给传统代码,LLM 只负责触发和解释结果。
- 半确定性任务:比如根据规则做初步分类、提取结构化字段。这类任务可以用 LLM + 少量样本,但必须有校验层。
- 不确定性任务:比如理解患者模糊描述、生成个性化建议、处理多轮对话中的意图漂移。这类才是 LLM 的主场。
我通常会在设计文档里画一张表,把每个智能体的任务按这三类标注。如果一个智能体 80% 的工作都是确定性任务,那它就不该叫智能体,叫“带自然语言接口的脚本”更合适。这个判断标准能帮你省下大量算力和调试时间。
2.2 架构选型:ReAct、Plan-and-Execute 还是混合
目前主流的智能体架构有三种:
| 架构类型 | 核心逻辑 | 适用场景 | 缺点 |
|---|---|---|---|
| ReAct | 推理-行动循环,边想边做 | 工具调用频繁、步骤不固定的任务 | 容易陷入循环,Token 消耗大 |
| Plan-and-Execute | 先制定完整计划,再逐步执行 | 步骤明确、可预先拆解的任务 | 计划一旦出错,后续全错 |
| 混合模式 | 先粗粒度规划,再 ReAct 执行 | 大多数垂直领域生产环境 | 实现复杂度高 |
我的建议是:医疗和金融领域优先用混合模式。原因很简单,这两个领域对可解释性和容错率要求极高。纯 ReAct 的“边想边做”在演示时很酷,但生产环境里一旦模型开始“自由发挥”,你根本不知道它下一步会调什么工具。混合模式的做法是:先用一个轻量规划器把任务拆成 3 到 5 个步骤,每个步骤再交给 ReAct 执行器去调工具。这样既保留了灵活性,又有了可控的骨架。
2.3 记忆系统:别把对话历史当记忆
很多开发者把“记忆”简单理解为把对话历史拼进 Prompt。这在短对话里没问题,但在垂直领域智能体里是灾难。一个金融智能体可能需要记住用户三个月前的风险偏好,一个医疗智能体需要记住患者的过敏史。这些信息不能靠对话历史传递,必须外置成结构化记忆。
我通常会把记忆分成三层:
- 会话记忆:当前对话的上下文,用滑动窗口或摘要压缩,保留最近 5 到 10 轮。
- 用户记忆:用户画像、历史行为、偏好设置,存在关系型数据库或向量数据库里,按需检索。
- 领域记忆:行业知识、规则库、案例库,用 RAG(检索增强生成)方式接入,每次只检索最相关的 Top-K 片段。
提示:记忆系统的检索质量直接决定智能体的“聪明程度”。我见过一个医疗智能体,因为过敏史检索没做好,差点给出错误用药建议。后来加了强制校验层,才把风险降下来。
3. 核心细节解析:从 Prompt 工程到工具编排
3.1 System Prompt 的写法:角色、约束、输出格式三件套
System Prompt 不是越长越好。我见过一个 3000 字的 Prompt,模型反而抓不住重点。有效的 System Prompt 应该包含三部分:
- 角色定义:一句话说清楚“你是谁、你为谁服务”。比如“你是一名辅助分诊的医疗 AI,服务对象是急诊科护士,你的输出将用于初步分诊决策。”
- 硬约束:列出绝对不能做的事。比如“不得给出确诊结论”“不得推荐处方药”“遇到不确定情况必须输出‘需人工复核’”。
- 输出格式:用 JSON Schema 或固定模板约束输出。比如分诊智能体的输出必须是
{"department": "心内科", "urgency": "高", "confidence": 0.87}。
我实测下来,带 JSON Schema 约束的 Prompt,输出稳定性比自由文本高 40% 以上。因为模型在生成时有了明确的“填空”目标,不容易跑偏。
3.2 工具调用的参数设计:别让模型猜
工具调用是智能体的核心能力,但很多团队的工具定义写得太随意。比如一个“查询患者信息”的工具,参数只写patient_id,模型根本不知道这个 ID 是什么格式、从哪里来。正确的做法是:
{ "name": "query_patient_info", "description": "根据患者唯一标识查询基本信息、过敏史和近期就诊记录。仅在用户明确提供患者 ID 或已通过身份验证后调用。", "parameters": { "patient_id": { "type": "string", "description": "患者唯一标识,格式为 8 位数字,例如 10023456。如果用户未提供,必须先询问。" }, "fields": { "type": "array", "items": {"type": "string", "enum": ["basic", "allergy", "visit"]}, "description": "需要查询的字段列表,默认返回 basic。" } } }关键点是 description 里要写清楚“什么时候调用”和“什么时候不调用”。模型对工具的描述非常敏感,你写得越具体,它调用的准确率越高。
3.3 多智能体协作:什么时候需要,什么时候是过度设计
标题里说“30 个智能体”,不是让你把它们全部串成一个超级工作流。我的经验是:只有当单个智能体的职责无法用一句话说清,或者需要多个专业视角交叉验证时,才引入多智能体协作。比如金融反洗钱场景,一个智能体负责交易模式分析,一个负责客户背景调查,一个负责规则引擎校验,最后用一个仲裁智能体汇总。这种设计是合理的,因为每个子任务确实需要不同的知识库和工具集。
但如果只是“分诊”和“挂号”两个步骤,完全可以用一个智能体加两个工具搞定。多智能体带来的通信开销和调试复杂度,往往被低估。我踩过的坑是:三个智能体互相调用,结果一个超时导致整个链路卡死。后来改成异步消息队列 + 超时降级,才稳定下来。
4. 实操过程:手把手构建一个医疗分诊智能体
4.1 环境准备与基座模型选择
先说明,这里不涉及任何需要特殊网络环境才能访问的服务。我用的都是公开可获取的模型和框架。基座模型选择上,医疗领域我推荐两个方向:
- 通用大模型 + 医疗微调:比如基于开源 LLaMA 架构的医疗微调版本,优点是成本低、可本地部署,缺点是推理能力上限有限。
- 商用 API + 医疗 RAG:优点是推理能力强,缺点是数据隐私和成本问题。
我的建议是:先用商用 API 快速验证流程,跑通后再考虑本地化。因为智能体的核心难点不在模型本身,而在工具编排和记忆系统。你花两周调模型,不如花两天把工具调用跑通。
环境依赖很简单:
pip install langchain openai faiss-cpu pydantic如果你用本地模型,再加transformers和accelerate。向量数据库我推荐 FAISS,轻量、够用,医疗知识库通常几万条文档,FAISS 完全扛得住。
4.2 知识库构建:医疗分诊需要哪些数据
分诊智能体的知识库至少包含三类数据:
- 症状-科室映射表:比如“胸痛”对应心内科,“腹痛”对应消化内科或普外科。这个表可以从医院公开的分诊指南里整理。
- 紧急程度规则:比如“胸痛 + 出汗 + 左臂放射痛”属于高危,必须优先处理。
- 科室排班与容量信息:这个需要实时接口,不能放在静态知识库里。
我整理了一份示例映射表:
| 主诉关键词 | 推荐科室 | 紧急程度 | 备注 |
|---|---|---|---|
| 胸痛、胸闷 | 心内科 | 高 | 伴随出汗、放射痛时立即处理 |
| 腹痛、腹泻 | 消化内科 | 中 | 持续超过 6 小时需优先 |
| 头痛、眩晕 | 神经内科 | 中 | 突发剧烈头痛需排除出血 |
| 发热、咳嗽 | 呼吸内科 | 低 | 体温超过 39 度升级为中 |
这张表看起来简单,但实际落地时最大的坑是“同义词”。患者不会说“胸痛”,他会说“胸口不舒服”“心口疼”“前胸压得慌”。所以知识库检索必须做同义词扩展,或者用向量检索兜底。
4.3 智能体主循环实现
下面是一个简化版的 ReAct 主循环,用 Python 写:
import json from typing import List, Dict class TriageAgent: def __init__(self, llm, tools, memory, max_steps=5): self.llm = llm self.tools = tools self.memory = memory self.max_steps = max_steps def run(self, user_input: str) -> Dict: self.memory.add_user_message(user_input) for step in range(self.max_steps): prompt = self._build_prompt() response = self.llm.generate(prompt) action = self._parse_action(response) if action["type"] == "final_answer": return action["content"] elif action["type"] == "tool_call": tool_name = action["tool"] tool_args = action["args"] result = self.tools[tool_name].run(**tool_args) self.memory.add_tool_result(tool_name, result) else: return {"error": "无法解析模型输出", "raw": response} return {"error": "超过最大步数,需人工介入"} def _build_prompt(self) -> str: return f""" 你是一个医疗分诊智能体。根据对话历史和可用工具,决定下一步行动。 可用工具:{list(self.tools.keys())} 对话历史:{self.memory.get_context()} 输出格式:{{"type": "tool_call", "tool": "工具名", "args": {{...}}}} 或 {{"type": "final_answer", "content": {{...}}}} """这个循环的核心是最大步数限制。我设的是 5 步,超过就转人工。医疗场景下,宁可多转人工,也不能让模型无限循环。
4.4 输出校验与降级策略
模型输出必须经过校验层才能进入业务系统。我的校验层包含三关:
- 格式校验:JSON 是否能解析,字段是否齐全。
- 逻辑校验:科室是否在允许列表内,紧急程度是否合法。
- 置信度校验:模型输出的 confidence 低于 0.7 时,自动转人工复核。
降级策略也很重要。如果工具调用超时,智能体应该返回“当前无法查询,请稍后重试”,而不是编一个答案。在医疗和金融领域,编造答案的代价远高于说“我不知道”。
5. 常见问题与排查技巧实录
5.1 模型不调用工具,只输出文本
这是最常见的问题。原因通常是工具描述不够清晰,或者 System Prompt 里没有强调“必须调用工具”。我的解决步骤:
- 检查工具 description 是否包含“何时调用”的说明。
- 在 System Prompt 里加一句“如果用户问题需要实时数据,必须先调用工具,不得直接回答”。
- 如果还不行,用 Few-shot 示例,在 Prompt 里给 2 到 3 个调用工具的样例。
5.2 工具调用参数错误
模型经常把参数名写错,或者传空值。我的做法是:在工具执行前加一层参数校验,用 Pydantic 定义参数模型,校验失败就返回错误信息给模型,让它重新生成。这样模型有机会自我修正。
5.3 多轮对话中意图漂移
用户聊着聊着就换话题了,智能体还在执行旧任务。解决办法是在每轮对话开始时,用一个轻量分类器判断意图是否变化。如果变化,重置任务状态。这个分类器可以用小模型,也可以用规则匹配。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 模型不调工具 | 工具描述不清 | 检查 description | 补充调用时机说明 |
| 参数错误 | 模型理解偏差 | 打印模型原始输出 | 加 Pydantic 校验层 |
| 循环超时 | 任务太复杂 | 查看步数日志 | 限制最大步数,转人工 |
| 输出格式错 | Prompt 约束弱 | 检查 JSON Schema | 用结构化输出约束 |
| 检索不准 | 同义词未覆盖 | 检查召回结果 | 加同义词扩展或向量检索 |
注意:医疗和金融场景下,任何智能体输出都必须有“人工复核”入口。这不是技术问题,是责任边界问题。
6. 从 1 到 30:智能体矩阵的扩展思路
当你跑通第一个分诊智能体后,扩展到 30 个的思路不是复制粘贴,而是抽象出可复用的组件。我通常会把智能体拆成三层:
- 基础层:LLM 调用、记忆管理、工具注册、日志监控。这层所有智能体共用。
- 领域层:医疗知识库、金融规则库、法律条文库。这层按领域划分。
- 任务层:每个智能体的 Prompt、工具集、校验规则。这层最轻,也最容易扩展。
这样你新增一个智能体,只需要写任务层的配置,基础层和领域层直接复用。我实测下来,第二个智能体的开发时间通常是第一个的 30% 到 40%。
另外,智能体之间不要直接互相调用,而是通过消息队列或事件总线通信。这样单个智能体故障不会拖垮整个系统。金融领域尤其要注意这一点,交易监控智能体和分析智能体必须解耦。
最后分享一个我踩过的坑:早期我把所有智能体的 Prompt 放在一个文件里,结果改一个影响一片。后来改成每个智能体独立配置文件,用 YAML 管理,版本控制清晰多了。这个习惯建议你从第一个智能体就养成。