RAG源代码笔记JAVA-高级RAG

一、压缩查询检索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();

背景:联网搜索,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);
        }
    }
相关推荐
营销操盘手阿泽2 小时前
GEO优化技术全景:从语义建模到被AI引用的系统工程
ai
复业思维202401082 小时前
Altium Designer (24.2.2)中更改库以及保持器件参数不变
笔记·学习·硬件工程
mubei-1232 小时前
Self-RAG:通过自我反思学习检索、生成和批判
人工智能·llm·rag·检索增强生成
巧克力味的桃子2 小时前
进制转换3 学习笔记
笔记·学习
日更嵌入式的打工仔3 小时前
Ethercat EOE笔记
网络·笔记·ethercat
不吃橘子的橘猫3 小时前
NVIDIA DLI 《Build a Deep Research Agent》学习笔记
开发语言·数据库·笔记·python·学习·算法·ai
算法与双吉汉堡3 小时前
【短链接项目笔记】6 短链接跳转
java·开发语言·笔记·后端·springboot
玄同7654 小时前
Python 正则表达式:LLM 噪声语料的精准清洗
人工智能·python·自然语言处理·正则表达式·nlp·知识图谱·rag
营销操盘手阿泽4 小时前
GEO优化服务战略蓝图:成本、模式与快速启动指南
ai