AI Agent 从概念到实践:一套完整的 RAG + 知识图谱智能客服系统落地全记录
摘要: 本文从 Agent 的基础概念出发,深入拆解其四大核心模块(规划、记忆、工具、行动),结合我们在 VTN 品牌落地的一套完整的知识图谱 RAG 智能客服系统,详细阐述从理论到工程实践的全过程。如果你正在考虑把 AI 能力接入现有业务系统,这篇文章会给你一个可参考的完整路径。
一、什么是 AI Agent?
1.1 从一句话说起
OpenAI 应用研究主管 Lilian Weng 提出了一个被广泛引用的公式:
Agent = LLM + 规划(Planning)+ 记忆(Memory)+ 工具使用(Tool Use)
这个公式的精妙之处在于:它把大模型从一个"回答问题的聊天机器人"提升到了一个"能自主完成任务的智能体"。
打个比方:
- LLM 是大脑,负责理解和推理
- 规划 是做计划的能力,把大任务拆成小步骤
- 记忆 是记住之前发生的事情,不让每次对话都从零开始
- 工具 是手脚,能搜索网页、调用 API、读写文件
1.2 Agent ≠ ChatGPT 升级版
一个常见的误解是 Agent 就是"更好用的 ChatGPT"。实际上它们有本质区别:
| ChatGPT(LLM) | AI Agent | |
|---|---|---|
| 核心能力 | 生成文本 | 理解目标 + 自主执行 |
| 交互方式 | 一问一答 | 感知 → 思考 → 行动 → 观察 循环 |
| 能否调用工具 | 不能(原生) | 能,这是核心能力 |
| 能否处理未知 | 不能,遇到训练外的知识就编造 | 能,通过工具搜索实时信息 |
| 类比 | 你问一个人问题,他凭记忆回答 | 你派一个人去完成任务,他会自己想办法 |
正如一篇分析文章所说:如果 Copilot 是副驾驶,那么 Agent 就是主驾驶。
1.3 人机协同的三个阶段
AI 落地到业务中,人机协同经历了三个阶段:
阶段一:嵌入模式(Embedding) — AI 是工具
用户写 prompt,AI 生成结果。比如用 ChatGPT 写文案、翻译。AI 的角色相当于一个更聪明的搜索引擎。
阶段二:副驾驶模式(Copilot) — AI 是搭档
人类和 AI 共同参与工作流程。比如 GitHub Copilot 帮程序员写代码,人类审核和调整。AI 能提供建议但不做决策。
阶段三:智能体模式(Agent) — AI 是主驾驶
人类设定目标,AI 独立承担大部分工作,人类只做监督和评估。比如 AutoGPT 可以自主完成调研、写报告、写代码。
我们落地的 VTN 智能客服系统,目前处于阶段二到阶段三的过渡:AI 能自主检索知识、生成回答、推荐产品,但在复杂场景下仍需要人类(管理员)通过知识库管理和反馈审核来介入。
二、Agent 四大核心模块深度拆解
2.1 规划(Planning)— 怎么把事做对
规划是 Agent 的"战略制定"能力。它决定了 Agent 如何把一个复杂目标拆解为可执行的子任务。
短期规划 vs 长期规划
短期规划(战术):根据当前环境实时决策下一步做什么。代表项目:
- AutoGPT:每一步都让 LLM 决定下一步行动
- ReAct:Thought → Action → Observation 循环
长期规划(战略):在行动开始前就制定完整计划。代表项目:
- BabyAGI:先生成任务列表,再逐步执行
- XAgent:外环做长期规划(PlanAgent),内环做短期执行(ToolAgent),形成双循环
我们的实践:
在 VTN 系统中,我们把规划能力用在了查询改写环节。用户问"VC 好处",系统需要自主规划:
- 这个问题涉及什么产品/品牌?(实体域识别)
- 用户真正想问什么?换几种表述方式(查询扩展)
- 用户有没有提到健康问题?(疾病提取)
- 用户是想看产品卡还是问问题?(意图识别)
这 4 步并行执行,全部由 GPT-4o 完成。本质上就是一种短期规划——LLM 根据用户输入自主决定"我需要从哪些角度去理解这个问题"。
# 核心代码:4路并行查询改写
async def rewrite_query(query_model):
results = await asyncio.gather(
detective_query_domain(query_model), # 1. 实体域识别
extension_query(query_model), # 2. 查询扩展
detective_query_condition(query_model), # 3. 疾病提取
query_intent_recognize(query_model), # 4. 意图识别
)
return combine_results(results)为什么需要意图识别?
这是很多初学者会忽略的问题。想象这个场景:用户之前在看产品,突然说"给我看看一卡通"。
如果不做意图识别,系统会把"一卡通"当成营养问题去知识库检索,然后输出一段关于"什么是产品一卡通"的文字回答——完全不是用户想要的。
意图识别的本质是一个路由器:识别出用户要的是产品卡图片,就短路整个 RAG 流程,直接返回结构化数据。
# 意图识别后的短路逻辑
if intents: # 用户想要产品卡或价格表
yield format_intent_event(intents, goods_data)
return # 不走检索和LLM生成,直接返回为什么需要查询扩展?
用户输入太短了。"VC 好处"只有 4 个字,直接拿去做向量检索,精度很低。
查询扩展就是让 LLM 从不同角度重新表述同一个问题:
- 原始:VC 好处
- 扩展 1:维生素 C 对人体健康有哪些好处?
- 扩展 2:补充维生素 C 能改善什么健康问题?
- 扩展 3:维生素 C 的功效与作用是什么?
4 个 query 分别检索,取并集。这就像你去图书馆查资料,只查一个关键词可能漏掉很多相关书,换个说法再查一次,覆盖面就大了。
2.2 记忆(Memory)— 记住发生过什么
Agent 的记忆系统借鉴了人类认知科学的模型,分为三层:
感知记忆(Sensory Memory)
原始输入的向量化表达。在我们的系统中,用户的每一次输入都会被 text-embedding-3-small 转化为 1536 维向量。这就是 Agent 的"感知"。
短期记忆(Short-term Memory)
上下文窗口中最近的对话历史。我们的系统加载最近 4 轮对话(8 条消息),作为 LLM 生成回答时的上下文。
# 加载最近4轮对话作为短期记忆
history = get_chat_history_messages(
chat_id=chat_id,
user_id=user_id,
loop_count=4 # 4轮 × 2(user + assistant) = 8条
)短期记忆的挑战在于上下文窗口有限。GPT-4o 的上下文是 128K,但考虑到成本和延迟,实际可用的只有几 K。我们的做法是截断旧消息——超过 35 个字符的历史消息只保留前 35 字符。
长期记忆(Long-term Memory)
持久化存储在数据库中的历史对话。当对话累积到 3 轮以上时,系统会异步调用 addMemory() 接口,把对话摘要存入长期记忆。
// Java网关层的长期记忆逻辑
if (chatItemCount >= lastMemoryChatItemId + 6) { // 3轮 = 6条消息
Dc.runAsync(() -> {
aiMemoryFeignClient.addMemory(chatId, idCode);
});
}会话内记忆 vs 会话间记忆
- 会话内记忆:同一个对话窗口内的多轮对话。用户说"维生素 C 有什么好处",接着问"那每天吃多少",系统需要知道"那"指的是维生素 C。
- 会话间记忆:跨对话的持久记忆。用户上周问过"BEE+ 蜂蜜怎么吃",这周问"蜂蜜能和维 C 一起吃吗",系统应该记住用户的偏好和历史。
我们的系统通过 chat_history 和 chat_messages 表实现了会话内记忆,通过 AiMemoryFeignClient 实现了会话间记忆的框架。
记忆的妙用
- 指代消解:短期记忆让 LLM 理解"它"、"这个产品"指的是什么
- 答案缓存:相似问题可以跳过 LLM 推理,直接返回缓存结果
- 偏好学习:长期记忆记住用户喜欢什么格式的回答、关心哪些产品
- 数据回流:用户的反馈(点赞/踩)可以用于优化知识库和 prompt
2.3 工具使用(Tool Use)— 让 Agent 有手有脚
Agent 的核心突破在于:LLM 不仅能生成文本,还能调用外部工具完成实际操作。
工具使用的演进
- WebGPT(2021):让 GPT-3 学会使用浏览器搜索信息
- Toolformer(2023):训练 LLM 自主决定何时调用 API
- HuggingGPT(2023):让 ChatGPT 调用 HuggingFace 上的专家模型
- Function Calling(2023):OpenAI 原生支持,LLM 输出结构化 JSON 调用指定函数
我们的工具使用实践
在 VTN 系统中,Agent 使用了以下"工具":
| 工具 | 什么时候用 | 怎么用 |
|---|---|---|
| PGVector 检索 | 用户提问时 | 5 路并行向量搜索,从知识库中找到相关内容 |
| BM25 关键词搜索 | 实体匹配时 | Jieba 中文分词 → BM25 打分,和向量搜索混合 |
| LLM 查询改写 | 用户提问时 | 4 路并行 GPT-4o 调用,改写和扩展用户问题 |
| Embedding API | 每次检索前 | text-embedding-3-small 生成查询向量 |
| 商品微服务查询 | 推荐产品时 | 调用内部 API 获取商品详情、价格、图片 |
| FastGPT 知识库 | 健康报告分析时 | 搜索产品功效和禁忌信息 |
| RabbitMQ | 问答完成后 | 异步发送聊天数据给 Java 网关层持久化 |
这些工具不是预定义的硬编码流程,而是由 Agent 在不同场景下自主选择调用的。比如:
- 普通问答 → PGVector + BM25 混合检索 → qwen-max 生成回答
- 健康报告上传 → GPT-4o OCR → doubao-pro 分析 → FastGPT 匹配产品
- 企微消息 → 阻塞式调用 vtn-agentic → 拿到完整回答后通过企微 API 发送
2.4 行动(Action)— 把想法变成结果
行动是 Agent 对环境的实际响应。根据任务类型不同,行动可以是:
- 生成一段文本回答
- 调用一个 API
- 执行一段代码
- 发送一条消息
ReAct 模式
最经典的 Agent 行动模式是 ReAct(Reasoning + Acting):
Thought: 用户问的是维生素C的功效,我需要先检索知识库
Action: PGVector.search("维生素C功效", top_k=20)
Observation: 找到15条相关知识...
Thought: 检索到了足够的信息,现在需要生成回答
Action: LLM.generate(system_prompt, context, user_query)
Observation: 生成了一段关于维生素C功效的回答
Final Answer: 维生素C是一种重要的水溶性维生素...我们的系统虽然不是标准的 ReAct 循环,但遵循了同样的"思考 → 行动 → 观察"范式,只是把循环次数压缩到了 1 次(规划 → 检索 → 生成),而不是多次迭代。这是工程上在效果和成本之间的权衡。
三、知识图谱 + RAG:Agent 的记忆外挂
3.1 为什么 RAG 不够用?
标准 RAG 的流程是:文档 → 分块 → 向量化 → 存入向量库 → 用户查询 → 向量检索 → LLM 生成。
但纯向量检索有几个问题:
- 精确匹配差:用户搜"VC"(缩写),向量模型可能不理解
- 关系丢失:维生素 C 和免疫系统之间的关系,在向量空间中是模糊的
- 领域精度低:全库搜索,可能搜到不相关品牌的产品
3.2 知识图谱怎么补上这些短板?
我们的做法是:在 RAG 的基础上叠加知识图谱,形成 **KG-RAG(Knowledge Graph RAG)**架构。
知识图谱的数据结构
实体(节点):
- 维生素C(营养素)
- BEE+ 维生素C片(产品)
- 免疫系统(身体系统)
关系(边):
- 维生素C → 免疫系统 [增强免疫, 强度8]
- 维生素C → 胶原蛋白 [促进合成, 强度7]
- BEE+ 维生素C片 → 维生素C [含有, 强度10]知识存储在 PostgreSQL 中,用 PGVector 扩展做向量搜索,关系用专门的 kg_entity_relationships 表存储。
混合检索策略
我们用 5 路并行 PGVector 查询 + BM25 关键词搜索的混合策略:
用户查询 embedding
│
├─ 全局关系搜索(top 20)──── 不带 domain 过滤,覆盖面广
├─ 全局问题搜索(top 10)──── 搜 LLM 生成的问题索引
├─ 域内关系搜索(top 15)──── 带 WHERE domain IN (...),精度高
├─ 域内文本搜索(top 5)───── 搜原始文本片段
└─ 域内问题搜索(top 5)───── 搜域内 LLM 生成的问题
│
▼
合并 → 去重 → 过滤(distance > 0.52 的丢弃)→ 最终检索结果为什么分"域内"和"全局"两组?
- 域内查询:精度高,但可能漏掉不在已识别域内的知识
- 全局查询:覆盖面广,但可能混入噪声
- 两者取并集:用域内保精度,用全局补召回
索引时的查询扩展(镜像策略)
前面讲了搜索时的查询扩展。其实索引时也做了类似的事情——为每个文本块生成"可能被问到的问题":
文本块: "维生素C是水溶性维生素,具有抗氧化作用,可促进胶原蛋白合成"
│
│ LLM 生成问题
▼
"维生素C有什么功效?"
"维生素C是水溶性还是脂溶性的?"
"维生素C和胶原蛋白有什么关系?"这些问题和用户的真实提问方式更接近,向量相似度更高。这就是为什么要有 kg_text_units_index 表。
3.3 实体合并:知识图谱的去重
同一种产品可能在多个文档中被提到,每次描述略有不同。比如:
- 文档 A:"BEE+ 维生素 C 片,每片含 500mg 维生素 C"
- 文档 B:"BEE+ 维 C 泡腾片,适合免疫力低下人群"
- 文档 C:"BEE+ 维 C 产品,可搭配蜂蜜服用"
如果不合并,搜"维生素 C"会搜出 3 条分散的结果。合并后只保留一条最完整的描述,搜索结果更集中、排名更准确。
合并方式是让 LLM 把多条描述综合成一条:
# 实体合并的简化逻辑
entities = kg_entities.query(entity_name="维生素C")
merged_description = llm.invoke(
MERGE_ENTITIES_PROMPT,
entities=[e.description for e in entities]
)
kg_final_entities.create(
entity_name="维生素C",
entity_description=merged_description
)四、工程实践:一套完整系统的技术架构
4.1 整体架构
┌─────────────┐ ┌──────────────┐
│ 前端/App │ │ 企业微信 │
└──────┬──────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────────────────┐
│ agentic-base (Java Spring Boot) │
│ 网关层:认证、路由、会话管理 │
│ │
│ ┌────────────┐ ┌─────────────────┐ │
│ │ SSE代理 │ │ 企微消息处理 │ │
│ │ 服务发现 │ │ MQ消费/生产 │ │
│ │ 流量控制 │ │ Redis锁/去重 │ │
│ └────────────┘ └─────────────────┘ │
└────────────────┬─────────────────────────┘
│ HTTP (SSE流式 / 阻塞)
▼
┌──────────────────────────────────────────┐
│ vtn-agentic (Python FastAPI) │
│ AI引擎:RAG + 知识图谱 + LLM推理 │
│ │
│ ┌────────────────────────────────┐ │
│ │ RAG 编排器 │ │
│ │ ┌──────────┐ ┌─────────────┐ │ │
│ │ │ 查询改写 │ │ 知识检索 │ │ │
│ │ │ 4路并行 │ │ 5路并行 │ │ │
│ │ └──────────┘ └─────────────┘ │ │
│ │ ┌──────────┐ ┌─────────────┐ │ │
│ │ │ 答案生成 │ │ 追问建议 │ │ │
│ │ │ qwen-max │ │ qwen-max │ │ │
│ │ └──────────┘ └─────────────┘ │ │
│ └────────────────────────────────┘ │
└────────────────┬─────────────────────────┘
│
┌────────────┼────────────┬──────────┐
▼ ▼ ▼ ▼
PostgreSQL RabbitMQ one-api FastGPT
(PGVector) (异步持久化) (LLM代理) (知识库)4.2 多模型策略
不是所有任务都需要最强的模型。我们的模型选择策略:
| 任务 | 模型 | 原因 |
|---|---|---|
| 实体域识别 | GPT-4o | 需要强推理理解产品关系 |
| 查询扩展 | GPT-4o | 需要多角度改写 |
| 疾病/症状提取 | GPT-4o | 医学实体识别要高精度 |
| 意图识别 | GPT-4o | 分类任务要准确率 |
| Embedding(检索) | text-embedding-3-small | 够用、快、便宜 |
| Embedding(实体匹配) | text-embedding-3-large | 匹配精度要求更高 |
| 答案生成 | qwen-max | 中文好、成本低 |
| 追问建议 | qwen-max | 简单任务 |
| 健康报告 OCR | GPT-4o | 需要视觉能力 |
| 健康报告分析 | doubao-pro-128k | 超长上下文 |
| 结果重排序 | gpt-4o-mini | 简单排序、最便宜 |
所有模型通过 one-api 代理统一接入,格式统一为 OpenAI 兼容格式。换模型只需在 one-api 后台改配置,代码不动。
4.3 一次问答的完整时序
0ms 前端: POST /api/conversation/completions
│
8ms agentic-base: SSO认证 → 服务发现(轮询) → 创建SseEmitter
│ [新线程] POST → vtn-agentic
│
18ms vtn-agentic: 加载聊天历史 (PostgreSQL)
│
│ ─── 4路并行LLM改写 (gpt-4o) ───
│ 实体域识别: 混合搜索(BM25+向量) → LLM提取 ─── 850ms
│ 查询扩展: LLM生成3个变体 ─── 800ms
│ 疾病提取: LLM提取 ─── 600ms
│ 意图识别: LLM分类 ─── 500ms
│ 总计: ~800ms (并行)
│
818ms Embedding: text-embedding-3-small → 768维向量 ~100ms
│
│ ─── 5路并行PGVector查询 ─── ~80ms
│ 合并 + 过滤 ~5ms
│
903ms 构建prompt: System + History + 知识上下文
│
│ ─── qwen-max 流式输出 ───
│ 首token ~500ms
│ 完整输出 ~3000ms
│
3903ms 用户看到完整回答
│
│ ─── 后处理 ───
│ 追问建议 (qwen-max) ~800ms
│ 写PostgreSQL ~30ms
│ 发RabbitMQ ~10ms
│
4743ms 用户看到追问建议,流程结束
│
│ (异步) RabbitMQ消费 → agentic-base → MySQL写入 ~50ms4.4 中间件设计要点
SSE 流式代理
Java 网关层用 SseEmitter 透传 Python 引擎的 SSE 流:
SseEmitter emitter = new SseEmitter(5 * 60 * 1000L);
new Thread(() -> {
restTemplate.execute(url, HttpMethod.POST, request -> {
request.getHeaders().set("Accept", "text/event-stream");
}, response -> {
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.getBody()));
String line;
String eventType = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith("event:")) {
eventType = line.substring(6).trim();
} else if (line.startsWith("data:")) {
emitter.send(SseEmitter.event()
.name(eventType).data(line.substring(5)));
}
}
emitter.complete();
});
}).start();踩坑: Nginx 默认会缓冲 SSE 响应,导致前端收不到实时流。需要配置 proxy_buffering off。
自研服务发现
vtn-agentic 是 Python 服务,注册不到 Nacos/Eureka。我们自研了一套轻量级方案:
vtn-agentic 启动 → POST /agentDiscovery/register (注册)
agentic-base 定时 → 异步HTTP探针检查 /health
健康 → status=READY
不健康 → status=NOT_READY
持续不健康30分钟 → 删除实例
选实例 → agent_discovery 表 WHERE status=READY → 轮询Redis 分布式锁
聊天数据持久化时,vtn-agentic 可能对同一对话并发发多条 MQ 消息。用 Redis 分布式锁防止重复写入:
String lockKey = "agentic-base:agent-data-save:"
+ appId + ":" + idCode + ":" + chatId;
DRedisLock lock = new DRedisLock(lockKey);
if (lock.tryLock()) {
try {
saveChatData(chatSaveDTO);
} finally {
lock.unlock();
}
}五、Agent 的挑战与我们的应对
5.1 上下文长度有限
问题: GPT-4o 128K 上下文看起来够用,但考虑成本和延迟,实际可用的很有限。
应对: 旧消息截断(保留前 35 字符)+ 长期记忆摘要 + 检索式历史(只加载最相关的几轮对话,而非最近的全部)。
5.2 LLM 幻觉
问题: 大模型可能编造知识库中没有的内容。
应对: 在 prompt 中严格约束"只根据 <Data> 中的内容回答"。同时存储 message_context 记录每次检索到的知识,方便追溯和优化。
5.3 成本控制
问题: 一次问答涉及 7+ 次 LLM 调用(4 路改写 + 答案生成 + 追问建议 + 可能的重排序),token 消耗大。
应对: 多模型策略——推理任务用 GPT-4o,生成任务用 qwen-max(成本低 5-10 倍),简单排序用 gpt-4o-mini。Embedding 结果缓存到磁盘,避免重复调用。
5.4 中文分词
问题: 向量搜索对缩写("VC")、产品编号("VTN-2024-A001")效果差。
应对: 混合搜索——BM25 关键词搜索做兜底。Jieba 中文分词后用 BM25 打分,和向量搜索结果归一化加权合并。
5.5 知识更新
问题: 产品更新了(新成分、新功效),知识库不及时更新会导致回答过时。
应对: 飞书机器人命令 + 管理后台双通道更新。管理员在飞书群里发 updateKnowledgeGraphByName,BEE+ 就能触发增量更新。
六、总结与展望
6.1 我们做到了什么
一套完整的 AI 智能客服系统,覆盖:
- 基于知识图谱的 RAG 问答
- 混合检索(PGVector + BM25)
- 多模型编排(GPT-4o / qwen-max / text-embedding-3)
- 查询改写 + 意图识别 + 实体域识别
- 健康报告 OCR + 分析 + 产品推荐
- 多渠道接入(App / 企微 / 飞书)
- SSE 流式响应 + 自研服务发现 + MQ 异步持久化
6.2 Agent 的下一步
参考行业趋势,我们的系统未来可以往这几个方向演进:
- 多 Agent 协作:不同 Agent 分工负责产品咨询、售后服务、订单查询,通过协调器统一调度
- 反思机制(Reflection):Agent 生成回答后自动检查质量,不合格就重新生成
- 长期记忆增强:利用用户历史偏好做个性化推荐,而不只是基于当前问题检索
- 工具扩展:接入更多外部工具(天气查询、物流追踪、订单系统),让 Agent 能完成更复杂的任务
- 微调优化:基于用户反馈数据微调开源模型,提升专业领域的回答质量
Agent 不是一蹴而就的。就像 LILIAN WENG 说的:Agent 的成败将是决定这一场 GPT 革命是否是新一代工业革命的关键。 我们已经在路上了。
参考资料:
- Lilian Weng: LLM Powered Autonomous Agents
- 吴恩达: Agent 四种设计模式(Reflection / Tool Use / Planning / Multiagent Collaboration)
- XAgent 双循环机制、MetaGPT SOP 编码、MemGPT 记忆管理
- 微信公众号:格林 | 神州问学《Agent如何帮助大模型"增强记忆"?》
- 微信公众号:神州问学《AI Agent规划能力全面拆解》