langchain4j 中的 Advanced RAG 涉及到诸多策略,今天和大家聊一聊这里涉及到的一些策略。
首先介绍一下 Retrieval Augmentor,因为我们接下来的内容会用到这个工具。从名字上可以看出来,这是一个检索增强器。
Retrieval Augmentor 就像 RAG 系统的“中央处理器”,专门负责给用户的问题“加料”——通过调用各种检索渠道(比如数据库、文档库、网络资源),把找到的相关知识片段“贴”到原始问题里,让大模型回答时能参考这些资料。
举个栗子🌰: 当用户问“如何申请年假?”时,Retrieval Augmentor 会:

DefaultRetrievalAugmentor 基于行业验证的方案设计,适合 80% 的常规需求(如知识问答、文档分析) 配置方法:
// 创建AI助手时直接加载
Assistant助手 = AiServices.builder(Assistant.class)
.retrievalAugmentor(defaultRetrievalAugmentor)
.build();
如果需要特殊检索逻辑(例如先查内部机密库,再查公开资料)或者是处理多模态数据(如图片+文本混合检索),那么可以继承 RetrievalAugmentor 接口,按需改写检索流程。
每次用户提问时,Retrieval Augmentor 都会执行以下动作:
整个过程就像有个秘书帮你整理好所有参考资料,再交给专家写答案。
在 RAG 流程中,Query 就是用户提出的问题。比如你问"糖醋排骨怎么做?"这就是一个 Query。每个 Query 里包含两个重要部分:
那么这些元数据都包含哪些东西呢?
举个栗子🌰: 如果用户先问"江南一点雨是谁?",接着问"他住在哪?",系统通过聊天记录里的元数据就能知道"他"指的是江南一点雨。
这个模块就像问题的"翻译官",负责把原始问题加工得更适合检索。常见加工方式有:
• 直接把问题传给检索模块,不做任何修改 • 适用场景:问题本身已经很清晰明确时
当后续问题涉及之前对话时,自动补全信息 经典案例:
用大模型自动生成多个相关问题版本
比如把"如何预防感冒?"扩展成:

Query Transformer 的整个过程就像有个"问题加工车间":
这种设计让系统能像人类一样理解"话里有话"的问题,比如: • "刚才说的那件事..." → 自动关联前文 • "和之前类似的方案..." → 自动匹配历史方案
//1.读取文件
AutoDetectParser parser = new AutoDetectParser();
BodyContentHandler bodyContentHandler = new BodyContentHandler();
parser.parse(new FileInputStream("/Users/youyou/Desktop/jianying/松哥/思否有约访谈提纲 - 江南一点雨.docx"), bodyContentHandler, new Metadata());
Document document = Document.from(bodyContentHandler.toString());
//2.文件切分
DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(150, 50);
List<TextSegment> textSegments = splitter.split(document);
//3.文件向量化
ZhipuAiEmbeddingModel embeddingModel = ZhipuAiEmbeddingModel.builder()
.apiKey(API_KEY)
.logRequests(true)
.logResponses(true)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
Response<List<Embedding>> embedded = embeddingModel.embedAll(textSegments);
//4.向量存储
MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
.host("localhost")
.port(19530)
.collectionName("javaboy_collection_data_3")
.dimension(1024)
.indexType(IndexType.FLAT)
.metricType(MetricType.COSINE)
.username("username")
.password("password")
.consistencyLevel(ConsistencyLevelEnum.EVENTUALLY)
.autoFlushOnInsert(true)
.idFieldName("id")
.textFieldName("text")
.metadataFieldName("metadata")
.vectorFieldName("vector")
.build();
store.addAll(embedded.content(), textSegments);
//5. 搜索
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.5)
.build();
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
QueryTransformer queryTransformer = new CompressingQueryTransformer(chatModel);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.contentRetriever(contentRetriever)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(chatMemory)
.build();
String answer1 = assistant.answer("江南一点雨是谁?");
String answer2 = assistant.answer("他为什么选择做开发?");
System.out.println("answer1 = " + answer1);
System.out.println("answer2 = " + answer2);
QueryTransformer 有三个不同的实现类,分别对应我们前面 1.2.1-1.2.3 小节所介绍的三种不同方案,大家可以根据自己的需求选择不同的实现。
其实就是提示词有差别而已。不过说句不好听的,这提示词很多人写不出来,写不清楚。从这个角度来说,这个框架对于一些开发者来说是有价值的,有意义的。

Query Router 它就像快递分拣站的智能分拣员,负责把用户的问题(Query)精准派发到对应的“知识仓库”里找答案。 举个栗子🌰: 当用户问"深圳今天天气如何?"时,路由器要判断是调用天气 API、查本地数据库还是搜索新闻资讯库。
工作方式:
适用场景:
举个实际案例: 用户问"糖尿病饮食注意事项",默认路由器会:
核心原理: • 用大模型(LLM)当"决策大脑",根据问题内容智能选择检索路径 • 类似人类专家快速判断"这个问题该查哪类资料"
工作流程:

经典案例: 用户问:"ChatGPT 的 Transformer 架构有什么创新?" 智能路由器会:
优势: • 减少无效检索,响应速度提升 40% • 避免多路检索的资源浪费

通过这种设计,RAG 系统就像有个经验丰富的图书管理员,既能广撒网找资料,又能精准锁定目标库,兼顾效率与质量。
public class QueryRouterDemo {
public static void main(String[] args) {
Assistant assistant = createAssistant();
startConversationWith(assistant);
}
private static void startConversationWith(Assistant assistant) {
String answer1 = assistant.answer("江南一点雨是谁");
String answer2 = assistant.answer("Java私教课程是松哥全程带吗");
System.out.println("answer1 = " + answer1);
System.out.println("answer2 = " + answer2);
}
private static Assistant createAssistant() {
EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
EmbeddingStore<TextSegment> course =
embed(toPath("/Users/youyou/javaboy/doc/Java1对1.pdf"), embeddingModel);
ContentRetriever courseContentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(course)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.6)
.build();
EmbeddingStore<TextSegment> interview =
embed(toPath("/Users/youyou/javaboy/doc/思否有约访谈提纲-江南一点雨.pdf"), embeddingModel);
ContentRetriever interviewContentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(interview)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.6)
.build();
ChatLanguageModel chatLanguageModel = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
Map<ContentRetriever, String> retrieverToDescription = new HashMap<>();
retrieverToDescription.put(courseContentRetriever, "Java私教课程相关问题");
retrieverToDescription.put(interviewContentRetriever, "关于江南一点雨的采访");
QueryRouter queryRouter = new LanguageModelQueryRouter(chatLanguageModel, retrieverToDescription);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
}
private static Path toPath(String s) {
return Paths.get(s);
}
private static EmbeddingStore<TextSegment> embed(Path documentPath, EmbeddingModel embeddingModel) {
Document document = loadDocument(documentPath, new PDFDocumentParser());
DocumentSplitter splitter = DocumentSplitters.recursive(300, 0);
List<TextSegment> segments = splitter.split(document);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(embeddings, segments);
return embeddingStore;
}
}
上面的案例我相当于自己准备了两个向量库。框架会根据问题的不同,自动路由到不同的知识库,然后选择相关文档发送给模型,去回答问题。代码细节比较简单,这里不赘述。
就像快递包裹里的物品,Content 是 RAG 系统中承载知识的最小单元:
它像智能分拣机器人,根据用户问题从不同仓库找包裹(知识片段)。支持多种不同的数据源。
代码示例:
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)//返回结果数
// 可以根据问题动态设置返回结果数量,例如简单问题返回 3 条结果,复杂问题返回 10 条结果
.dynamicMaxResults(query -> 3)
.minScore(0.75)
// 最小得分数也可以根据问题动态设置
.dynamicMinScore(query -> 0.75)
.filter(metadataKey("userId").isEqualTo("12345"))
// 可以根据用户 ID 动态设置过滤条件(例如客户只能查询和自己相关的资料)
.dynamicFilter(query -> {
String userId = getUserId(query.metadata().chatMemoryId());
return metadataKey("userId").isEqualTo(userId);
})
.build();
WebSearchContentRetriever 可以调用 Google 等搜索引擎实时抓取网页内容,适合需要最新信息的场景(如查新闻、赛事结果)。 典型配置:
WebSearchEngine googleSearchEngine = GoogleCustomWebSearchEngine.builder()
.apiKey(System.getenv("GOOGLE_API_KEY"))
.csi(System.getenv("GOOGLE_SEARCH_ENGINE_ID"))
.build();
ContentRetriever contentRetriever = WebSearchContentRetriever.builder()
.webSearchEngine(googleSearchEngine)
.maxResults(3)
.build();
SqlDatabaseContentRetriever 可以把自然语言问题转成 SQL 语句(比如"销量最高的产品" → SELECT * FROM products ORDER BY sales DESC)。这个适合查询企业内部数据库(如 ERP 系统、订单记录等等)。
我们来看个例子:
public class SqlDatabaseContentRetrieverDemo {
public static void main(String[] args) {
Assistant assistant = createAssistant();
startConversationWith(assistant);
}
private static void startConversationWith(Assistant assistant) {
String answer = assistant.answer("我们的总销售额是多少?");
System.out.println("answer = " + answer);
}
private static Assistant createAssistant() {
DataSource dataSource = createDataSource();
ChatLanguageModel chatLanguageModel = ZhipuAiChatModel.builder()
.apiKey(API_KEY)
.model("glm-4")
.temperature(0.6)
.maxToken(1024)
.maxRetries(1)
.callTimeout(Duration.ofSeconds(60))
.connectTimeout(Duration.ofSeconds(60))
.writeTimeout(Duration.ofSeconds(60))
.readTimeout(Duration.ofSeconds(60))
.build();
ContentRetriever contentRetriever = SqlDatabaseContentRetriever.builder()
.dataSource(dataSource)
.chatLanguageModel(chatLanguageModel)
.build();
return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.contentRetriever(contentRetriever)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
}
private static DataSource createDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(MYSQL_URL);
dataSource.setUsername(MYSQL_USERNAME);
dataSource.setPassword(MYSQL_PASSWORD);
return dataSource;
}
}
这是官方的案例,松哥做了一个简单修改。工具会根据我们的提示词生成 SQL,然后去数据库中查询。
其他的还有像 AzureAiSearchContentRetriever 主要负责和 Azure AI 进行交互,Neo4jContentRetriever 则主要负责和 Neo4j 数据库进行交互。这里松哥就不逐一列举了。

Content Aggregator 就像物流中心的智能调度员,负责把不同渠道搜到的知识包裹(Content)合并成一个最优清单。比如同时用百度搜索、企业知识库、数据库查询三种方式找答案,聚合器会综合所有结果,帮你整理出最相关的信息包。
来看下它的工作流程:

DefaultContentAggregator 像投票选举一样,给每个检索渠道的结果打分,具体策略是 Reciprocal Rank Fusion (RRF),比如:
最后按总分排序。 这么做的优势是快速整合多源结果,适合实时性要求高的场景(如客服问答)
ReRankingContentAggregator 用 Cohere 等模型二次打分,像专家评审团重新评估:
对于像医疗诊断、法律文书等需要高准确率的场景可以使用这种方案。

这就像 RAG 系统的"信息打包员",负责把检索到的知识片段(Content)整合到用户的问题中,让大模型回答时有参考资料。主要功能包括:
工作流程:
用户原始问题 → "如何取消预订?"
检索结果 →
1. "取消需登录官网操作..." (来源:cancellation_procedure.html)
2. "允许提前24小时取消..." (来源:cancellation_policy.html)
最终注入后的消息:
How can I cancel my reservation?
Answer using the following information:
content: To cancel a reservation, go to ...
source: ./cancellation_procedure.html
content: Cancellation is allowed for ...
source: ./cancellation_policy.html
特点:
Answer using the following information:前缀content:标识RetrievalAugmentor.builder()
.contentInjector(DefaultContentInjector.builder()
// 自定义问题与内容的组合方式
.promptTemplate(PromptTemplate.from("{{userMessage}}\n相关参考资料:\n{{contents}}"))
.build())
必须保留
{{userMessage}}和{{contents}}占位符
继承DefaultContentInjector后重写方法:
public class CustomInjector extends DefaultContentInjector {
@Override
protected String format(Content content) {
return "🔍 参考文档:" + content.text();
}
}
实现ContentInjector接口,自由控制:
通过配置展示关键信息:
DefaultContentInjector.builder()
.metadataKeysToInclude(List.of("source","author")) // 显示来源和作者
.build()
生成效果:
content: 糖尿病患者需控制碳水...
source: 中华医学会指南_v3.pdf
author: 王建国主任医师
这种设计类似论文的参考文献格式,既保证回答准确性,又方便溯源。
在单线程模式下,当只有 1 个包裹(Query)需要分拣,且只有 1 条传送带(ContentRetriever)时,由 1 个分拣员处理即可。但是在多线程模式下,当有多个包裹需要通过不同传送带分拣时,分拣站长(Executor)会安排多个分拣员同时工作。
配置策略:
// 创建专属排班表(限制最大20个分拣员,队列容量100)
ExecutorService customExecutor = new ThreadPoolExecutor(
5, // 常驻分拣员5人
20, // 高峰期最多20人
30, TimeUnit.SECONDS, // 空闲30秒后缩减人员
new LinkedBlockingQueue<>(100) // 待处理包裹等候区容量100
);
// 应用到系统
DefaultRetrievalAugmentor.builder()
.executor(customExecutor)
.build();
结合具体案例来说下。
在电商客服系统场景下:
并行处理:三个子问题通过不同线程同时检索,响应时间从 3 秒缩短至 0.8 秒。
在 LangChain4j 的 AIServices 中,通过Result包装类和流式回调两种方式可以获取检索增强的原始资料。
interface Assistant {
Result<String> chat(String userMessage);
}
Result<String> result = assistant.chat("How to do Easy RAG with LangChain4j?");
String answer = result.content();
List<Content> sources = result.sources();
应用场景:
interface Assistant {
TokenStream chat(String userMessage);
}
assistant.chat("How to do Easy RAG with LangChain4j?")
.onRetrieved((List<Content> sources) -> ...)
.onPartialResponse(...)
.onCompleteResponse(...)
.onError(...)
.start();
技术亮点:
在来源信息中提取关键字段:
sources.forEach(content -> {
String name = content.metadata().get("source");
Double score = content.score();
System.out.printf("[%.2f] %s\n", score, name);
});
结合评分阈值筛选可靠来源:
List<Content> list = sources.stream()
.filter(c -> c.score() > 0.75)
.toList();
当使用多检索器时识别来源类型:
sources.forEach(content -> {
String type = content.metadata().getOrDefault("retriever", "向量库");
System.out.println(type + " → " + content.text());
});
该功能基于 LangChain4j 的响应拦截器机制实现: