Langchain4j实现本地RAG和联网查询
1、代码使用openai公网模型、ollama本地模型和本地部署的searxng搜索引擎
pom文件配置如下
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-web-search-engine-searxng</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-poi</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-tika</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-milvus-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0-beta2</version>
</dependency>
2、SearxngSearchDemo.java
package com.lee.deepseektest.util;
import com.lee.deepseektest.service.Assistant;
import dev.langchain4j.community.web.search.searxng.SearXNGWebSearchEngine;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.ollama.OllamaEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.content.retriever.WebSearchContentRetriever;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import dev.langchain4j.web.search.WebSearchEngine;
import java.io.InputStream;
import java.util.List;
/**
* 联网查询demo
* docs.langchain4j.dev/tutorials/ai-services
* <p>
* 各种函数方法的官方文档说明
* docs.langchain4j.dev/tutorials/rag#retrieval-augmentor
*/
public class SearxngSearchDemo {
/**
* 文本向量化模型
*/
private static final String MODEL_NAME = "bge-m3:latest";
/**
* 本地知识库,这里使用一个本地文件系统,也可以使用S3,只要能读取到文件流就行
*/
private static final String RAG_FILE_PATH = "11.txt";
/**
* searxng url
*/
private static final String SEARXNG_BASEURL = "http://172.16.50.25:8080";
/**
* ollama url
*/
private static final String OLLAMA_BASEURL = "http://172.16.50.25:11434";
private static final String OLLAMA_CHAT_MODEL_NAME = "deepseek-r1:32b";
public static void main(String[] args) {
//--创建一个chat接口
Assistant assistant = createAssistant();
//--开始chat
LangChainUtils.startConversationWith(assistant);
}
public static Assistant createAssistant() {
//--初始化文本向量化模型
EmbeddingModel embeddingModel =
OllamaEmbeddingModel.builder().baseUrl(OLLAMA_BASEURL).modelName(MODEL_NAME).build();
//--向量数据库,也称为向量存储或向量搜索引擎,是一种专门设计用于存储和管理向量(固定长度的数字列表)及其他数据项的数据库
//--这些向量是数据点在高维空间中的数学表示,其中每个维度对应数据的一个特征,向量数据库的主要目的是通过近似最近邻(ANN)算法实现高效的相似性搜索
//--读取需要作为知识库的文本,将向量化后的文本数据存储到向量数据库中
EmbeddingStore<TextSegment> embeddingStore =
embed(embeddingModel);
//--文本库检索query,返回最大3个结果,最少分数要0.6
ContentRetriever embeddingStoreContentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();
//--联网搜索引擎,这里使用自建的searxng,官方文档:github.com/searxng/searxng
WebSearchEngine webSearchEngine = SearXNGWebSearchEngine.builder()
.logRequests(true).logResponses(true).baseUrl(SEARXNG_BASEURL).build();
//--联网检索query,最大返回3个结果
ContentRetriever webSearchContentRetriever = WebSearchContentRetriever.builder()
.webSearchEngine(webSearchEngine)
.maxResults(10)
.build();
//--路由查询QueryRouter,将对应的查询路由到对应的ContentRetriever
QueryRouter queryRouter = new DefaultQueryRouter(embeddingStoreContentRetriever, webSearchContentRetriever);
//--检索query增强器,通过从不同的数据源查询检索,给chat返回增强后的对话信息
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.build();
//对话模型,这里使用ollama本地的,也可以使用官方的,第三方的,或者公司息壤的接口
OllamaChatModel ollamaLanguageModel =
OllamaChatModel.builder().baseUrl(OLLAMA_BASEURL)
.modelName(OLLAMA_CHAT_MODEL_NAME).build();
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
.baseUrl("https://api.siliconflow.cn/v1")
.apiKey("sk-bedafbqsexpyunwgfawojwcachflvafxxksdgszvdsahwtlu")
.modelName("Qwen/Qwen2.5-7B-Instruct")
.build();
//--.chatMemoryProvider() 用于区分不同的用户账号
return AiServices.builder(Assistant.class)
.chatLanguageModel(openAiChatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
}
private static EmbeddingStore<TextSegment> embed(EmbeddingModel embeddingModel) {
//--将已拆分的知识片段文本存储向量库以便后续可以进行检索,而向量库存储的数据是向量不是文本.
//--将一个字符串转换为一个N维数组,这个过程在自然语言处理(NLP)领域称为文本嵌入(Words Embedding),像Dify和RagFlow中使用的是elasticsearch,使用的是KNN算法
//--地区需要作为知识库的文件流,转换成Document对象
DocumentParser documentParser = new TextDocumentParser();
InputStream inputStream = SearxngSearchDemo.class.getClassLoader().getResourceAsStream(RAG_FILE_PATH);
Document document = documentParser.parse(inputStream);
//--切割文档
//--分段大小(一个分段中最大包含多少个token)、重叠度(段与段之前重叠的token数)重叠度的设计是为了减少按大小拆分后切断原来文本的语义、分词器(将一段文本进行分词,得到token)
//--Token是经过分词后的文本单位,即将一个文本分词后得到的词、子词等的个数,具体取决于分词器(Tokenizer),这里使用默认的。这个向量化的效果分段大小,重叠度,分词器有关,那情况调优能提供很好的匹配度
DocumentSplitter splitter = DocumentSplitters.recursive(500, 0);
//--文档拆分的目的:与LLM交互的时候输入的文本对应的token长度是有限制的,输入过长的内容,LLM会无响应或直接该报错
List<TextSegment> segments = splitter.split(document);
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
//--这里使用简单的内存向量库存储,可以换成自己的elasticsearch,chroma,clickhouse等向量库
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(embeddings, segments);
return embeddingStore;
}
}
3、
Assistant.java
package com.lee.deepseektest.service;
import dev.langchain4j.service.SystemMessage;
public interface Assistant {
//--使用@SystemMessage首先提示词 今天是 {{current_date}} 当前时间
@SystemMessage("使用知识库查询{{query}}是否有结果,如果没有相关内容,则使用searxng联网搜索 ; 今天是 {{current_date}}")
String answer(String query);
}
4、
LangChainUtils.java
package com.lee.deepseektest.util;
import com.lee.deepseektest.service.Assistant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
public class LangChainUtils {
public static void startConversationWith(Assistant assistant) {
Logger log = LoggerFactory.getLogger(Assistant.class);
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
log.info("==================================================");
log.info("User: ");
String userQuery = scanner.nextLine();
if (StringUtils.isBlank(userQuery)) {
log.info("请输入对话内容: ");
continue;
}
log.info("==================================================");
if ("exit".equalsIgnoreCase(userQuery)) {
break;
}
String agentAnswer = assistant.answer(userQuery);
log.info("==================================================");
log.info("Assistant: " + agentAnswer);
}
}
}
}
5、运行
SearxngSearchDemo,控制台输入问题如下
相思无尽处:无情不似多情苦,一寸还成千万缕。天涯地角有穷时,只有相思无尽处。
15:14:11.604 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:26.488 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:26.488 [main] INFO com.lee.deepseektest.service.Assistant -- Assistant: “相思无尽处”出自上述信息中的诗文,诗句的内容是:
无情不似多情苦,一寸还成千万缕。
天涯地角有穷时,只有相思无尽处。
这句话表达了相思之情的深沉和无尽,即使天地有边际,但相思之情却无穷无尽。
至于其他信息,特朗普签署行政令宣布对进口汽车征收25%关税的相关内容,可以总结如下:
1. **实施时间**:相关措施将于4月2日生效。
2. **关税适用范围**:适用于所有进口汽车以及关键汽车零部件(发动机、变速箱、动力总成零部件和电气元件)。
3. **免税情况**:符合《美墨加协定》(USMCA)的汽车零部件将继续免征关税。
4. **报复措施**:加拿大总理卡尼表示将采取报复性关税措施。
结合上述信息,答案如下:
“相思无尽处”出自诗句,表达了相思之情的深沉和无尽,即使天地有边际,但相思之情却无穷无尽。关于特朗普宣布的汽车关税,这些关税将于4月2日生效,适用于所有进口汽车以及关键汽车零部件,但符合《美墨加协定》的汽车零部件将继续免征关税。加拿大总理卡尼表示将采取报复性关税措施。
15:14:26.488 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:26.488 [main] INFO com.lee.deepseektest.service.Assistant -- User:
今日天气
15:14:34.323 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:40.602 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:40.602 [main] INFO com.lee.deepseektest.service.Assistant -- Assistant: 根据提供的信息,没有直接提到2025年3月27日的具体天气情况。为了获取今日天气的信息,我将使用searxng进行联网搜索。
检索结果显示:
2025年3月27日合肥市今天的天气预报为:
- 气温:最低气温12°C,最高气温22°C
- 天气情况:多云转晴
- 风力:东北风2级
- 空气质量:良好
综上所述,2025年3月27日合肥的天气预报为:
- 气温:12°C至22°C
- 天气:多云转晴
- 风速:东北风2级
- 空气质量:良好
15:14:40.602 [main] INFO com.lee.deepseektest.service.Assistant -- ==================================================
15:14:40.602 [main] INFO com.lee.deepseektest.service.Assistant -- User: