一次完整的AI工程化落地之旅,记录从零到一构建企业级智能知识库系统的全过程
技术栈: Spring AI Alibaba + Vue 3 + ElasticSearch + Redis
目录
- 一、引言:为什么需要企业智能知识库?
- 二、核心概念:深入理解RAG技术栈
- [三、技术选型:为什么选择Spring AI Alibaba?](#三、技术选型:为什么选择Spring AI Alibaba?)
- 四、架构设计:微服务+RAG的完美融合
- 五、核心实现:文档向量化全流程
- 六、智能对话:RAG检索增强生成
- 七、高级特性:Multi-Agent协作框架
- 八、工程化实践:从开发到生产
- 九、实战经验:踩过的坑与解决方案
一、引言:为什么需要企业智能知识库?
1.1 企业知识管理的痛点
在数字化转型的浪潮中,每个企业都积累了海量的知识资产:技术文档、业务规范、API文档、FAQ、代码规范等等。但这些知识往往面临三大挑战:
- 分散难查找: 知识分散在Confluence、GitLab、Notion、Word等多个平台
- 获取门槛高: 新员工需要花费大量时间熟悉文档,技术人员反复查阅规范
- 更新不及时: 文档版本管理混乱,员工不知道哪个是最新版本
1.2 AI带来的范式转变
传统的知识管理是关键词检索:用户输入关键词→系统返回匹配文档→用户自己阅读理解。这种方式效率低、体验差。
而基于AI的知识助手实现了范式转变:用户提出问题→AI理解意图→检索相关知识→生成准确答案。用户获得的不再是文档列表,而是直接可用的答案。
1.3 本文要解决的问题
本文将分享我们从零到一构建企业智能知识库助手的完整经验,包括:
- 技术选型: 为什么选择Spring AI Alibaba而非LangChain4j?
- 架构设计: 如何将RAG技术融入微服务架构?
- 核心实现: 文档向量化、语义检索、答案生成的完整流程
- 高级特性: Multi-Agent协作、Graph Workflow、Function Call
- 工程化: 性能优化、监控告警、部署运维的最佳实践
二、核心概念:深入理解RAG技术栈
在开始实践之前,我们需要理解几个关键概念。我尽量用通俗的语言解释,避免晦涩的学术术语。
2.1 什么是RAG?
RAG(Retrieval-Augmented Generation) 直译是"检索增强生成",听起来很玄乎,其实原理很简单:
传统LLM: 直接问AI→AI基于训练数据回答→可能出现幻觉(编造答案)
RAG模式: 先从知识库检索相关内容→把检索结果喂给AI→AI基于检索内容回答→答案准确可溯源
用一个比喻:传统LLM像考试时靠记忆答题,RAG像开卷考试,允许翻书找答案。
2.2 Embedding与向量化
Embedding是将文本转换为向量(一串数字)的过程。为什么要转成向量?因为:
- 语义相似度: 意思相近的文本,向量也相近
- 高效检索: 向量数据库可以快速找出最相似的向量
举个例子:
"数据库表设计规范" → [0.12, 0.34, 0.56, ..., 0.78] (1536维向量)
"MySQL建表规范" → [0.13, 0.35, 0.55, ..., 0.77] (相似度>0.95)
"天气怎么样" → [0.89, 0.21, 0.05, ..., 0.32] (相似度<0.1)
2.3 向量数据库
向量数据库专门用来存储和检索向量,常见的有:
- ElasticSearch: 我们的选择,同时支持向量检索和全文检索
- Milvus: 专业向量数据库,性能更高但运维复杂
- Pinecone: 云服务,按量付费
我们选择ElasticSearch因为它能同时做向量检索 (语义相似度)和全文检索(关键词匹配),两者结合效果更好。
2.4 RAG工作流程
让我用一个完整的流程图展示RAG的工作原理:
文档上传
检索
用户提问: 数据库表设计规范?
问题向量化
向量数据库检索Top-5
获取相关文档片段
构建Prompt
LLM生成回答
返回答案+来源
知识库
文档解析
智能分块
向量化
向量数据库
2.5 核心技术点
理解了RAG后,实现企业知识库需要掌握几个技术点:
- 文档解析: PDF/Word/Markdown等格式的文本提取
- 智能分块: 将长文档切成合适大小的片段(chunk)
- 向量化: 使用Embedding模型生成向量
- 混合检索: 向量检索+全文检索,提升准确率
- Prompt工程: 设计合理的提示词,让AI生成高质量答案
三、技术选型:为什么选择Spring AI Alibaba?
3.1 主流方案对比
实现RAG应用有多种选择,我对比了三种主流方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LangChain(Python) | 生态最成熟,功能最全 | 与Java技术栈割裂,需要跨语言调用 | Python项目 |
| LangChain4j | Java原生,社区活跃 | 需要大量代码组装,缺少开箱即用的RAG | 对灵活性要求高的项目 |
| Spring AI Alibaba | 与Spring无缝集成,RAG开箱即用 | 相对较新,文档较少 | Java+Spring项目(我们的情况) |
3.2 为什么选Spring AI Alibaba?
我们最终选择Spring AI Alibaba,主要基于以下考虑:
1. 原生Spring体验
java
// Spring AI Alibaba - 原生Spring风格
@Autowired
private ChatClient chatClient;
@Autowired
private VectorStore vectorStore;
public String chat(String question) {
return chatClient.prompt()
.user(question)
.advisors(new QuestionAnswerAdvisor(vectorStore)) // 一行代码启用RAG
.call()
.content();
}
无需学习新的API,Spring开发者零门槛上手。
2. RAG开箱即用
Spring AI Alibaba内置完整的RAG Workflow:
- DocumentReader: 支持PDF、Word、Markdown等格式解析
- TextSplitter: 智能文档分块
- EmbeddingModel: 向量化模型
- VectorStore: 向量数据库集成
- QuestionAnswerAdvisor: RAG增强器
这些组件开箱即用,不需要从零实现。
3. 阿里云生态
- 通义千问: 国产大模型,价格实惠(¥0.004/1K tokens)
- 文本向量化: text-embedding-v3模型,1536维向量
- 企业级稳定性: 阿里云背书,经过大规模生产验证
4. 对比LangChain4j
| Spring AI Alibaba | LangChain4j | |
|---|---|---|
| 集成难度 | ⭐⭐⭐⭐⭐ 声明式配置 | ⭐⭐⭐ 需要手动组装 |
| RAG能力 | ⭐⭐⭐⭐⭐ 开箱即用 | ⭐⭐⭐⭐ 功能完整但需组装 |
| 向量数据库 | ⭐⭐⭐⭐⭐ 内置ES/Milvus | ⭐⭐⭐⭐ 支持但需配置 |
| 可观测性 | ⭐⭐⭐⭐⭐ SDK级调用链 | ⭐⭐⭐ 需手动埋点 |
| 企业特性 | ⭐⭐⭐⭐⭐ 生产级稳定 | ⭐⭐⭐ 社区驱动 |
3.3 整体技术栈
最终确定的技术栈:
后端:
- Spring Boot 3.3.0 + Spring Cloud 2023.0.2
- Spring AI Alibaba 1.0.0.2-RC1
- MySQL 8.4.0 + MyBatis-Plus 3.5.6
- Redis 7.0 (会话缓存)
- ElasticSearch 8.11.0 (向量+全文检索)
- 通义千问qwen-plus (对话模型)
- text-embedding-v3 (向量模型)
前端:
- Vue 3.5 + TypeScript 5.7
- Element Plus 2.9 (UI组件)
- Pinia 2.2 (状态管理)
- Vite 6.4 (构建工具)
四、架构设计:微服务+RAG的完美融合
4.1 整体架构图
让我用一个完整的架构图展示系统设计:
外部服务
数据层
Spring AI Alibaba核心
应用层
接入层
前端层
调用
存储
调用
检索
Vue 3 Web应用
移动端H5
API Gateway
知识管理服务
对话服务
Prompt管理
ChatClient
VectorStore
EmbeddingModel
QuestionAnswerAdvisor
MySQL
业务数据
Redis
会话缓存
ElasticSearch
向量索引
OSS
文件存储
通义千问
qwen-plus
Embedding
text-embedding-v3
4.2 分层架构设计
我们采用经典的DDD分层架构:
Controller层 → Service层 → Repository层 → Mapper层 → Database
↓ ↓ ↓
参数校验 业务逻辑 数据持久化
返回Response 事务管理 lambda查询
4.3 微服务划分
虽然是单一功能,但我们预留了拆分空间:
当前 : 单一微服务 mythos-knowledge-assistant
未来可拆分为:
knowledge-management-service: 知识管理chat-service: 对话服务retrieval-service: 检索服务prompt-service: Prompt管理
为什么当前不拆分?
- 业务内聚性高,强行拆分会增加复杂度
- 避免过度设计,先快速迭代验证
- 单一服务性能足够(QPS 500+)
4.4 数据模型设计
核心表结构
contains
contains
knowledge_document
bigint
id
PK
varchar
doc_id
UK
varchar
doc_title
tinyint
doc_type
varchar
category
int
chunk_count
tinyint
status
knowledge_chunk
bigint
id
PK
varchar
chunk_id
UK
varchar
doc_id
FK
text
content
varchar
vector_id
int
chunk_tokens
chat_session
bigint
id
PK
varchar
session_id
UK
bigint
user_id
varchar
session_title
tinyint
session_status
int
message_count
chat_message
bigint
id
PK
varchar
message_id
UK
varchar
session_id
FK
varchar
role
text
content
text
retrieved_docs
tinyint
satisfaction
存储分离策略
- MySQL: 业务数据(文档元数据、对话历史)
- ElasticSearch: 向量索引(1536维向量+全文检索)
- Redis: 会话缓存(最近3轮对话、用户状态)
- COS: 原始文件(PDF、Word等)
这样设计的好处:
- 各存储引擎发挥所长
- MySQL不存储大文本,性能更好
- 向量检索由ES优化,速度快
五、核心实现:文档向量化全流程
文档向量化是知识库的基础,我将详细拆解整个流程。
5.1 文档上传流程
MySQL ElasticSearch EmbeddingModel 腾讯云COS DocumentService Controller MySQL ElasticSearch EmbeddingModel 腾讯云COS DocumentService Controller chunk_size=500 overlap=100 用户 上传PDF文档 参数校验(格式/大小) uploadDocument() 上传原始文件 返回文件URL 解析文档(PDFReader) 智能分块(TextSplitter) 批量向量化 返回1536维向量 存储向量+文本 保存元数据 返回doc_id 上传成功 用户
5.2 代码实现详解
1. 文档解析
Spring AI Alibaba提供了多种DocumentReader:
java
@Service
public class DocumentServiceImpl implements DocumentService {
@Autowired
private VectorStore vectorStore;
@Autowired
private TextSplitter textSplitter;
@Autowired
private TencentCosService cosService;
public Response<String> uploadDocument(MultipartFile file,
String title,
Integer docType,
String category) {
// 1. 上传文件到COS
String fileUrl = cosService.uploadFile(file);
// 2. 根据文件类型选择解析器
List<Document> documents = parseDocument(file);
// 3. 智能分块
List<Document> chunks = textSplitter.apply(documents);
// 4. 向量化并存储到ElasticSearch
vectorStore.add(chunks);
// 5. 保存元数据到MySQL
String docId = saveMetadata(title, docType, category,
fileUrl, chunks.size());
return Response.success(docId);
}
private List<Document> parseDocument(MultipartFile file) {
String filename = file.getOriginalFilename();
Resource resource = new MultipartFileAdapter(file);
DocumentReader reader;
if (filename.endsWith(".pdf")) {
// PDF解析
reader = new PagePdfDocumentReader(resource);
} else if (filename.endsWith(".docx") || filename.endsWith(".doc")) {
// Word解析(使用Apache Tika)
reader = new TikaDocumentReader(resource);
} else if (filename.endsWith(".md")) {
// Markdown解析
reader = new TextResourceDocumentReader(resource);
} else {
throw new BusinessException("不支持的文件格式");
}
return reader.get();
}
}
2. 智能分块策略
为什么要分块?
- LLM有上下文长度限制(如8K tokens)
- 小块更精准,大块包含噪音
- 合理分块能平衡精度和召回率
我们的分块配置:
java
@Bean
public TextSplitter textSplitter() {
return new TokenTextSplitter(
500, // 每个chunk的Token数量
100 // chunk之间的重叠(保持语义连贯)
);
}
为什么重叠100 tokens?
假设一个规范分散在chunk边界:
Chunk 1: ...索引设计原则:1.高区分度字段在前 2.联合
Chunk 2: 索引最多5个字段 3.避免使用函数索引...
如果没有重叠,用户问"联合索引最多几个字段",可能检索不到。有100 tokens重叠,Chunk 1的结尾会包含在Chunk 2的开头,确保完整性。
3. 向量化与存储
java
// Spring AI Alibaba自动处理向量化
vectorStore.add(chunks); // 一行代码完成:向量化+存储
底层发生了什么?
- 调用text-embedding-v3生成向量
- 将向量存入ElasticSearch的dense_vector字段
- 同时存储原文到text字段(用于全文检索)
ElasticSearch索引结构:
json
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word" // 中文分词
},
"embedding": {
"type": "dense_vector",
"dims": 1536,
"index": true,
"similarity": "cosine" // 余弦相似度
},
"doc_id": {"type": "keyword"},
"section_title": {"type": "text"}
}
}
}
5.3 性能优化点
1. 批量向量化
java
// ❌ 不好:逐个向量化,慢
for (Document doc : documents) {
vectorStore.add(List.of(doc));
}
// ✅ 好:批量向量化,快
vectorStore.add(documents); // 底层会批量调用Embedding API
批量调用可以减少网络开销,提升5-10倍性能。
2. 异步处理
文档向量化是耗时操作(10MB PDF需要30-60秒),我们采用异步处理:
java
@Async
public CompletableFuture<String> uploadDocumentAsync(MultipartFile file) {
String docId = uploadDocument(file, ...);
return CompletableFuture.completedFuture(docId);
}
用户上传后立即返回,后台异步处理。前端轮询状态:
javascript
// 上传文档
const { docId } = await uploadDocument(formData);
// 轮询处理状态
const interval = setInterval(async () => {
const { status } = await getDocumentStatus(docId);
if (status === 'COMPLETED') {
clearInterval(interval);
ElMessage.success('文档处理完成!');
}
}, 2000);
六、智能对话:RAG检索增强生成
文档向量化完成后,就可以进行智能问答了。
6.1 对话流程
是
否
用户提问
是否新会话?
创建session_id
加载历史对话
问题向量化
向量检索Top-5
相似度过滤>0.7
构建RAG Prompt
LLM生成回答
提取来源文档
推荐相关问题
保存对话历史
返回用户
6.2 核心代码实现
java
@Service
public class ChatServiceImpl implements ChatService {
@Autowired
private ChatClient chatClient; // Spring AI提供
@Autowired
private SessionService sessionService;
@Autowired
private ChatMessageRepository messageRepository;
public Response<ChatResponseDTO> chat(ChatRequest request) {
long startTime = System.currentTimeMillis();
// 1. 获取或创建会话
String sessionId = sessionService.getOrCreateSession(
request.getUserId(), request.getSessionId());
// 2. 加载对话历史(最近3轮)
List<ChatMessage> history = sessionService.getRecentMessages(sessionId, 3);
// 3. 构建Prompt
Prompt prompt = buildPrompt(request.getQuestion(), history);
// 4. RAG增强的对话 (核心)
ChatResponse chatResponse = chatClient.prompt(prompt)
.advisors(questionAnswerAdvisor) // 启用RAG
.call();
// 5. 提取答案和来源
String answer = chatResponse.getResult().getOutput().getContent();
List<DocumentSourceDTO> sources = extractSources(chatResponse);
// 6. 推荐相关问题
List<String> relatedQuestions = generateRelatedQuestions(
request.getQuestion(), answer);
// 7. 保存对话历史
saveChatHistory(sessionId, request.getQuestion(), answer, sources);
// 8. 组装响应
return Response.success(ChatResponseDTO.builder()
.sessionId(sessionId)
.answer(answer)
.sources(sources)
.relatedQuestions(relatedQuestions)
.responseTime(System.currentTimeMillis() - startTime)
.build());
}
}
6.3 Prompt工程
Prompt是RAG的灵魂,我们精心设计了多层Prompt:
1. 系统Prompt (定义角色)
text
你是神话公司的企业智能知识助手,名为"小智"。
你的职责是帮助员工快速、准确地获取企业内部知识。
【你的能力】
- 回答技术规范、业务流程、操作指引等问题
- 提供代码示例、配置示例
- 推荐相关文档和最佳实践
【你的原则】
1. 只基于知识库内容回答,不编造信息
2. 如果知识库中没有相关信息,明确告知用户
3. 回答要准确、完整、易懂,使用Markdown格式
4. 标注答案来源(文档名称和章节)
5. 保护用户隐私和企业信息安全
2. RAG Prompt (检索内容注入)
这部分由Spring AI Alibaba自动生成:
text
【知识库内容】
{context} // 自动填充检索到的Top-5文档
【用户问题】
{question}
【回答要求】
1. 基于上述知识库内容回答
2. 如果知识库中没有相关信息,回答"知识库中暂无相关信息"
3. 使用Markdown格式组织答案
4. 标注答案来源
3. 相关问题生成Prompt
java
private List<String> generateRelatedQuestions(String question, String answer) {
String prompt = String.format("""
基于用户的问题和你的回答,生成3个相关的问题,引导用户深入了解。
用户问题:%s
你的回答:%s
要求:
1. 问题要有递进关系(从基础到进阶)
2. 问题要具体,不要太宽泛
3. 只返回问题,不要编号,每行一个
""", question, answer);
String result = chatClient.prompt().user(prompt).call().content();
return Arrays.asList(result.split("\n"));
}
6.4 混合检索策略
纯向量检索有时会漏掉关键词匹配,我们采用混合检索:
java
@Service
public class RetrievalServiceImpl implements RetrievalService {
@Autowired
private VectorStore vectorStore;
@Autowired
private ElasticsearchClient esClient;
public List<Document> hybridSearch(String question, int topK) {
// 1. 向量检索(语义相似度)
List<Document> vectorResults = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(topK * 2)
);
// 2. 全文检索(关键词匹配)
List<Document> fullTextResults = fullTextSearch(question, topK * 2);
// 3. 结果融合:向量70% + 全文30%
Map<String, SearchResult> merged = new HashMap<>();
for (Document doc : vectorResults) {
float score = doc.getScore() * 0.7f;
merged.put(doc.getId(), new SearchResult(doc, score));
}
for (Document doc : fullTextResults) {
float score = doc.getScore() * 0.3f;
merged.merge(doc.getId(), new SearchResult(doc, score),
(old, newVal) -> {
old.setScore(old.getScore() + newVal.getScore());
return old;
});
}
// 4. 排序并返回Top-K
return merged.values().stream()
.sorted(Comparator.comparing(SearchResult::getScore).reversed())
.limit(topK)
.map(SearchResult::getDocument)
.collect(Collectors.toList());
}
private List<Document> fullTextSearch(String question, int topK) {
// 使用ElasticSearch的BM25算法
SearchRequest request = SearchRequest.of(s -> s
.index("knowledge_vector")
.query(q -> q.multiMatch(m -> m
.query(question)
.fields("content^3", "doc_title^2", "section_title^2")
))
.size(topK)
);
SearchResponse<Document> response = esClient.search(request, Document.class);
return response.hits().hits().stream()
.map(Hit::source)
.collect(Collectors.toList());
}
}
混合检索的效果提升:
实际测试中,混合检索比纯向量检索准确率提升15-20%,特别是对包含专业术语的问题效果明显。
6.5 会话管理与上下文
多轮对话的关键是维护上下文:
Redis缓存设计
# 会话基本信息(Hash)
Key: session:{session_id}
TTL: 15分钟
Fields:
- user_id: 用户ID
- session_id: 会话ID
- message_count: 消息数量
- last_activity: 最后活动时间
# 对话历史(List,最多10条)
Key: session:history:{session_id}
TTL: 15分钟
Value: [message1, message2, ..., message10]
# 用户当前会话映射(String)
Key: user:session:{user_id}
TTL: 30分钟
Value: session_id
代码实现
java
@Service
public class SessionServiceImpl implements SessionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String SESSION_KEY = "session:";
private static final String HISTORY_KEY = "session:history:";
private static final int SESSION_TTL = 15 * 60; // 15分钟
public List<ChatMessage> getRecentMessages(String sessionId, int limit) {
String key = HISTORY_KEY + sessionId;
// 从Redis获取最近N条
List<Object> messages = redisTemplate.opsForList()
.range(key, -limit, -1);
if (CollectionUtils.isEmpty(messages)) {
// Redis没有,从MySQL加载
messages = loadFromDatabase(sessionId, limit);
// 回填Redis
if (!messages.isEmpty()) {
redisTemplate.opsForList().rightPushAll(key, messages);
redisTemplate.expire(key, SESSION_TTL, TimeUnit.SECONDS);
}
}
return messages.stream()
.map(obj -> (ChatMessage) obj)
.collect(Collectors.toList());
}
public void saveMessage(ChatMessage message) {
// 1. MySQL持久化
messageRepository.saveCustom(message);
// 2. Redis缓存(最近10条)
String key = HISTORY_KEY + message.getSessionId();
redisTemplate.opsForList().rightPush(key, message);
Long size = redisTemplate.opsForList().size(key);
if (size > 10) {
redisTemplate.opsForList().trim(key, -10, -1);
}
// 3. 刷新TTL
redisTemplate.expire(key, SESSION_TTL, TimeUnit.SECONDS);
}
}
七、高级特性:Multi-Agent协作框架
简单的RAG已经能解决80%的问题,但对于复杂场景,我们引入了Multi-Agent协作。
7.1 什么是Multi-Agent?
传统RAG是单一智能体:检索→生成,一气呵成。
Multi-Agent是多智能体协作:每个Agent负责特定任务,协作完成复杂目标。
举个例子,用户问:"帮我分析一下订单系统的数据库设计,并给出优化建议"
单一Agent: 检索相关文档→生成回答(可能不够全面)
Multi-Agent:
- QueryAgent: 理解问题,拆解为子任务
- RetrievalAgent: 检索订单相关的表设计文档
- AnalysisAgent: 分析当前设计的问题
- RecommendAgent: 基于最佳实践给出优化建议
- AnswerAgent: 整合所有结果,生成完整回答
7.2 Agent架构设计
我们基于Spring AI Alibaba的Graph框架实现Multi-Agent:
java
public interface Agent {
/**
* Agent名称
*/
String getName();
/**
* Agent能力描述
*/
String getDescription();
/**
* 执行Agent任务
*/
AgentContext execute(AgentContext context);
/**
* 判断是否应该执行该Agent
*/
boolean shouldExecute(AgentContext context);
}
实现的Agent
- QueryAgent: 问题理解与拆解
- RetrievalAgent: 知识检索
- AnswerAgent: 答案生成
- QualityAgent: 答案质量检查
7.3 Graph Workflow
使用Spring AI的Graph框架编排Agent:
java
@Service
public class KnowledgeGraphService {
@Autowired
private QueryAgent queryAgent;
@Autowired
private RetrievalAgent retrievalAgent;
@Autowired
private AnswerAgent answerAgent;
@Autowired
private QualityAgent qualityAgent;
public ChatResponse processWithGraph(String question) {
// 定义Graph Workflow
var graph = Graph.builder(ChatState::new)
// 节点1:问题分析
.addNode("query", context -> {
return queryAgent.execute(context);
})
// 节点2:知识检索
.addNode("retrieval", context -> {
return retrievalAgent.execute(context);
})
// 节点3:答案生成
.addNode("answer", context -> {
return answerAgent.execute(context);
})
// 节点4:质量检查
.addNode("quality", context -> {
return qualityAgent.execute(context);
})
// 定义边(执行顺序)
.addEdge("query", "retrieval")
.addEdge("retrieval", "answer")
.addEdge("answer", "quality")
// 条件边:质量不合格重新生成
.addConditionalEdge("quality", context -> {
return context.getQualityScore() > 0.8
? "END" // 质量合格,结束
: "retrieval"; // 质量不合格,重新检索
})
.build();
// 执行Graph
ChatState initialState = new ChatState();
initialState.setQuestion(question);
ChatState finalState = graph.invoke(initialState);
return finalState.getResponse();
}
}
7.4 Graph可视化
Graph执行流程:
用户提问
拆解子问题
检索到Top-5文档
生成初步答案
质量<0.8,重新检索
质量≥0.8,返回答案
QueryAgent
RetrievalAgent
AnswerAgent
QualityAgent
理解问题意图
识别是否需要工具
拆解复杂问题
混合检索(向量+全文)
结果重排序
上下文扩展
基于检索结果生成
添加代码示例
标注来源
检查答案完整性
验证答案准确性
评估用户满意度
7.5 实际效果对比
测试问题: "帮我分析订单系统的数据库设计,有哪些优化点?"
单一RAG:
- 检索时间: 200ms
- 生成时间: 1.5s
- 总时间: 1.7s
- 答案质量: 7/10 (只给出了表设计,没有优化建议)
Multi-Agent:
- QueryAgent: 100ms (拆解为"表设计"+"性能优化"两个子问题)
- RetrievalAgent: 300ms (检索更全面)
- AnswerAgent: 2s
- QualityAgent: 500ms
- 总时间: 2.9s
- 答案质量: 9/10 (完整分析+具体优化建议+示例代码)
结论: Multi-Agent响应时间增加70%,但答案质量提升28%,值得!
八、工程化实践:从开发到生产
理论再好,也要能跑起来。这一章分享工程化实践。
8.1 性能优化
1. 向量检索缓存
相同问题重复查询ES浪费资源,我们用Redis缓存:
java
@Service
public class RetrievalServiceImpl implements RetrievalService {
@Autowired
private RedisTemplate<String, List<Document>> redisTemplate;
public List<Document> vectorSearchWithCache(String question, int topK) {
// 计算问题的MD5哈希
String cacheKey = "vector:cache:" + DigestUtils.md5Hex(question);
// 尝试从缓存获取
List<Document> cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
log.info("向量检索命中缓存, question={}", question);
return cached;
}
// 缓存未命中,查询ES
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(topK)
);
// 存入缓存,TTL=1小时
redisTemplate.opsForValue().set(cacheKey, results, 1, TimeUnit.HOURS);
return results;
}
}
效果: 缓存命中率35%,这部分请求延迟从200ms降至5ms。
2. 批量向量化
java
// ❌ 低效:逐个向量化
for (Document doc : documents) {
embeddingModel.embed(doc.getContent());
}
// ✅ 高效:批量向量化
List<String> texts = documents.stream()
.map(Document::getContent)
.collect(Collectors.toList());
List<float[]> embeddings = embeddingModel.embed(texts); // 批量调用
效果: 1000个chunk向量化时间从5分钟降至30秒。
3. 异步文档处理
java
@Service
public class DocumentServiceImpl {
@Async("taskExecutor")
public CompletableFuture<String> uploadDocumentAsync(MultipartFile file) {
try {
String docId = uploadDocument(file, ...);
return CompletableFuture.completedFuture(docId);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
@Configuration
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("doc-upload-");
executor.initialize();
return executor;
}
}
8.2 监控与可观测性
Spring AI Alibaba内置可观测性,自动记录:
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: "*"
metrics:
export:
prometheus:
enabled: true
tracing:
sampling:
probability: 1.0 # 生产环境建议0.1
spring:
ai:
observability:
enabled: true
自动追踪的指标:
- 模型调用: prompt、response、tokens、耗时
- 向量检索: query、results、similarity、耗时
- 工具调用: function name、params、result
Grafana Dashboard
我们配置的监控大盘:
核心指标:
- QPS: 每秒请求数
- P95响应时间: <3秒
- 成功率: >99%
- LLM调用耗时: <2秒
- 向量检索耗时: <500ms
告警规则:
yaml
# Prometheus告警
groups:
- name: knowledge_assistant
rules:
- alert: HighResponseTime
expr: http_request_duration_seconds{job="knowledge-assistant"} > 3
for: 5m
annotations:
summary: "响应时间过高"
description: "P95响应时间超过3秒"
- alert: LowSuccessRate
expr: http_request_success_rate{job="knowledge-assistant"} < 0.99
for: 5m
annotations:
summary: "成功率过低"
description: "接口成功率低于99%"
8.3 部署方案
Docker Compose(开发/测试环境)
yaml
version: '3.8'
services:
knowledge-assistant:
build: .
ports:
- "8092:8092"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
- MYSQL_HOST=mysql
- REDIS_HOST=redis
- ELASTICSEARCH_URIS=http://elasticsearch:9200
depends_on:
- mysql
- redis
- elasticsearch
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=knowledge_assistant
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=${ES_PASSWORD}
volumes:
- es-data:/usr/share/elasticsearch/data
volumes:
mysql-data:
es-data:
Kubernetes(生产环境)
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: knowledge-assistant
spec:
replicas: 3
selector:
matchLabels:
app: knowledge-assistant
template:
metadata:
labels:
app: knowledge-assistant
spec:
containers:
- name: knowledge-assistant
image: registry.mythos.com/knowledge-assistant:1.0.0
ports:
- containerPort: 8092
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DASHSCOPE_API_KEY
valueFrom:
secretKeyRef:
name: ai-secrets
key: dashscope-api-key
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8092
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8092
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: knowledge-assistant-service
spec:
selector:
app: knowledge-assistant
ports:
- port: 80
targetPort: 8092
type: ClusterIP
8.4 成本优化
1. LLM成本
通义千问定价:
- qwen-turbo: ¥0.003/1K tokens (便宜但效果一般)
- qwen-plus: ¥0.004/1K tokens (性价比最高)
- qwen-max: ¥0.02/1K tokens (最强但贵)
我们的策略:
- 简单问答: qwen-plus
- 复杂推理: qwen-max
- Prompt优化: 减少冗余内容,降低token消耗
实测: 平均每次对话消耗1500 tokens,成本¥0.006/次,月活1000用户,月成本约¥180。
2. 向量存储成本
ElasticSearch存储:
- 1篇文档(10KB) → 20个chunks → 20个向量(1536维 × 4字节) = 120KB
- 1000篇文档 ≈ 120MB
- 加上全文索引和元数据,总计约200MB
相比专业向量数据库Milvus,ES更省钱(无需额外部署)。
九、实战经验:踩过的坑与解决方案
理论很美好,实践很骨感。分享我们踩过的坑。
9.1 文档分块踩坑
问题: 最初用固定500字符分块,结果:
- Markdown代码块被截断
- 表格被切分成多个chunk
- 检索效果差
解决: 改用TokenTextSplitter,按Token而非字符,且识别Markdown结构:
java
// ❌ 错误:按字符分块
new CharacterTextSplitter(500, 100);
// ✅ 正确:按Token分块,识别Markdown
new TokenTextSplitter(500, 100);
9.2 向量检索召回率低
问题: 用户问"金额字段用什么类型",检索不到"金额必须用DECIMAL"的文档。
原因: 问题和文档的表述差异大,向量相似度不高。
解决: 混合检索(向量70% + 全文30%)
java
// 全文检索能匹配关键词"金额""字段""类型"
List<Document> fullTextResults = fullTextSearch(question, topK);
// 向量检索理解语义
List<Document> vectorResults = vectorStore.similaritySearch(...);
// 融合结果
return mergeResults(vectorResults, fullTextResults);
效果: 召回率从75%提升到92%。
9.3 会话上下文丢失
问题: 用户追问"它有哪些限制",AI回答"抱歉,我不知道'它'指什么"。
原因: 没有携带上下文。
解决: Redis缓存最近3轮对话
java
List<ChatMessage> history = sessionService.getRecentMessages(sessionId, 3);
// 构建Prompt时加入历史
Prompt prompt = new Prompt(
new SystemMessage(systemPrompt),
...history.stream().map(this::toMessage), // 历史对话
new UserMessage(question) // 当前问题
);
9.4 LLM幻觉问题
问题: 知识库明明没有的内容,AI却编造答案。
原因: Prompt没有约束LLM只基于知识库回答。
解决: 严格的Prompt约束
text
【回答要求】
1. 严格基于上述知识库内容回答
2. 如果知识库中没有相关信息,必须回答"知识库中暂无相关信息"
3. 禁止编造、猜测、假设任何内容
4. 不确定时明确告知用户
效果: 幻觉率从15%降至<3%。
9.5 性能问题
问题: 高峰期响应时间3-5秒,用户体验差。
瓶颈分析:
- 向量检索: 200ms
- MySQL查询历史: 300ms (热点)
- LLM生成: 1.5s
- 总计: 2s+
优化方案:
- Redis缓存会话: MySQL查询降至5ms
- 向量检索缓存: 命中率35%,降至5ms
- 异步文档处理: 不影响对话响应
效果: P95响应时间从3.5s降至1.8s。
附录
A. 相关资源
官方文档:
开源项目:
推荐阅读:
- 《大型语言模型:从理论到实践》
- 《RAG技术原理与实践》
- 《企业级AI应用架构设计》