本地RAG系统

月伴飞鱼 2025-04-06 21:00:48
实战相关 > AI实战
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

大模型开发和传统JAVA开发

大模型开发—大模型实现业务逻辑:

开发前,开发人员关注数据准备(进行训练)、选择和微调模型(得到更好的效果,更能匹配业务预期)。

开发过程中(大多数时候),重点在于如何有效的与大模型(LLM)进行沟通,利用LLM的专业知识解决特定的业务问题,

开发中更关注如何描述问题(提示工程)进行有效的推理,关注如何将大模型的使用集成到现有的业务系统中。

传统的JAVA开发—开发者实现业务逻辑:

开发前,开发人员关注系统架构的选择(高并发、高可用),功能的拆解、模块化等设计。

开发过程中(大多数时候)是根据特定的业务问题,设计特定的算法、数据存储等以实现业务逻辑,以编码为主。

向量库(Chroma)安装:

https://www.python.org/downloads/macos/

验证-执行:chroma run

集成LangChain4j:

<properties>
  <langchain4j.version>0.31.0</langchain4j.version>
</properties>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-core</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-open-ai</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>${langchain4j.version}</version> 
</dependency>

知识采集:

一般是公司内网的知识库中或互联网上进行数据采集,获取到的文本文件、WORD文档或PDF文件。

案例使用resources目录下的【笑话.txt】作为知识采集的结果文件。

URL docUrl = Main.class.getClassLoader().getResource("笑话.txt");
if(docUrl==null){
    log.error("未获取到文件");
}
Document document = getDocument(docUrl);
if(document==null){
    log.error("加载文件失败");
}

private static Document getDocument(URL resource) {
    Document document = null;
    try{
        Path path = Paths.get(resource.toURI());
        document = FileSystemDocumentLoader.loadDocument(path);
    }catch (URISyntaxException e){
        log.error("加载文件发生异常", e);
    }
    return document;
}

文档切分:

使用dev.langchain4j.data.document.splitter.DocumentSplitters#recursize

它有三个参数:

  • 分段大小(一个分段中最大包含多少个token)
  • 重叠度(段与段之前重叠的token数)
  • 分词器(将一段文本进行分词,得到token)

其中,重叠度的设计是为了减少按大小拆分后切断原来文本的语义,使其尽量完整。

DocumentSplitter splitter = DocumentSplitters.recursive(150,10,new OpenAiTokenizer());
splitter.split(document);

文档拆分的目的:

由于与LLM交互的时候输入的文本对应的token长度是有限制的,输入过长的内容,LLM会无响应或直接该报错,

因此不能将所有相关的知识都作为输入给到LLM,需要将知识文档进行拆分,存储到向量库,

每次调用LLM时,先找出与提出的问题关联度最高的文档片段,作为参考的上下文输入给LLM。

文档拆分的方案langchain4j中提供了6种:

1、基于字符的:逐个字符(含空白字符)分割

2、基于行的:按照换行符(\n)分割

3、基于段落的:按照连续的两个换行符(\n\n)分割

4、基于正则的:按照自定义正则表达式分隔

5、基于句子的(使用Apache OpenNLP,只支持英文)

6、基于字的:将文本按照空白字符分割

文档切分的流程:

其中segments是最终输出的拆分结果,类型是:List。

先使用基于段落的方案将整个文档切成若干段(分段):parts。

再对每个段落part按照其他的某个方案(如:分句),并在满足【分段大小(一个分段中最大包含多少个token)】的条件下进行。

同时计算重叠部分,按照【重叠度(段与段之前重叠的token数)】补充重叠信息。

文本向量化:

由于需要将已拆分的知识片段文本存储向量库以便后续可以进行检索,而向量库存储的数据是向量不是文本。

因此需要将文本进行向量化,即将一个字符串转换为一个N维数组。

这个过程在自然语言处理(NLP)领域称为文本嵌入(Words Embedding)。

不同的LLM对于文本嵌入的实现是不同的,ChatGPT的实现是基于transformer架构的。

相关实现存储在服务端,每次嵌入都需要访问OpenAI的HTTP接口。

通过下面的例子可以看到OpenAi使用的模型是:text-embedding-ada-002,向量的维度是:1536。

OpenAiEmbeddingModel embeddingModel = new OpenAiEmbeddingModel.OpenAiEmbeddingModelBuilder().apiKey(API_KEY).baseUrl(BASE_URL).build();
log.info("当前的模型是: {}", embeddingModel.modelName());
String text = "两只眼睛";
Embedding embedding = embeddingModel.embed(text).content();
log.info("文本:{}的嵌入结果是:\n{}", text, embedding.vectorAsList());
log.info("它是{}维的向量", embedding.dimension());

向量库存储

向量数据库,也称为向量存储或向量搜索引擎。

是一种专门设计用于存储和管理向量(固定长度的数字列表)及其他数据项的数据库。

这些向量是数据点在高维空间中的数学表示,其中每个维度对应数据的一个特征。

向量数据库的主要目的是通过近似最近邻(ANN)算法实现高效的相似性搜索。

在使用向量库前,需要先启动chromdb,再通过LangChain4j封装的SDK连接到向量库,并创建数据存储容器。

  • 即集合(Collection)中(相当于MySQL的表)。
Client client = new Client(CHROMA_URL);
EmbeddingFunction embeddingFunction = new OpenAIEmbeddingFunction(API_KEY, OPEN_AI_MODULE_NAME);
client.createCollection(CHROMA_DB_DEFAULT_COLLECTION_NAME,null,true, embeddingFunction);

嵌入完成后,通过SDK连接到向量库,将向量(Embedding)与文本段(TextSegment)绑定,一并存储到向量库中。

EmbeddingStore<TextSegment> embeddingStore = ChromaEmbeddingStore.builder().baseUrl(CHROMA_URL).collectionName(CHROMA_DB_DEFAULT_COLLECTION_NAME).build();
segments.forEach(segment->{
  Embedding embedding = embeddingModel.embed(segment).content();
  embeddingStore.add(embedding, segment);
});

向量库检索

为了在向量库中查询到相似的知识片段,作为查询的文本也需要进行向量化,方法同上。

Embedding queryEmbedding = embeddingModel.embed(qryText).content(); 

检索时,向量库通过ANN算法查找与查询向量(queryEmbedding)距离最近的若个(取决于查询入参)个文本片段。

EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).maxResults(1).build();
EmbeddingSearchResult<TextSegment> embeddedEmbeddingSearchResult = embeddingStore.search(embeddingSearchRequest);
List<EmbeddingMatch<TextSegment>> embeddingMatcheList = embeddedEmbeddingSearchResult.matches();
EmbeddingMatch<TextSegment> embeddingMatch = embeddingMatcheList.get(0);
TextSegment TextSegment = embeddingMatch.embedded();

查询方法有4个入参:

查询文本嵌入向量(queryEmbedding)、最大查询数量(最多查询多少个距离最近的向量)。

最小分值(通过该值过滤一些候选值)、元数据过滤器(根据元数据过滤一些候选值)。

与LLM交互

定义Prompt模板,告知LLM通过给定的上下文知识(context,即:查询向量库获取到的相关知识)回答提出的问题(question)。

基于如下信息进行回答:\n{{context}}\n提问:\n{{question}}
PromptTemplate promptTemplate = PromptTemplate.from("基于如下信息进行回答:\n" +
                "{{context}}\n" +
                "提问:\n" +
                "{{question}}");
Map<String, Object> variables = new HashMap<>();
variables.put("context", textSegment.text());
variables.put("question", QUESTION);
prompt = promptTemplate.apply(variables);

将Prompt信息组合到请求LLM的入参中:

OpenAiChatModel openAiChatModel =  OpenAiChatModel.builder().apiKey(API_KEY).baseUrl(BASE_URL).modelName(OPEN_AI_MODULE_NAME).temperature(TEMPERATURE_NO_RANDOM).build();
UserMessage userMessage = prompt.toUserMessage();
Response<AiMessage> aiMessageResponse = openAiChatModel.generate(userMessage);
String response = aiMessageResponse.content();
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!