Agent 生产落地:从 demo 到产品的血泪经验

AI Agent
这篇是我agent实践记录的第一篇,主要聊聊我前段时间在做的一个宠物医疗"病历质控 Agent"从原型做到生产落地的。不是什么高大上的理论,就是实打实的踩坑记录和权衡取舍。

前言

虽然正儿八经做架构设计不到两年,但是我越来越笃信一个理念:好的架构不是”一步到位”的 perfection,而是在成本约束下的渐进式演进。

尤其是在构建 AI Agent 系统 时,这个理念更为重要。Agent 不同于传统的 API 调用或批处理任务,它是一个能自主决策、持续学习、与环境交互的智能体。这意味着:

  • 它不是静态的:今天有效的策略,明天可能需要调整
  • 它是有状态的:需要维护上下文、记忆、反馈循环
  • 它是渐进的:从简单的固定工作流(Workflow)到复杂的自主决策(ReAct),需要一步步验证

这里的成本不只是钱,还包括:

  • 实现成本:团队对 Agent 模式的理解、Prompt Engineering 能力、RAG 调优经验
  • 部署成本:LLM 推理的延迟和费用、向量数据库的运维、多模型路由的复杂度
  • 机会成本:为了做通用 Agent 而放弃的专用模型,值不值得

但我说的渐进式演进,绝不是无脑地给自己挖坑、留技术债的借口——当然,这话也不能说太绝。

实际上,有时候我会故意选择留一些”可控的技术债”,或者接受部分妥协。比如明知道某个设计将来可能需要推翻重来,但如果当前阶段的目标只是快速验证(打单、demo、MVP),那我可能干脆就不考虑那么远的扩展性。等真到了需要推倒重来的那一天,说明业务已经跑通了,那点重构成本完全值得。

还有一种情况:完全推翻重来也没关系。比如早期为了抢时间做的 demo,本来就是用来验证需求的,验证完了直接扔掉重写,反而比在一堆妥协之上缝缝补补更干净。

所以关键不是”不能留技术债”,而是清醒地知道自己在做什么选择——是为了赶时间而主动承担的技术债?还是为了偷懒而无意间埋的雷?前者可控,后者致命。

每一阶段都要有清晰的”逃生通道”和”回滚方案”,但具体是渐进演进还是推倒重来,得看场景,不能教条

先说说背景

我们是在构建一个病历质控 Agent——核心职责是:医生写完病历后,Agent 自动评估质量,识别潜在风险并给出改进建议。

这看起来就是调个 LLM API,但实际上要做一个能持续运行、自主决策、从反馈中学习的 Agent 系统,坑很多:

  • 医疗场景容错率极低:漏了过敏史或剂量错误,就是事故
  • 需要可解释:必须说明为啥判定有问题,依据是什么
  • 需要可控制:不能让它胡说,得有明确边界
  • 需要可进化:能从医生反馈中学习,越用越准

所以我们设计了两阶段路线:Phase 1 固定工作流Phase 2 ReAct Agent,渐进式演进。

AI Agent 生产落地

技术选型:不同阶段,不同选择

技术选型不是非黑即白,而是看阶段、看目标。我们的演进路径经历了三个阶段:

阶段一:预研验证(LangChain / LangGraph + Python)

刚开始预研时,我们的目标其实挺大:想做一个 1 + N 的 AI 能力底座

1 是一个可微调、可沉淀领域能力的小模型,用来理解宠物医疗里的术语、病历结构、诊疗知识;N 是围绕业务长出来的一组 Agent,比如病历质控 Agent、用药检查 Agent、诊疗辅助 Agent、话术合规 Agent。

这个阶段最大的问题不是”怎么写一个 HTTP 调用”,而是 Agent 的边界到底在哪:

  • 要不要多 Agent 协作?
  • 工具调用怎么编排?
  • 状态怎么保存?
  • 什么时候需要 human-in-the-loop?
  • 失败后怎么恢复?

所以当时选了 LangChain / LangGraph 这类流行框架。LangChain 更像 LLM 应用开发生态,模型调用、工具封装、RAG、Agent 都有;LangGraph 更偏 Agent 编排,适合探索长流程、状态化、多工具调用这些问题。

这时候用成熟生态没问题,因为核心目标是跑通 Agent 的各种可能性,快速验证边界。但这只是预研工具,不是生产承诺。等后面发现病历质控主链路其实是固定 Workflow,不需要复杂 Agent 编排时,我们才逐步做减法。

阶段二:打单/MVP(Spring AI + Java)

预研验证通过后,需要给客户、投资人演示。但目前主要的技术栈是 Java,团队内当前资源也有限,没法快速用 Python demo 去交付。

于是迁移到 Spring AI。这时候的目标是快速产出可演示的产品,框架封装得好,开发速度快,哪怕有点”魔法”、有点性能损耗,也先不管。

阶段三:生产落地(WebClient + Jackson)

真正要上线生产了,目标变成了稳定、可控、可运维。这时候 Spring AI 的问题暴露出来了:

  • 对我们这个固定 Workflow 来说,框架抽象带来的收益不大
  • 真正出问题时,我更想直接看到 HTTP 请求、响应、超时、重试和 JSON 解析细节
  • 我们暂时不需要复杂的 Advisor、Tool Calling、Memory 抽象,自己控制反而更简单

这不是说 Spring AI 不适合生产。相反,如果你需要快速接入多模型、结构化输出、工具调用、RAG 编排,它是一个很好的选择。

但在我们的场景里,控制流很固定,业务风险又高,于是做减法,直接上 WebClient + Jackson。自己写 HTTP 调用、自己处理 JSON,代码多了一点,但超时、重试、日志、Schema 校验、错误码映射,每一层都能看得见、控得住

阶段 目标 选型 理由
预研 探索 1+N Agent 边界 LangChain / LangGraph (Python) 生态成熟,适合快速验证多 Agent、工具调用、状态编排
打单 快速交付 Spring AI (Java) 适配技术栈,演示效果好
生产 稳定可控 WebClient + Jackson 少一层 AI 框架抽象,排查简单,长期维护成本低

你看,没有绝对的好坏,只有适合不适合当前阶段

当然,基础设施层的选择是贯穿始终的——这些一旦选定就不容易改,所以一开始就做了减法:

最初的念头 最终的方案 理由
Qdrant 向量库 pgvector 已有的 PostgreSQL 加个插件就搞定,零新增基础设施;Qdrant 留作规模上来后的迁移选项
独立的 Schema 隔离 表名前缀 agent_ 同团队同代码库,schema 隔离是过度设计
只用在线 API 先 DeepSeek API,私有化再本地 vLLM/Qwen3 API 性价比高、交付快;只有客户明确要求私有化时,才值得承担本地推理成本
text2vec-base-chinese Qwen3 Embedding 中英混合、长文本、RAG 检索效果更稳,后续可接 Qwen3 Reranker

生产环境跟 POC 最大的区别就是:能少一个组件就少一个组件。每多一个依赖,就是多一个故障点。

模型部署方式这块,我反而觉得不是最核心的选择。

这里说的在线 API,不是指国外 API。国外 API 肯定不在我们的候选里,不是模型能力问题,而是数据合规、网络稳定性、客户接受度都绕不过去。主路径更现实的选择是 DeepSeek API:接入快,成本可控,也不用一开始就背 GPU 服务器和推理运维的包袱。

本地部署一开始并不是首选。真要自己上 vLLM、上 Qwen3,本质上是在买一套推理基础设施:GPU、显存、并发、排队、监控、扩容、模型升级,哪一个都不是免费的。对 MVP 和早期生产来说,性价比远不如 API。

后来之所以补本地部署方案,是因为有客户明确提了私有化诉求,希望模型和数据都在本地环境里跑。那我们就把 LLM 调用层抽成统一接口:默认走 DeepSeek API,私有化部署时切到 vLLM + Qwen3。这个切换并不复杂,真正复杂的是 Agent 的流程、工具边界、RAG 命中、反馈闭环和安全护栏。

换句话说:模型可以换,Agent 设计不能乱。这篇文章真正想讲的重点,也不是“到底用哪个模型”,而是怎么把 Agent 设计贴到宠物医疗的真实业务流程上。

这里很多人会问:那为什么不用 Qdrant?

Qdrant 是一个很强的向量数据库,尤其适合:

  • 数据量上来以后,需要更稳定的低延迟 ANN 检索
  • metadata filter 很复杂,比如按医院、科室、宠物类型、疾病分类、时间范围一起过滤
  • 想做 dense + sparse hybrid search,甚至多向量检索、late interaction rerank
  • 向量检索已经变成系统核心能力,需要单独扩容、监控、调参

但我现在还是先选 pgvector。原因很现实:我们已有 PostgreSQL,病历、反馈、样本、审计日志都在里面。Phase 1 的样本量也就几百到几万级,pgvector + HNSW 足够跑。医疗场景里,事务一致性、审计链路、少一个运维组件,比单纯追求向量检索性能更重要。

所以我的判断是:pgvector 是当前阶段的正确选择,Qdrant 是下一阶段的迁移选项。如果后面样本库涨到百万级,或者我们发现复杂过滤下 pgvector 召回/延迟开始不稳,再把检索层抽成独立服务迁到 Qdrant。这样迁移成本可控,也不会太早把系统复杂度抬上去。

RAG 设计:向量检索不是万能药

做医疗质控,核心是怎么让 AI “懂业务”。我们用了 RAG(检索增强生成)来做知识注入,但这里面有几个细节值得说说。

两种检索策略

我们设计了两条检索路径,分别对应不同的业务场景:

第一种:Few-shot 样本检索(对应 agent_few_shot_examples 表)

根据病历内容,从历史优质病例里找相似的。Embedding 首选 Qwen3 Embedding,存 pgvector。

这里有个坑:不能把整份病历一股脑塞进去做 embedding。病历是长文本,而且字段语义差异很大。主诉、诊断、用药、检查结果、医嘱,混在一起向量化,很容易把关键风险冲淡。

所以我们按字段拆 chunk:

  • 主诉/现病史:用于找相似病例
  • 诊断:用于找诊疗规范
  • 用药:用于找剂量、禁忌、配伍规则
  • 化验结果:用于找参考值和异常解释

第一版用纯向量召回,后面会加 keyword + vector 的混合检索,再接 Qwen3 Reranker 做二次排序。医疗场景里,召回率比“看起来很准”更重要,漏掉一条禁忌才是真正要命的问题。

1
2
3
4
5
6
7
8
9
10
11
-- pgvector 用法很简单
CREATE EXTENSION IF NOT EXISTS vector;

ALTER TABLE agent_few_shot_examples
ADD COLUMN embedding vector(1024); -- Qwen3-Embedding-0.6B 默认 1024 维

-- 语义相似度检索
SELECT *, 1 - (embedding <=> CAST(:embedding AS vector)) AS similarity
FROM agent_few_shot_examples
ORDER BY embedding <=> CAST(:embedding AS vector)
LIMIT :limit;

这只是最小示例。生产里还得建索引,不然样本库一大,延迟会很难看:

1
2
3
4
-- HNSW 构建慢一点、吃内存一点,但通常有更好的召回/延迟平衡
CREATE INDEX agent_few_shot_examples_embedding_hnsw
ON agent_few_shot_examples
USING hnsw (embedding vector_cosine_ops);

pgvector 还有 IVFFlat,构建更快,但要调 lists/probes。我们的选择是:样本量还不大时直接 HNSW,先把线上查询延迟和召回稳定住。

第二种:知识库检索(对应 agent_knowledge_base 表)

药典、诊疗规范、化验参考值。注意:图片不参与向量检索,存 OSS 里,检索命中后返回链接即可。

Embedding 模型的选择

embedding 模型我现在首选 Qwen3 Embedding,原因很直接:

  • 跟 Qwen3 主模型体系更一致,RAG 链路里少一点“模型语义空间错位”
  • 对中英混合、长文本、多任务检索更稳
  • 后续可以接 Qwen3 Reranker,把“多召回一点”变成“排序更准一点”

text2vec-base-chinese 不是不能用。它的优点是轻、便宜、CPU 能跑,适合做边缘部署或降级方案。但如果目标是病历质控这种高风险场景,我会把它放在 fallback,而不是主路径。

1
2
3
4
5
6
7
8
9
10
11
# 示例:Embedding 服务可以独立部署,主 Java 服务只通过 HTTP 调用
from flask import Flask, jsonify, request
from sentence_transformers import SentenceTransformer

app = Flask(__name__)
model = SentenceTransformer('Qwen/Qwen3-Embedding-0.6B')

@app.route('/embed', methods=['POST'])
def embed():
text = request.json['text']
return jsonify({'embedding': model.encode(text).tolist()})
小提示:向量检索的召回率比精度更重要。我们宁可多召回几条再让 LLM 筛选,也不要漏掉相关的样本。

多模型路由:Agent 也得”看菜吃饭”

不同任务对模型能力要求不同,我们做了 LlmRouter,让 Agent 按需选择。

这里路由的重点不是把代码写死到某个模型厂商,而是按任务能力分层:

任务 复杂度 默认接入 私有化替代 理由
病历质控 高(需推理) DeepSeek API Qwen3-32B 理解上下文、判断合规性
用药建议 高(需知识) DeepSeek API Qwen3-32B 剂量计算、配伍禁忌
话术合规 DeepSeek API 低成本配置 Qwen3-7B 敏感词检测,不需要每次都上高推理成本
诊疗辅助 高(需对话分析) DeepSeek API Qwen3-32B 根据问诊对话分析可能病症,给出检查和处置建议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class LlmRouter {
private final Map<AgentType, ModelProfile> profiles;
private final LlmClient llmClient;

public LlmRouter(LlmClient llmClient) {
this.llmClient = llmClient; // 默认 DeepSeek API,私有化时切到 vLLM/Qwen3 实现
this.profiles = Map.of(
AgentType.RECORD_EVAL, ModelProfile.HIGH_REASONING,
AgentType.DRUG_CHECK, ModelProfile.HIGH_REASONING,
AgentType.SCRIPT_COMPLIANCE, ModelProfile.FAST_CHECK,
AgentType.DIAGNOSIS_ASSIST, ModelProfile.HIGH_REASONING
);
}

public LlmResponse chat(AgentType type, List<Message> messages) {
return llmClient.chat(profiles.get(type), messages);
}
}

这样做以后,默认线上可以走 DeepSeek API;客户要私有化,就换成 vLLM + Qwen3 的 LlmClient 实现。Agent 的业务代码不用跟着模型一起改。

成本降低 60%,不是因为某个模型突然变魔法了,而是因为任务被拆清楚了。高风险、高推理任务用高能力档;低风险、规则性任务用低成本档。生产环境嘛,能省则省,但不能省到影响医疗安全。

当然,这句话不能只靠感觉。我们内部看的是一组固定评测集:

指标 为什么看它
高风险漏检率 医疗场景最怕漏掉过敏、剂量、禁忌这类问题
误报率 误报太多,医生会直接不看
医生采纳率 真实业务反馈,比离线 benchmark 更接近产品价值
P95 延迟 病历保存后多久能看到质控报告
单次评估成本 模型路由到底有没有省钱

尤其是高风险漏检率,只要这个指标变差,成本省再多也不能上。医疗场景不是推荐系统,不能用“整体点击率提升”那套逻辑糊弄自己。

Phase 1 vs Phase 2:Agent 的渐进式落地

这里想重点聊聊落地节奏。我们分两阶段走:

Phase 1:固定工作流(已上线)

先做”傻瓜式”的固定流程:

  1. 病历保存 → 触发评估事件 → Redis Stream
  2. Worker 消费消息 → PromptBuilder 拼装提示词
  3. RAG 检索相似样本 → 注入 Prompt
  4. 调用 LLM → 返回 JSON 格式的评估结果
  5. 前端展示报告 → 医生可反馈(采纳/拒绝/评分)

这个阶段的 Agent 没有”自主决策”能力,就是按固定套路走。但好处是可控、可预期、好调试。出问题了一步步排查,不会遇到那种”Agent 突然抽风”的诡异 bug。

Phase 2:ReAct Agent(灰度中)

等工作流稳定了,再逐步引入 ReAct 循环,让 Agent 能主动调用工具:

  • 药典查询(判断用药合理性)
  • 历史病历检索(了解既往病史)
  • 诊疗规范检索(判断诊断是否规范)
  • 化验参考值(判断检查结果)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ReAct 循环核心
public EvaluationResult run(MedicalRecord record) {
List<Message> context = buildInitialContext(record);

for (int turn = 0; turn < MAX_TURNS; turn++) {
// LLM 推理
LlmResponse resp = llmRouter.chat(AgentType.RECORD_EVAL, context);

if (resp.isFinished())
return parseResult(resp.content()); // 输出结论

// 否则调用工具
ToolCall call = resp.getToolCall();
String toolResult = toolRouter.invoke(call);

// 工具结果回传上下文
context.add(Message.assistant(resp.content(), call));
context.add(Message.tool(call.id(), toolResult));
}
return fallbackToWorkflowOrHumanReview(context); // 超出轮次后降级,不强行给医疗结论
}

有个 agent.enabled 开关可以切模式,万一 Phase 2 出问题,随时回滚到 Phase 1。

经验之谈:Agent 系统一定要设计"逃生通道"。再聪明的 Agent 也可能犯错,得让人工随时能接管。

生产护栏:比模型更重要的东西

Agent 上生产,模型能力只是一半。另一半是护栏。

我们做了几件很朴素但很关键的事:

  1. 结构化输出校验:LLM 必须返回符合 JSON Schema 的结果,解析失败重试一次,再失败就转人工复核
  2. Prompt 和规则版本化:每次评估都记录 prompt 版本、规则版本、模型版本,出了问题能回放
  3. RAG 命中留痕:命中了哪些样本、哪些知识条目、相似度多少,都落库
  4. 高风险结论必须有依据:涉及过敏、剂量、禁忌、诊疗规范,不能只有 LLM 一句话,必须能指回规则或知识库
  5. 工具调用白名单:ReAct 能调用哪些工具、每个工具最大超时、最大轮次,都写死在配置里
  6. 灰度开关和降级路径:Phase 2 出问题,马上切回 Phase 1;LLM 出问题,转人工复核,不让系统硬撑

这些东西不酷,但这才是生产环境真正救命的地方。

进化机制:让 Agent 自己长本事

最让我们团队骄傲的,是这个”每周进化”机制。

传统的 AI 项目上线了就完事了,效果衰减没人管。我们设计了一个定时任务(每周日凌晨 3 点):

  1. 统计反馈数据:过去一周医生的采纳率、评分
  2. 提炼高质量样本:采纳率>80% 且 评分>80 的案例,自动提升为 Few-shot 样本
  3. 分析 Bad Case:高拒绝率的案例,让 LLM 分析原因,生成候选规则
  4. 淘汰低效样本:长期不被采纳的样本自动下线

这就形成了一个闭环:Agent 评估 → 医生反馈 → 自动学习 → Agent 能力提升 → 更多评估 → 更多反馈…

但这里必须克制:Agent 不能自己改生产规则

我们做的是“自动发现,人工发布”。LLM 可以帮忙总结 Bad Case,生成候选 prompt 规则,推荐哪些样本应该提升为 Few-shot,但这些变更要先进审核队列。医生或业务专家确认后,才会发布到生产版本。每次发布都有版本号,也能回滚。

上线两个月,样本库从初始的 50 条涨到了 300+ 条,采纳率从 65% 提升到了 82%。关键不是“零人工干预”,而是把人工精力用在审核和校准上,而不是每天手工翻 Bad Case。这才是可持续的进化。

进化飞轮

部署架构:能复用就复用

最后简单说下部署。我们的原则是:默认不新增重基础设施,客户要求私有化时再补本地推理层

默认 SaaS 形态是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────┐
│ 现有基础设施 │
├─────────────────────────────────────────────────────────────┤
│ PostgreSQL (+ pgvector 插件) ← 已有实例,加向量列即可 │
│ Redis ← 已有,新增 Stream key │
│ Nacos ← 已有,服务注册发现 │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ ai-agent-service (新服务,2C/2G) │
│ ├── 固定工作流 (Phase 1) │
│ ├── RAG 检索模块 │
│ └── ReAct Agent (Phase 2,可开关) │
└─────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌────────────────────────────┐
│ DeepSeek API │ │ embedding 服务 │
│ 默认 LLM 调用入口 │ │ Qwen3 Embedding 独立部署 │
└──────────────────────┘ └────────────────────────────┘

如果客户要求私有化部署,再把 DeepSeek API 那一层替换成本地推理服务:

1
2
3
DeepSeek API

vLLM + Qwen3(客户私有化环境)

这个替换只发生在 LlmClient 实现层,不影响 Agent 的工作流、工具调用、RAG 检索和反馈闭环。也正因为这样,我们没有一开始就为了“看起来更自主可控”去买 GPU 堆本地模型。早期最贵的不是 API 调用费,而是团队把精力消耗在推理运维上,却还没证明 Agent 真的贴合业务。

写在最后

做 Agent 生产落地,最大的感悟是:克制比炫技更重要

我们当然可以搞个多 Agent 协作系统,每个 Agent 都有自己的记忆、工具、规划能力,看起来很酷。但现实是,大部分业务场景不需要那么复杂。一个固定的 Workflow + RAG 增强 + LLM 推理,能解决 80% 的问题。这就是我们 Phase 1 的选择。

剩下的 20%,才是需要 ReAct、自主决策、工具调用的场景。而且即使做 Agent,也要给足控制手段——开关、降级、人工介入,一样都不能少。

Agent 不是魔法,它是一个需要工程化打磨的智能系统。把预期放低一点,把工程做实一点,反而更容易成功。

参考链接:

关联阅读:


如果你也在做 Agent 落地,欢迎交流。踩过的坑,说不定能帮到你。