本章导读
本章介绍AgentScope的RAG(Retrieval Augmented Generation,检索增强生成)系统,这是让Agent能够基于最新知识库回答问题的关键技术。
RAG通过检索外部知识库中的相关信息 ,为LLM提供上下文,从而显著提升回答的准确性和时效性。
本章概览:
- What(是什么):理解RAG的工作原理和架构
- Why(为什么):明确什么时候需要使用RAG
- How(怎样做):完整的实现示例和生产可用案例
12.1 RAG设计理念
什么是RAG?
**RAG(检索增强生成)**是一种混合式的处理流程:
css
用户问题
↓
[检索阶段] 在知识库中搜索相关文档
↓
[增强阶段] 将检索到的文档作为上下文
↓
[生成阶段] LLM基于上下文回答问题
↓
准确、可追溯的答案
RAG的核心优势
| 特性 | 传统LLM | RAG增强 |
|---|---|---|
| 知识时效性 | 依赖训练数据(可能过时) | 基于最新知识库 ✓ |
| 可追溯性 | 黑盒回答 | 可引用具体来源 ✓ |
| 领域专业性 | 通用知识 | 垂直领域专业知识 ✓ |
| 成本 | 需要大模型 | 可用小模型 ✓ |
| 隐私 | 数据上传到厂商 | 私有知识库 ✓ |
RAG的三个核心阶段
1. 检索(Retrieval)
css
目标:从知识库中找到与用户问题最相关的文档
方法:
├─ 关键词匹配(BM25):快速但精度低
├─ 语义相似度(向量):精度高,需要Embedding
└─ 混合方法:结合两者优点
过程:
用户问题 → 向量化 → 在向量库中搜索
↓
返回top-k相关文档
2. 增强(Augmentation)
sql
目标:将检索到的文档融入到LLM的输入中
方式:
├─ 追加到System Prompt
├─ 插入到上下文中
└─ 作为工具输入
结果:
LLM获得了额外的上下文信息,能做出更好的回答
3. 生成(Generation)
arduino
目标:基于增强后的输入,生成准确的答案
特点:
├─ 可以引用具体来源
├─ 回答更专业和准确
└─ 避免"幻觉"(生成虚假信息)
12.2 RAG的工作原理
为什么需要RAG?
1. LLM知识的局限性
vbnet
问题:LLM的训练数据有截止日期,无法回答最新的问题
示例:
Q: "2024年最新的AI法规是什么?"
A: 模型训练于2023年10月,无法回答
解决:RAG可以从最新的知识库中检索相关信息
2. 领域专业性要求
vbnet
问题:通用LLM可能缺乏特定领域的专业知识
示例:
Q: "我们公司的产假政策是什么?"
A: 通用LLM只能给出通用政策,不知道公司具体规定
解决:将公司文件添加到知识库,RAG可以直接回答
3. 成本和隐私
diff
问题:大模型贵,私有信息不能上传
示例:
- 用更小的模型也能获得准确的专业回答
- 私有知识保留在内部,不上传到云端
解决:结合RAG和小模型(如qwen-turbo),低成本高效率
RAG的核心价值
markdown
┌──────────────────────────────┐
│ 知识库 + 小模型 │
│ = 大模型的效果 + 低成本 │
└──────────────────────────────┘
↓
┌──────────────────────────────┐
│ 专业、准确、可追溯 │
│ 的智能问答系统 │
└──────────────────────────────┘
12.3 RAG的实现方式
基础RAG实现
java
public class BasicRAGDemo {
public static void main(String[] args) throws Exception {
Model model = DashScopeModel.builder()
.modelName("qwen-turbo")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build();
// 1. 创建知识库
System.out.println("=== 初始化知识库 ===\n");
Knowledge knowledge = new SimpleEmbeddingKnowledge(
new DashScopeEmbeddingService(),
new InMemoryVectorStore()
);
// 2. 添加公司知识文档
System.out.println("=== 添加知识文档 ===\n");
List<Document> documents = List.of(
new Document(
"产假政策",
"女性员工享受158天产假,其中:" +
"- 产前30天\n" +
"- 产后128天\n" +
"符合条件的可申请延长30天"
),
new Document(
"年假政策",
"年假计算:\n" +
"- 入职1-10年:10天\n" +
"- 入职10-20年:15天\n" +
"- 入职20年以上:20天\n" +
"年假可以跨年结转,最多3年"
),
new Document(
"远程工作政策",
"申请条件:\n" +
"- 入职满6个月\n" +
"- 性能评级不低于B\n" +
"频率:每周最多3天\n" +
"需要主管批准"
),
new Document(
"技术栈",
"后端:Java 17 + Spring Boot 3.0 + Spring Cloud\n" +
"数据库:MySQL + Redis + Elasticsearch\n" +
"部署:Kubernetes + Docker\n" +
"监控:Prometheus + Grafana"
)
);
knowledge.addDocuments(documents).block();
System.out.println("已添加 " + documents.size() + " 个文档\n");
// 3. 创建支持RAG的Agent
System.out.println("=== 创建支持RAG的Agent ===\n");
ReActAgent agent = ReActAgent.builder()
.name("CompanyInfoAgent")
.model(model)
.memory(new InMemoryMemory())
.systemPrompt("你是公司员工的HR助手,回答关于公司政策和技术的问题。" +
"如果使用了知识库中的信息,请明确引用来源。")
.hooks(List.of(
new RAGHook(knowledge)
))
.build();
// 4. 提问
System.out.println("=== 提问环节 ===\n");
String[] questions = {
"我刚入职,想了解公司的年假政策",
"能否告诉我们公司使用的技术栈?",
"我需要申请产假,能享受多久?"
};
for (String question : questions) {
System.out.println("Q: " + question);
Msg response = agent.call(Msg.builder()
.role(MsgRole.USER)
.textContent(question)
.build()).block();
System.out.println("A: " + response.getTextContent() + "\n");
}
}
/**
* 简单的RAG Hook实现
*/
public static class RAGHook implements Hook {
private final Knowledge knowledge;
public RAGHook(Knowledge knowledge) {
this.knowledge = knowledge;
}
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
if (!(event instanceof PreReasoningEvent pre)) {
return Mono.just(event);
}
// 从用户问题中提取查询
List<Msg> messages = pre.getInputMessages();
if (messages.isEmpty()) {
return Mono.just(event);
}
String query = messages.get(messages.size() - 1).getTextContent();
// 检索相关文档
return knowledge.retrieve(query, RetrieveConfig.builder()
.limit(3)
.scoreThreshold(0.5)
.build())
.flatMapMany(documents -> {
// 将检索结果作为System消息插入
String ragContext = "相关知识库信息:\n";
for (Document doc : documents) {
ragContext += "\n【" + doc.getTitle() + "】\n" +
doc.getContent() + "\n";
}
Msg ragMsg = Msg.builder()
.role(MsgRole.SYSTEM)
.textContent(ragContext)
.build();
List<Msg> enhanced = new ArrayList<>();
enhanced.add(ragMsg);
enhanced.addAll(messages);
pre.setInputMessages(enhanced);
return Mono.just(event);
})
.thenReturn(event);
}
@Override
public int getPriority() {
return 20;
}
}
}
使用工具方式的RAG
java
public class ToolBasedRAGDemo {
public static void main(String[] args) throws Exception {
Model model = DashScopeModel.builder()
.modelName("qwen-turbo")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build();
// 创建知识库
Knowledge knowledge = new SimpleEmbeddingKnowledge(
new DashScopeEmbeddingService(),
new InMemoryVectorStore()
);
knowledge.addDocuments(getSampleDocuments()).block();
// 创建Agent,通过工具而不是Hook使用RAG
ReActAgent agent = ReActAgent.builder()
.name("ToolBasedRAGAgent")
.model(model)
.toolkit(new Toolkit()
.registerObject(new RAGTools(knowledge)))
.systemPrompt("""
你是一个信息助手。
当需要查询信息时,使用searchKnowledgeBase工具。
""")
.build();
// 提问时,Agent会主动调用RAG工具
Msg response = agent.call(Msg.builder()
.textContent("请告诉我产假的具体规定")
.build()).block();
System.out.println(response.getTextContent());
}
/**
* 提供RAG功能的工具集
*/
public static class RAGTools {
private final Knowledge knowledge;
public RAGTools(Knowledge knowledge) {
this.knowledge = knowledge;
}
@Tool(description = "在知识库中搜索相关信息")
public String searchKnowledgeBase(String query) {
List<Document> results = knowledge.retrieve(query,
RetrieveConfig.builder()
.limit(5)
.scoreThreshold(0.5)
.build())
.block();
if (results.isEmpty()) {
return "知识库中未找到相关信息";
}
StringBuilder sb = new StringBuilder("搜索结果:\n");
for (Document doc : results) {
sb.append("【").append(doc.getTitle()).append("】\n");
sb.append(doc.getContent()).append("\n\n");
}
return sb.toString();
}
}
private static List<Document> getSampleDocuments() {
return List.of(
new Document("产假政策", "女性员工享受158天产假..."),
new Document("年假政策", "年假计算:入职1-10年:10天..."),
new Document("远程工作政策", "申请条件:入职满6个月...")
);
}
}
12.4 生产场景:企业知识库QA系统
场景描述
markdown
一个完整的企业知识库系统:
1. 用户提出各种问题(HR、IT、产品等)
2. 系统自动检索相关知识库文档
3. 基于检索结果给出准确的回答
4. 追踪未能回答的问题,不断完善知识库
实现代码
java
public class EnterpriseKnowledgeQASystem {
/**
* 知识库管理系统
*/
public static class KnowledgebaseManager {
private final Knowledge knowledge;
private final ReActAgent qaAgent;
private final Map<String, Integer> unansweredQuestions = new ConcurrentHashMap<>();
public KnowledgebaseManager(Model model) {
this.knowledge = new SimpleEmbeddingKnowledge(
new DashScopeEmbeddingService(),
createPersistentVectorStore()
);
this.qaAgent = ReActAgent.builder()
.name("EnterpriseQAAgent")
.model(model)
.memory(new InMemoryMemory())
.toolkit(new Toolkit()
.registerObject(new KBTools(knowledge)))
.hooks(List.of(
new RAGHook(knowledge),
new FeedbackHook(unansweredQuestions)
))
.systemPrompt(getSystemPrompt())
.build();
// 初始化知识库
initializeKnowledgebase();
}
/**
* 回答用户问题
*/
public String answerQuestion(String question) {
System.out.println("\n用户问题:" + question);
Msg response = qaAgent.call(Msg.builder()
.role(MsgRole.USER)
.textContent(question)
.build()).block();
String answer = response.getTextContent();
System.out.println("系统回答:" + answer);
return answer;
}
/**
* 用户反馈
*/
public void provideFeedback(String question, String feedback) {
System.out.println("\n用户反馈:" + feedback);
// 记录反馈,用于持续改进
if (feedback.contains("不准确") || feedback.contains("不符合")) {
System.out.println("⚠️ 记录为不准确回答,需要改进知识库");
} else {
System.out.println("✓ 感谢反馈,用于改进系统");
}
}
/**
* 添加新文档到知识库
*/
public void addKnowledgeDocuments(List<Document> documents) {
knowledge.addDocuments(documents).block();
System.out.println("已添加 " + documents.size() + " 个新文档");
}
/**
* 获取未能回答的问题统计
*/
public void showUnansweredStatistics() {
System.out.println("\n=== 未能回答的问题统计 ===");
unansweredQuestions.entrySet().stream()
.sorted((a, b) -> b.getValue().compareTo(a.getValue()))
.forEach(entry ->
System.out.println("「" + entry.getKey() + "」:" +
entry.getValue() + "次"));
}
private void initializeKnowledgebase() {
List<Document> docs = List.of(
new Document("入职流程", "新员工入职流程:..."),
new Document("办公系统", "公司使用:OA系统、钉钉、企业邮箱..."),
new Document("差旅费规定", "国内出差:...,国际出差:..."),
new Document("采购流程", "小于5000元:部门批准即可..."),
new Document("绩效评估", "年度评估周期:...,晋升标准:...")
);
addKnowledgeDocuments(docs);
}
private String getSystemPrompt() {
return """
你是企业知识库的AI助手,帮助员工了解公司政策和流程。
原则:
1. 优先使用知识库信息回答问题
2. 如果知识库中没有相关信息,明确说明
3. 对于敏感问题(如薪资、个人隐私),建议联系HR
4. 回答时明确引用知识库来源
5. 鼓励用户反馈,帮助我们改进
如果无法回答,请说:"抱歉,我在知识库中没有找到相关信息,
请联系相应部门或HR。"
""";
}
private VectorStore createPersistentVectorStore() {
// 在生产中应该使用如Milvus、Weaviate等向量数据库
return new InMemoryVectorStore();
}
}
/**
* 知识库工具集
*/
public static class KBTools {
private final Knowledge knowledge;
public KBTools(Knowledge knowledge) {
this.knowledge = knowledge;
}
@Tool(description = "在知识库中搜索")
public String search(String keyword) {
List<Document> results = knowledge.retrieve(keyword,
RetrieveConfig.builder()
.limit(5)
.scoreThreshold(0.5)
.build())
.block();
return formatResults(results);
}
private String formatResults(List<Document> documents) {
if (documents.isEmpty()) {
return "未找到相关文档";
}
StringBuilder sb = new StringBuilder();
for (Document doc : documents) {
sb.append("【").append(doc.getTitle()).append("】\n")
.append(doc.getContent()).append("\n\n");
}
return sb.toString();
}
}
/**
* 反馈Hook:记录无法回答的问题
*/
public static class FeedbackHook implements Hook {
private final Map<String, Integer> unansweredQuestions;
public FeedbackHook(Map<String, Integer> unansweredQuestions) {
this.unansweredQuestions = unansweredQuestions;
}
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
if (event instanceof PostReasoningEvent post) {
// 检查回答质量
String response = post.getReasoningResult();
if (response.contains("无法") || response.contains("不清楚")) {
// 记录无法回答的问题
String question = extractQuestion(post.getInputMessages());
unansweredQuestions.put(question,
unansweredQuestions.getOrDefault(question, 0) + 1);
}
}
return Mono.just(event);
}
private String extractQuestion(List<Msg> messages) {
if (!messages.isEmpty()) {
return messages.get(messages.size() - 1).getTextContent();
}
return "Unknown";
}
@Override
public int getPriority() {
return 100;
}
}
/**
* RAG Hook
*/
public static class RAGHook implements Hook {
private final Knowledge knowledge;
public RAGHook(Knowledge knowledge) {
this.knowledge = knowledge;
}
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
if (!(event instanceof PreReasoningEvent pre)) {
return Mono.just(event);
}
// 检索相关文档
List<Msg> messages = pre.getInputMessages();
String query = messages.isEmpty() ? "" :
messages.get(messages.size() - 1).getTextContent();
if (query.isEmpty()) {
return Mono.just(event);
}
return knowledge.retrieve(query, RetrieveConfig.builder()
.limit(3)
.scoreThreshold(0.5)
.build())
.flatMapMany(documents -> {
String context = buildRAGContext(documents);
Msg ragMsg = Msg.builder()
.role(MsgRole.SYSTEM)
.textContent(context)
.build();
List<Msg> enhanced = new ArrayList<>();
enhanced.add(ragMsg);
enhanced.addAll(messages);
pre.setInputMessages(enhanced);
return Mono.just(event);
})
.thenReturn(event);
}
private String buildRAGContext(List<Document> documents) {
if (documents.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder("【来自知识库的相关信息】\n");
for (Document doc : documents) {
sb.append("\n").append(doc.getTitle()).append(":\n")
.append(doc.getContent()).append("\n");
}
return sb.toString();
}
@Override
public int getPriority() {
return 20;
}
}
/**
* 测试主程序
*/
public static void main(String[] args) throws Exception {
Model model = DashScopeModel.builder()
.modelName("qwen-turbo")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.build();
KnowledgebaseManager kb = new KnowledgebaseManager(model);
// 回答一系列问题
String[] questions = {
"新员工入职有哪些流程?",
"我要出国出差,有什么规定吗?",
"怎样申请采购?",
"无敌问题?" // 故意问一个无法回答的问题
};
for (String question : questions) {
kb.answerQuestion(question);
kb.provideFeedback(question, "✓ 很有帮助");
}
// 显示统计信息
kb.showUnansweredStatistics();
}
}
12.5 支持的知识库后端
java
// 1. 内存向量库(开发用)
Knowledge kb1 = new SimpleEmbeddingKnowledge(
embeddingService,
new InMemoryVectorStore()
);
// 2. 阿里云百炼
Knowledge kb2 = new BailianKnowledge(
apiKey,
workspaceId,
datasetId
);
// 3. Dify
Knowledge kb3 = new DifyKnowledge(
baseUrl,
apiKey
);
// 4. RAGFlow
Knowledge kb4 = new RAGFlowKnowledge(
baseUrl,
apiKey
);
// 5. 自建向量库(Milvus、Weaviate等)
Knowledge kb5 = new CustomVectorKnowledge(
customVectorStore,
customEmbeddingService
);
本章总结
本章介绍了RAG(检索增强生成)系统,这是让Agent基于最新知识库回答问题的核心技术:
核心要点
- RAG是检索+生成的混合模式
- 支持最新知识、领域专业性、成本优化
- 通过Hook或工具集成RAG功能
使用场景
- 企业知识库QA系统
- 文档基础的客服
- 持续学习和知识积累
- 私有信息的安全处理
下一章预告
第13章:结构化输出(Structured Output)
当Agent需要返回机器可解析的数据而不是自由文本时,结构化输出就变得至关重要。第13章将介绍如何确保LLM返回类型安全的、结构化的数据。