前言
使用Langchain4j(vectorDB+AiService+SearXNG)实现自建RAG+联网查询这里提供一些初步的使用例子,具体可以查看官方文档(docs.langchain4j.dev/tutorials/ai-services#rag),官方文档很细且有很多例子。
代码实现
官方Demo参考文档(github.com/langchain4j/langchain4j-examples/blob/main/rag-examples/src/main/java/_3_advanced/_08_Advanced_RAG_Web_Search_Example.java)。
maven库
# manven库
<properties>
<langchain4j.version>1.0.0-beta1</langchain4j.version>
<chromadb-java-client.version>0.1.7</chromadb-java-client.version>
<searxng.version>0.36.0</searxng.version>
</properties>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-web-search-engine-searxng</artifactId>
<version>${searxng.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-chroma</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.amikos-tech</groupId>
<artifactId>chromadb-java-client</artifactId>
<version>${chromadb-java-client.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
AIService
public interface Assistant {
//--使用@SystemMessage首先提示词
@SystemMessage("使用知识库查询{{query}}是否有结果,如果没有相关内容,则使用searxng联网搜索")
String answer(String query);
}
核心实现
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.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;
import static com.whg.aijmldemo.examples.common.SysConstants.OLLAMA_BASEURL;
import static com.whg.aijmldemo.examples.common.SysConstants.OLLAMA_CHAT_MODEL_NAME;
/**
* 联网查询demo
* docs.langchain4j.dev/tutorials/ai-services
*
* 各种函数方法的官方文档说明
* docs.langchain4j.dev/tutorials/rag#retrieval-augmentor
*/
public class SearxngSearchDemo {
/**文本向量化模型*/
private static final String MODEL_NAME = "shaw/dmeta-embedding-zh:latest";
/**本地知识库,这里使用一个本地文件系统,也可以使用S3,只要能读取到文件流就行*/
private static final String RAG_FILE_PATH = "assert/X3VS2VSCOS.txt";
/**ollama地址*/
private static final String SEARXNG_BASEURL = "ip:8081";
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(3)
.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();
//--.chatMemoryProvider() 用于区分不同的用户账号
return AiServices.builder(Assistant.class)
.chatLanguageModel(ollamaLanguageModel)
.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;
}
}
模拟调用
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);
}
}
}
}