1. 引言

传统 RAG 在复杂问答场景下缺乏主动推理与多步检索能力;本文介绍如何将 Agent 与本地知识库 RAG 系统联用,实现更智能的问答。读完本文你将掌握:Agentic RAG 的核心概念、基于 Langchain4j + PGVector 搭建本地 RAG 知识库的方法、Agent 接入多轮对话的实现步骤,以及使用 DSPy 优化检索性能的进阶思路。

2. 核心概念:RAG 与 Agentic RAG

2.1 RAG 基础

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将信息检索与文本生成相结合的技术范式。其核心流程分为三步:

  1. 检索(Retrieve):当用户提出问题时,系统首先将问题编码为向量,然后在知识库的向量索引中执行近似最近邻(ANN)搜索,召回与问题语义最相关的文档片段。

  2. 增强(Augment):将检索到的文档片段作为上下文信息,与用户的原始问题拼接在一起,形成增强后的提示(Prompt)。

  3. 生成(Generate):将增强后的提示送入大语言模型,模型基于问题与上下文信息生成最终答案。

RAG 的主要优势体现在三个方面:

  • 减少幻觉:通过外挂知识库提供事实依据,LLM 无需依赖自身参数化记忆,显著降低编造信息的概率。
  • 知识更新:只需更新知识库文档或向量索引,无需重新训练或微调模型,成本远低于全量微调。
  • 特定领域知识:企业可以构建专属领域的知识库(如技术文档、内部规范、产品手册),让通用 LLM 也能回答垂直领域的问题。

在实践中,RAG 系统通常还需要处理文档分块策略、Embedding 模型选型、检索结果重排序等问题。例如,文档分块(chunking)要考虑段落语义的完整性,Embedding 模型要根据语言、文本长度和预算进行选型,检索结果可以使用 RRF(Reciprocal Rank Fusion)融合向量检索与关键字 BM25 的结果,提升召回质量。

2.2 Agentic RAG 演进

传统 RAG 虽然解决了知识外挂问题,但在复杂问答场景下仍存在明显局限。以“请找出2024年财报中涉及研发投入的部分,并对比2023年的数据,给出增长率”这样的查询为例,传统 RAG 可能无法一次完成多条件筛选与跨文档比较。这正是 Agentic RAG 要解决的问题。

Agentic RAG 将大型语言模型与智能 Agent 相结合,Agent 不再是被动地执行一次检索然后生成,而是像智能助手一样主动思考、规划、执行,并与用户进行交互,最终完成任务。具体来说,Agentic RAG 引入了以下能力:

  • 主动规划检索策略:Agent 可以将复杂问题拆解为多个子查询。例如,对于上述财务问题,Agent 可能先检索“2024年财报 研发投入”,再检索“2023年财报 研发投入”,然后让另一个工具计算增长率。
  • 使用工具(Tools):Agent 可以调用多个工具,例如本地 RAG 知识库查询、Web 搜索、API 调用、代码执行器、数据库查询等。

每个工具都有明确的描述和接口定义。

  • 管理多轮对话上下文:Agent 能够记住对话历史,在后续提问中自动关联之前提到的实体或约束条件。例如,用户问“刚才提到的那个方案,具体费用是多少?”Agent 需要从历史记录中找出“那个方案”指的是什么。
  • 错误恢复与异常处理:当某个工具调用失败或返回异常结果时,Agent 可以尝试替代策略或向用户请求澄清。

目前,已经有成熟的框架支持 Agentic RAG 的构建,例如微软的 AutoGen(用于构建基于 LLM 的多 Agent 工作流)、Langchain4j(Java 生态下的 Agent 框架)以及 LangChain(Python 生态下的 Agent 框架)。

2.3 适用场景对比

场景特征 推荐方案 说明
简单的单次事实查询 传统 RAG 如“公司的注册地址是什么?”
多条件筛选 Agentic RAG 如“查找2024年第一季度,华东区销售额超过100万的客户”
需要多步推理 Agentic RAG 如“分析最近三个季度的毛利率变化趋势”
涉及外部系统调用 Agentic RAG 如“查询订单状态后,调用CRM系统更新客户等级”
对延迟敏感 传统 RAG Agent 的多步推理会增加响应时间

3. 本地知识库搭建:基于 Langchain4j + PGVector

3.1 技术选型理由

在本地知识库 RAG 的搭建中,技术选型需综合考虑生态适配、数据一致性、部署复杂度等因素。Langchain4j 是 Java/Spring 生态下的 LLM 集成框架,提供便捷的 RAG 流程 API;PGVector 是 PostgreSQL 的向量检索扩展,允许在关系数据库中直接存储和查询向量。

选型 PGVector 的核心理由:

  • 数据一致性:向量数据与业务关系数据存储在同一个数据库,便于维护元数据关联和事务操作。
  • 企业级部署友好:大多数企业内部已部署 PostgreSQL,无需额外引入专用向量数据库(如 Milvus、Qdrant),降低运维复杂度。
  • 功能完整:支持多种索引类型(IVFFlat、HNSW)、多种距离度量(余弦、欧氏距离、内积),性能在百万级向量规模下可接受。

3.2 搭建步骤概要

步骤一:安装 PGVector 并创建向量表

确保 PostgreSQL 版本 ≥ 12,然后安装 PGVector 插件:

1
CREATE EXTENSION IF NOT EXISTS vector;

创建向量表时,需要根据 Embedding 模型的输出维度定义列类型。例如,使用 bge-m3 模型(输出 1024 维)时:

1
2
3
4
5
6
CREATE TABLE knowledge_chunks (
id SERIAL PRIMARY KEY,
chunk_content TEXT NOT NULL,
metadata JSONB,
embedding vector(1024)
);

注意:vector(1024) 中的维度必须与 Embedding 模型的输出维度完全一致。

步骤二:配置 Langchain4j 的向量存储

使用 Langchain4j 的 PgVectorEmbeddingStore 连接 PostgreSQL:

1
2
3
4
5
6
7
8
9
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("knowledge_base")
.user("postgres")
.password("password")
.table("knowledge_chunks")
.dimension(1024)
.build();

对于开发阶段的快速验证,也可以使用 EmbeddingStoreInMemory

1
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

步骤三:文档分块与 Embedding 模型选择

文档分块的策略直接影响检索质量。实践中建议:

  • 文本类文档:使用段落做天然切割,每个 chunk 保留标题、小标题等上下文信息。
  • 表格数据:单独处理,保留表头与行列结构。
  • 代码片段:按函数或类分块,保留注释。

分块示例参数:

1
2
3
4
DocumentSplitter splitter = DocumentSplitters.recursive(
paragraph(paragraph -> paragraph.length() < 1000),
sentence(sentence -> sentence.length() < 200)
);

Embedding 模型推荐选择开源且支持多语言的模型,如 bge-m3(BAAI 出品,支持 1024 维、多语言)或 BAAI/bge-large-zh-v1.5(中文场景优先)。Langchain4j 中使用 Embedding 模型:

1
2
EmbeddingModel embeddingModel = new BgeSmallZhEmbeddingModel();
// 或者使用 OpenAI / Ollama 等远程模型

步骤四:写入向量库并验证检索效果

1
2
3
4
5
List<TextSegment> segments = splitter.split(document);
for (TextSegment segment : segments) {
Embedding embedding = embeddingModel.embed(segment.text()).content();
embeddingStore.add(embedding, segment);
}

检索验证关注 Recall@k 指标(即前 k 个结果中包含正确结果的比例)。对于常见的知识问答场景,k=5 时 Recall 应达到 0.85 以上。

3.3 关键技术细节

  • 连接池配置:生产环境下使用 HikariCP 连接池,最小空闲连接设为 5,最大连接数设为 30。
  • 相似度检索:PGVector 支持 <->(L2 距离)、<=>(余弦距离)和 <#>(内积)。对于语义检索,余弦距离是最常用的度量方式,因为它对向量长度不敏感。
  • 检索结果重新排序(RRF 融合):当同时使用向量检索和 BM25 关键字检索时,通过 RRF 公式融合排名:
1
2
3
4
5
// 伪代码:分别获取向量检索结果与 BM25 结果
List<ScoredDocument> vectorResults = vectorStore.search(...);
List<ScoredDocument> keywordResults = bm25Search(...);
// RRF 融合
List<ScoredDocument> fusedResults = ReciprocalRankFusion.merge(vectorResults, keywordResults);

提示:初始搭建时使用 IVFFlat 索引(ivfflat lists = 100)即可满足百万级向量下的毫秒级检索;若数据量达到千万级,建议升级为 HNSW 索引(hnsw ef_construction = 200)。

4. Agent 接入实践:将本地 RAG 作为 Agent 的工具

4.1 Agent 架构设计

一个典型的 Agent 内部结构包括:

  • 主 Agent:负责解析用户输入、决定调用哪个工具、管理多轮对话上下文。
  • 工具注册表:所有可用的工具列表,每个工具有唯一的名称、描述和调用接口。
  • 记忆模块:维护对话历史,用于给 Agent 提供上下文信息。

本地 RAG 知识库作为一类检索工具注册到 Agent 中。当 Agent 判断用户问题需要知识库查询时,它会调用该工具。

4.2 主要工具类型

在 Agent 实践中,除了 RAG 检索工具,通常还会注册以下几类工具:

  • RAG 查询工具:从本地知识库检索相关文档片段。
  • Web 搜索工具:通过搜索引擎获取外部信息。
  • 代码执行工具:运行 Python/SQL 代码并返回结果。
  • API 调用工具:调用内部 REST API 获取业务数据。

4.3 实现步骤

4.3.1 定义 RAG 工具接口

在 Langchain4j 中,使用 @Tool 注解定义工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Tool("从本地知识库中根据查询内容检索相关信息,返回最相关的文档片段")
public String queryLocalKnowledgeBase(@P("查询内容") String query) {
// 1. 将查询文本编码为向量
Embedding queryEmbedding = embeddingModel.embed(query).content();

// 2. 从向量库检索最相关的 top-k 片段
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding, 5 // k = 5
);

// 3. 拼接检索结果
StringBuilder context = new StringBuilder();
for (EmbeddingMatch<TextSegment> match : matches) {
context.append(match.embedded().text()).append("\n---\n");
}

return context.toString();
}

注意:工具的描述应该清晰明确,以便 Agent 能正确判断何时使用该工具。实践中,可以增加一个示例占位符(如“用法:当用户询问公司政策、产品手册内容时使用此工具”)。

4.3.2 创建 Agent

使用 Assistant 模式创建 Agent,并绑定 LLM 与工具:

1
2
3
4
5
Assistant agent = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel) // 可以是 OpenAI、Ollama 等
.tools(new KnowledgeBaseTool(embeddingModel, embeddingStore))
.chatMemory(MessageWindowChatMemory.withMaxMessages(20))
.build();

4.3.3 执行多轮对话

1
2
3
4
5
6
7
8
// 第一轮对话
String response1 = agent.chat("请解释RAG的工作原理");
System.out.println(response1);

// 第二轮对话,利用历史上下文
String response2 = agent.chat("它在减少幻觉方面有什么优势?");
System.out.println(response2);
// Agent 会根据历史判断“它”指的 RAG,从而返回相应内容

4.4 易错点与调优提示

  • 工具返回值格式:工具返回的字符串应当尽量结构化,包含文档来源或 ID,便于 Agent 后续引用。例如在结果前加上 [来源:文档A]
  • 超时设置:工具调用应设置超时(建议 15 秒),避免 Agent 因某个工具长时间未响应而卡死。
  • 异常处理:在工具方法内部捕获异常,返回友好的错误消息,而不是抛出异常中断 Agent 执行。

5. 多轮对话增强:Agent+RAG 的上下文融合

5.1 问题分析

在传统 RAG 中,每一轮查询都是独立的,丢失了历史对话信息。例如,用户先问“2024年第一季度营收是多少?”,接着追问“比上个季度增长了多少?”,RAG 如果不结合历史,无法知道“上个季度”指的是 2023 年第四季度。

5.2 上下文融合方案

Agent 在构造检索查询时,自动将多轮对话中的关键实体与约束条件拼接进去。具体实现有两种常用方式:

方案一:利用 LLM 的对话摘要能力

让 LLM 在对之前的对话进行总结后,生成当前查询的完整描述。Langchain4j 的 MessageWindowChatMemory 会自动管理历史消息,Agent 会在系统提示中包含对话历史。

在 Agent 内部,工具调用前可以嵌入一个“查询构造”步骤:

1
2
3
4
5
6
7
// 伪代码:在调用 RAG 工具前,根据对话历史优化查询
String optimizedQuery = chatLanguageModel.generate(
String.format("基于以下对话历史,为当前用户问题生成一个完整的检索查询。" +
"对话历史:%s\n当前问题:%s",
conversationHistory, currentUserInput)
);
// 然后用 optimizedQuery 去调用知识库检索

方案二:手动维护结构化历史

在 Agent 的上下文中维护一个结构化摘要(如 HashMap<String, Object>),记录用户提到的关键实体(例如合同编号、时间范围、产品名称)。工具调用前,从摘要中提取相关约束条件注入查询。

5.3 实现示例

使用 Langchain4j 的 ConversationMemory 时,只需在创建 Agent 时配置即可:

1
2
3
4
5
6
7
8
9
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20) // 保留最近 20 条消息
.build();

Assistant agent = AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.tools(new KnowledgeBaseTool())
.chatMemory(chatMemory) // 已经内置上下文融合
.build();

5.4 注意事项

  • 避免上下文过长:对话历史随着轮数增加会迅速消耗 token。可以设置滑动窗口(例如最近 10 轮对话),或者将历史摘要化(只保留关键实体与问题概要)。
  • 定期清理混淆上下文:如果用户在一个会话中讨论了无关的多个话题,Agent 可能检索到错误的上文。可以在每次用户问题时,让 Agent 先判断当前查询是否切换了主题。

6. 进阶技巧:使用 DSPy 优化 RAG 检索与生成性能

6.1 DSPy 简介

DSPy(Declarative Self-improving Language Programs)是一个用于构建和优化 RAG 系统的声明式框架,由斯坦福大学 NLP 组开发。它的核心思想是:开发者以声明方式描述希望程序实现的目标,DSPy 自动优化提示(Prompts)和模型参数,以达到最佳性能。

相比于手动调整提示词,DSPy 的优化流程更加系统化:

  1. 定义 pipeline 的输入/输出。
  2. 声明模块(如检索器、生成器)。
  3. 提供少量标注样本。
  4. 自动搜索最佳优化策略(如 query 改写、few-shot 示例选择、prompt 模板优化)。

6.2 典型优化场景

查询改写策略优化:用户的原始查询可能不够精确(例如“介绍一下产品”过于宽泛),DSPy 可以自动训练一个“查询重写器”,将用户问题改写为更适合检索的形式(例如“描述公司2024年新推出的ERP产品功能特性”)。

生成阶段 prompt 优化:DSPy 可以优化生成阶段的 prompt,例如在不同任务上自动选择最佳示例数量或调整指令措辞。

6.3 集成路径建议

DSPy 目前主要支持 Python 生态。对于 Java 技术栈的团队,可以考虑以下集成路径:

  • 方案一:独立 Python 服务:将 DSPy 优化的 RAG pipeline 封装为一个微服务,通过 gRPC/HTTP 与 Java Agent 通信。
  • 方案二:引入 DSPy 的优化结果:在本地训练好最优的 query 改写规则或 prompt 模板后,将规则迁移到 Java Agent 中硬编码。

示例:使用 DSPy 优化查询改写(Python 伪代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import dspy
from dspy import signature, Module, Predict

class RewriteQuery(signature.Signature):
"""将用户原始查询改写为更适合知识库检索的详细查询"""
original_query = dspy.InputField()
rewritten_query = dspy.OutputField()

class RAGPipeline(Module):
def __init__(self):
self.rewrite = Predict(RewriteQuery)
self.retrieve = dspy.Retrieve(k=5)
self.generate = dspy.ChainOfThought("context, question -> answer")

def forward(self, question):
rewritten = self.rewrite(original_query=question).rewritten_query
context = self.retrieve(rewritten).passages
answer = self.generate(context=context, question=question)
return answer

# 使用少量标注样本自动优化
optimizer = dspy.BootstrapFewShot(max_bootstrapped_demos=5)
compiled_rag = optimizer.compile(RAGPipeline(), trainset=trainset)

6.4 效果评估

DSPy 优化的效果可以通过对比优化前后的检索与生成指标来量化:

指标 说明 优化前 优化后
Recall@5 前5个结果中包含正确文档的比例 0.78 0.91
NDCG@5 排序质量,考虑文档的相关性等级 0.65 0.82
生成准确率 最终回答与人工标注答案的匹配度 0.72 0.88

评估数据集建议从真实用户问题中采样 200-500 条并人工标注正确答案,保证评估的可靠性。

7. 踩坑记录与性能调优

7.1 常见问题与解决方案

向量库索引选择不当导致检索慢:PGVector 支持 IVFFlat 和 HNSW 两种索引。IVFFlat 索引需要指定 lists 参数(推荐 sqrt(data_size)),当数据量超过 500 万时建议切换为 HNSW。首次建立索引时注意采样完整性:

1
2
3
4
-- IVFFlat 索引
CREATE INDEX ON knowledge_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- HNSW 索引(需要 PGVector ≥ 0.5.0)
CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 200);

Agent 工具调用循环错误:Agent 可能重复调用同一个工具(例如连续多次调用 RAG 查询,每次返回相似结果)。可以通过设定“最大工具调用次数”来终止循环,或者让工具在返回值中附带“无新信息”标记。

1
2
3
4
5
// 限制 Agent 单轮对话中的最大工具调用次数为 10 次
AgentExecutor executor = AgentExecutor.builder()
.agent(reactAgent)
.maxIterations(10)
.build();

多轮对话中历史信息污染:当用户讨论 A 主题后突然切换到 B 主题,Agent 可能错误地融合了 A 主题的上下文。解决方案是在 Agent 的判断逻辑中增加“主题切换检测”,当检测到查询与当前主题无关时,清除之前的上下文。

7.2 性能优化方向

Embedding 模型领域微调:使用高质量的领域数据微调 Embedding 模型,尤其要构造难负样本(hard negatives)。例如,对于技术文档,可以将“Linux 系统安装”与“MySQL 数据库安装”这样容易混淆的文档对作为难负样本。微调后 Recall@5 可提升 5-15%。

1
2
3
4
5
6
7
8
// 伪代码:构造难负样本
List<TrainingExample> examples = new ArrayList<>();
for (Document doc : domainDocuments) {
// 查询 = doc 标题
// 正样本 = doc 正文
// 难负样本 = 语义相似但主题不同的另一文档
examples.add(new TrainingExample(query, positiveDoc, randomNegativeDoc, hardNegativeDoc));
}

元数据过滤 + 混合检索:在企业内部知识库中,元数据(如文档类型、创建时间、所属部门)可以有效缩小检索范围。例如,用户提问“2024年的研发政策”,可以先用元数据过滤 year=2024 && type='policy',再执行向量检索。

混合检索使用“向量相似度 (0.7) + 关键字BM25 (0.3)”加权融合,在语义匹配和精确匹配之间取得平衡。

将 Agent 的 LLM 替换为本地小模型:如果 Agent 的响应延迟过高,可以考虑将 LLM 从 GPT-4 切换到本地模型(如 Qwen2.5-7B 或 Llama-3-8B)。虽然部分复杂推理能力会下降,但延迟可从 3 秒降至 0.5 秒。对于简单场景(如工具调用),本地小模型通常足够胜任。

8. 总结与拓展

8.1 核心回顾

本文完整介绍了 Agent 接入本地知识库 RAG 的工程路径,核心要点如下:

  1. 本地 RAG 知识库搭建:基于 Langchain4j + PGVector 实现端到端的向量存储与检索,包括插件安装、表结构设计、文档分块策略、Embedding 模型选型与索引配置。

  2. Agent 工具封装:将 RAG 检索功能封装为 Agent 的可调用工具,通过 @Tool 注解定义名称、描述和参数,实现 Agent 对知识库的主动、多步调用。

  3. 多轮对话上下文融合:利用 Langchain4j 的 MessageWindowChatMemory 自动管理对话历史,并利用 Agent 的规划能力在每次检索前构造优化查询。

  4. DSPy 优化:通过声明式编程与自动化优化,提升 RAG 流水线的检索准确率和生成质量。

8.2 未来方向

  • 引入图嵌入与知识图谱:将知识图谱中的实体和关系映射到向量空间,与文档向量共存在同一个检索体系中。这样,Agent 不仅能够检索文档片段,还能直接检索实体之间的关联知识(如“A 部门负责 B 产品线的售后维护”),实现关联推理。
  • 多 Agent 协作:一个 Agent 负责检索信息,另一个 Agent 负责验证答案的准确性,第三个 Agent 负责与用户对话。

通过 AutoGen 等框架,让多个 Agent 通过消息传递协同完成任务,提升系统的可靠性和可扩展性。

  • 离线评估流程:建立持续的离线评估 pipeline,使用 MTEB (Massive Text Embedding Benchmark) 或其他领域自定义基准,跟踪 Embedding 模型和 RAG pipeline 的质量变化,防止模型更新或数据变化导致性能退化。

8.3 参考资源

  • GitHub 社区讨论:本地知识库引入 Agentic RAG 或 DSPy 的技术原理与集成建议(可搜索相关 Issue #1889)。

  • Langchain4j 官方文档:提供了 Java 环境下 Agent 与 RAG 的完整 API 说明。

  • DSPy 官方教程:包含丰富的 RAG 优化案例,覆盖问答、摘要、生成等场景。

  • 《RAG 离线部分:Embedding模型选型与领域适配微调》系列文章:深入探讨了 Embedding 模型的选型方法、微调策略与评估指标,以及元数据增强、知识图谱融合等进阶方向。

总结

通过本文的学习,相信你已经对「Agent接入本地知识库」有了更深入的理解。建议结合实际项目多加练习。如有疑问,欢迎交流!