一、压缩查询检索Query_Compression
背景:当用户问出代词时,指代是上文查询的名词,需要将名词替换进去,也就是对两次提问的压缩。
核心代码:
java
// We will create a CompressingQueryTransformer, which is responsible for compressing
// the user's query and the preceding conversation into a single, stand-alone query.
// This should significantly improve the quality of the retrieval process.
QueryTransformer queryTransformer = new CompressingQueryTransformer(chatModel);
最终一个能压缩查询的rag就返回好了,如下:检索增强是rag起点,两个属性查询转换是对于用户上下文查询进行一个压缩,压缩的llm可以和模型的llm不一致。和一个内容检索器,很好理解。
java
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.6)
.build();
// The RetrievalAugmentor serves as the entry point into the RAG flow in LangChain4j.
// It can be configured to customize the RAG behavior according to your requirements.
// In subsequent examples, we will explore more customizations.
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.contentRetriever(contentRetriever)
.build();
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
二、查询路由检索Query_Routing
背景:当用户的rag来源有很多时,有git仓库里的,文档中的,数据库中的等等,可能需要多个contentRetriever,很不方便,这时就需要Query_Routing
核心代码:将不同的contentRetriver映射到map中,设置到retrievalAugmentor检索增强中。
java
// Let's create a query router.
Map<ContentRetriever, String> retrieverToDescription = new HashMap<>();
retrieverToDescription.put(biographyContentRetriever, "biography of John Doe");
retrieverToDescription.put(termsOfUseContentRetriever, "terms of use of car rental company");
QueryRouter queryRouter = new LanguageModelQueryRouter(chatModel, retrieverToDescription);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
构建返回Assistant:
java
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
三、重排序re-ranking
背景:在最开始的初始检索阶段,经常会用更快更高效的模型处理大量数据,但是会有检索不匹配甚至幻觉的代价。
核心代码:源码利用的是cohere的评分模型,内容聚合器ContentAggregator对小于0.8分数的进行过滤。cohere是一个挺厉害的公司,可以了解一下。
java
// To register and get a free API key for Cohere, please visit the following link:
// https://dashboard.cohere.com/welcome/register
ScoringModel scoringModel = CohereScoringModel.builder()
.apiKey(System.getenv("COHERE_API_KEY"))
.modelName("rerank-multilingual-v3.0")
.build();
ContentAggregator contentAggregator = ReRankingContentAggregator.builder()
.scoringModel(scoringModel)
.minScore(0.8) // we want to present the LLM with only the truly relevant segments for the user's query
.build();
构建返回Assistant:
java
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(contentRetriever)
.contentAggregator(contentAggregator)
.build();
ChatModel model = OpenAiChatModel.builder()
.apiKey(OPENAI_API_KEY)
.modelName(GPT_4_O_MINI)
.build();
return AiServices.builder(Assistant.class)
.chatModel(model)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
四、元数据Metadata
背景:将文档和元数据融入到llm提示词中
核心代码:分割后的每个片段,会把「文本内容 + file_name + index + 向量」绑定存储;只有当检索到相似片段时,才会把这些信息(文本 + 文件名 + 索引)注入到 Prompt 中。
java
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.build();
// Each retrieved segment should include "file_name" and "index" metadata values in the prompt
ContentInjector contentInjector = DefaultContentInjector.builder()
// .promptTemplate(...) // Formatting can also be changed
.metadataKeysToInclude(asList("file_name", "index"))
.build();
构建Assistant:
java
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(contentRetriever)
.contentInjector(contentInjector)
.build();
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(OPENAI_API_KEY)
.modelName(GPT_4_O_MINI)
.logRequests(true)
.build();
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
五、元数据过滤Metadata_Filter
背景:可以过滤掉一些内容,增加检索准确性。
核心代码:对于文本贴上元数据标签,进行过滤。
java
TextSegment dogsSegment = TextSegment.from("Article about dogs ...", metadata("animal", "dog"));
Filter onlyDogs = metadataKey("animal").isEqualTo("dog");
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.filter(onlyDogs) // by specifying the static filter, we limit the search to segments only about dogs
.build();
六、检索跳过Skip_Retrieval
背景:有些提问不需要检索,可以路由到一个空的路由检索器中。
核心代码:
java
// Let's create a query router.
QueryRouter queryRouter = new QueryRouter() {
private final PromptTemplate PROMPT_TEMPLATE = PromptTemplate.from(
"Is the following query related to the business of the car rental company? " +
"Answer only 'yes', 'no' or 'maybe'. " +
"Query: {{it}}"
);
@Override
public Collection<ContentRetriever> route(Query query) {
Prompt prompt = PROMPT_TEMPLATE.apply(query.text());
AiMessage aiMessage = chatModel.chat(prompt.toUserMessage()).aiMessage();
System.out.println("LLM decided: " + aiMessage.text());
if (aiMessage.text().toLowerCase().contains("no")) {
return emptyList();
}
return singletonList(contentRetriever);
}
};
构建Assistant: 依旧是增强+builder
java
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
七、多个内容检索器Multiple_Retrievers
背景:有多个文档
核心代码:和一个是一样的,不过是再写一遍罢了。可以结合路由一起看,一般都是一起使用的。
java
// Let's create our second content retriever.
EmbeddingStore<TextSegment> embeddingStore2 =
embed(toPath("documents/biography-of-john-doe.txt"), embeddingModel);
ContentRetriever contentRetriever2 = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore2)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.6)
.build();
// Let's create a query router that will route each query to both retrievers.
QueryRouter queryRouter = new DefaultQueryRouter(contentRetriever1, contentRetriever2);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
八、联网搜索Web_Search
背景:联网搜索,This example requires "langchain4j-web-search-engine-tavily" dependency.
核心代码:
java
// Let's create our web search content retriever.
WebSearchEngine webSearchEngine = TavilyWebSearchEngine.builder()
.apiKey(System.getenv("TAVILY_API_KEY")) // get a free key: https://app.tavily.com/sign-in
.build();
ContentRetriever webSearchContentRetriever = WebSearchContentRetriever.builder()
.webSearchEngine(webSearchEngine)
.maxResults(3)
.build();
// Let's create a query router that will route each query to both retrievers.
QueryRouter queryRouter = new DefaultQueryRouter(embeddingStoreContentRetriever, webSearchContentRetriever);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
九、返回检索源Return_Sources
背景:想要知道来源
核心代码:你看不到 answer 的具体实现(因为 createAssistant() 会通过 LangChain4j 的 AiServices 动态生成),但它底层执行的是完整的 RAG 流程。关键:Result<String> 不是普通字符串!------ 它返回的不是 "一句话",而是「带溯源的回答对象]
java
interface Assistant {
Result<String> answer(String query);
}
public static void main(String[] args) {
Assistant assistant = createAssistant();
Logger log = LoggerFactory.getLogger(shared.Assistant.class);
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
log.info("==================================================");
log.info("User: ");
String userQuery = scanner.nextLine();
log.info("==================================================");
if ("exit".equalsIgnoreCase(userQuery)) {
break;
}
Result<String> result = assistant.answer(userQuery);
log.info("==================================================");
log.info("Assistant: " + result.content());
log.info("Sources: ");
List<Content> sources = result.sources();
sources.forEach(content -> log.info(content.toString()));
}
}
}
十、数据库检索SQL_Database_Retreiver
背景:如题,不建议用于生产,不能保证其无害。In this example we will use an in-memory H2 database with 3 tables: customers, products and orders. * See "resources/sql" directory for more details. *This example requires "langchain4j-experimental-sql" dependency.
核心代码:
java
DataSource dataSource = createDataSource();
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(OPENAI_API_KEY)
.modelName(GPT_4_O_MINI)
.build();
ContentRetriever contentRetriever = SqlDatabaseContentRetriever.builder()
.dataSource(dataSource)
.chatModel(chatModel)
.build();
java
private static DataSource createDataSource() {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
dataSource.setUser("sa");
dataSource.setPassword("sa");
String createTablesScript = read("sql/create_tables.sql");
execute(createTablesScript, dataSource);
String prefillTablesScript = read("sql/prefill_tables.sql");
execute(prefillTablesScript, dataSource);
return dataSource;
}
private static String read(String path) {
try {
return new String(Files.readAllBytes(toPath(path)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void execute(String sql, DataSource dataSource) {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
for (String sqlStatement : sql.split(";")) {
statement.execute(sqlStatement.trim());
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}