RAG

获取向量模型

向量数据库

📊 主流向量数据库概览

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

RagController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.DocumentReader;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;


@Slf4j
@RestController
@RequestMapping("/rag")
public class RagController {


@Resource
private OllamaEmbeddingModel ollamaEmbeddingModel;

@Resource
private VectorStore vectorStore;

@Resource
private ChatClient ollamaChatChatClient;

@RequestMapping("/test1")
public String test1() {
float[] embedded = ollamaEmbeddingModel.embed("我是中国人");
log.info("长度:{},内容:{}", embedded.length, embedded);
return "success";
}


@RequestMapping("/test2")
public String test2() {
saveData2VectorStore();
List<Document> documents = vectorStore.similaritySearch("鼠标多少钱?");
documents.forEach(item -> {
log.info("检索内容:{},得分:{}", item.getText(), item.getScore());
});

return null;
}

private void saveData2VectorStore() {
Document doc = Document.builder().text("""
咨询服务费用:
- 每小时100元
""").build();
vectorStore.add(List.of(doc));
doc = Document.builder().text("""
商品价格:
- 每个100元
""").build();
vectorStore.add(List.of(doc));
}


@RequestMapping(value = "/test3", produces = "text/stream;charset=utf8")
public Flux<String> test3() {
saveData2VectorStore();
String message = "鼠标多少钱?";
return ollamaChatChatClient.prompt().user("鼠标多少钱?").advisors(
SimpleLoggerAdvisor.builder().build(),
QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().query(message).similarityThreshold(0.5).topK(1).build()).build()).stream().content();
}


@Value("classpath:rag/test.txt")
private org.springframework.core.io.Resource txtResource;

@RequestMapping(value = "/txt", produces = "text/stream;charset=utf8")
public void txt() {
DocumentReader reader = new TextReader(txtResource);
List<Document> documents = reader.read();
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}

@Value("classpath:rag/test.md")
private org.springframework.core.io.Resource mdResource;

@RequestMapping(value = "/md", produces = "text/stream;charset=utf8")
public void md() {

MarkdownDocumentReaderConfig readerConfig = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeBlockquote(true)
.withIncludeCodeBlock(true)
.build();
DocumentReader reader = new MarkdownDocumentReader(mdResource, readerConfig);
List<Document> documents = reader.read();
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}


@Value("classpath:rag/textsplit.txt")
private org.springframework.core.io.Resource textSplit;

@RequestMapping(value = "/textSplitter", produces = "text/stream;charset=utf8")
public void textSplitter() {
DocumentReader reader = new TextReader(textSplit);
List<Document> documents = reader.read();
TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true);
documents = splitter.apply(documents);
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}
}

TokenTextSplitter

场景分析与策略推荐

1. 通用文档检索与问答

场景:处理混合型知识库文档(如产品手册、公司wiki、综合文章),用于构建RAG系统进行问答。

  • 目标:在信息完整性和检索精度间取得平衡。

  • 推荐策略:

    • 分块大小:512-1024 tokens

    • 重叠大小:100-200 tokens

    • 理由:中等大小的分块能容纳完整概念,适度的重叠确保边界信息不丢失。

2. 法律文档与合同分析

场景:处理法律条文、合同条款、法规文件。

  • 特点:文本结构严谨,逻辑关系紧密,术语定义精确。

  • 推荐策略:

    • 分块大小:256-512 tokens

    • 重叠大小:50-100 tokens

    • 额外建议:优先按自然段落分割,保持条款完整性

    • 理由:较小的分块提高法律条文检索的精确度。

3. 学术论文与技术文档

场景:科研论文、技术规范、API文档处理。

  • 特点:包含公式、代码、图表引用,结构层次分明。

  • 推荐策略:

    • 分块大小:800-1200 tokens

    • 重叠大小:150-250 tokens

    • 额外建议:尊重章节边界,保持技术概念的完整性

    • 理由:较大的分块确保复杂技术概念的完整表达。

4. 对话记录与聊天分析

场景:客服对话、会议记录、聊天日志分析。

  • 特点:多轮对话,上下文依赖性强,话轮转换明显。

  • 推荐策略:

    • 分块大小:200-400 tokens

    • 重叠大小:包含完整的对话轮次

    • 额外建议:按对话主题或时间窗口分割

    • 理由:较小的分块匹配对话的自然单元。

5. 代码分析与程序理解

场景:源代码分析、代码文档生成。

  • 特点:语法结构严格,函数/方法为自然单元。

  • 推荐策略:

    • 分块大小:按函数/方法单元,而非固定token数

    • 重叠大小:0-50 tokens

    • 额外建议:优先保持代码结构的完整性

    • 理由:代码的理解依赖于完整的语法结构。

6. 新闻文章与博客内容

场景:新闻聚合、内容推荐、舆情分析。

  • 特点:倒金字塔结构,段落相对独立。

  • 推荐策略:

    • 分块大小:400-600 tokens

    • 重叠大小:50-100 tokens

    • 理由:匹配新闻段落的典型长度,保持事件描述的完整性。

⚙️ 实践配置示例

法律文档处理的Spring AI配置:

1
2
3
4
5
6
7
TokenTextSplitter legalSplitter = new TokenTextSplitter.Builder()
.defaultChunkSize(400) // 较小的分块提高法律精度
.chunkOverlap(80) // 适度的重叠确保条款连贯
.minChunkSizeChars(200) // 避免过小的无意义片段
.keepSeparator(true) // 保留法律文本的格式标记
.maxNumChunks(5000) // 防止异常文档
.build();

技术文档处理的配置:

1
2
3
4
5
6
TokenTextSplitter techSplitter = new TokenTextSplitter.Builder()
.defaultChunkSize(1000) // 较大的分块保持技术概念完整
.chunkOverlap(200) // 显著的重叠确保技术上下文
.minChunkSizeChars(500) // 确保有足够的技术内容
.keepSeparator(true) // 保留代码和公式格式
.build();

获取向量模型

向量数据库

📊 主流向量数据库概览

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

RagController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.DocumentReader;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;


@Slf4j
@RestController
@RequestMapping("/rag")
public class RagController {


@Resource
private OllamaEmbeddingModel ollamaEmbeddingModel;

@Resource
private VectorStore vectorStore;

@Resource
private ChatClient ollamaChatChatClient;

@RequestMapping("/test1")
public String test1() {
float[] embedded = ollamaEmbeddingModel.embed("我是中国人");
log.info("长度:{},内容:{}", embedded.length, embedded);
return "success";
}


@RequestMapping("/test2")
public String test2() {
saveData2VectorStore();
List<Document> documents = vectorStore.similaritySearch("鼠标多少钱?");
documents.forEach(item -> {
log.info("检索内容:{},得分:{}", item.getText(), item.getScore());
});

return null;
}

private void saveData2VectorStore() {
Document doc = Document.builder().text("""
咨询服务费用:
- 每小时100元
""").build();
vectorStore.add(List.of(doc));
doc = Document.builder().text("""
商品价格:
- 每个100元
""").build();
vectorStore.add(List.of(doc));
}


@RequestMapping(value = "/test3", produces = "text/stream;charset=utf8")
public Flux<String> test3() {
saveData2VectorStore();
String message = "鼠标多少钱?";
return ollamaChatChatClient.prompt().user("鼠标多少钱?").advisors(
SimpleLoggerAdvisor.builder().build(),
QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().query(message).similarityThreshold(0.5).topK(1).build()).build()).stream().content();
}


@Value("classpath:rag/test.txt")
private org.springframework.core.io.Resource txtResource;

@RequestMapping(value = "/txt", produces = "text/stream;charset=utf8")
public void txt() {
DocumentReader reader = new TextReader(txtResource);
List<Document> documents = reader.read();
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}

@Value("classpath:rag/test.md")
private org.springframework.core.io.Resource mdResource;

@RequestMapping(value = "/md", produces = "text/stream;charset=utf8")
public void md() {

MarkdownDocumentReaderConfig readerConfig = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeBlockquote(true)
.withIncludeCodeBlock(true)
.build();
DocumentReader reader = new MarkdownDocumentReader(mdResource, readerConfig);
List<Document> documents = reader.read();
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}


@Value("classpath:rag/textsplit.txt")
private org.springframework.core.io.Resource textSplit;

@RequestMapping(value = "/textSplitter", produces = "text/stream;charset=utf8")
public void textSplitter() {
DocumentReader reader = new TextReader(textSplit);
List<Document> documents = reader.read();
TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true);
documents = splitter.apply(documents);
documents.forEach(item -> {
log.info("内容:{}", item.getText());
});
}
}

TokenTextSplitter

场景分析与策略推荐

1. 通用文档检索与问答

场景:处理混合型知识库文档(如产品手册、公司wiki、综合文章),用于构建RAG系统进行问答。

  • 目标:在信息完整性和检索精度间取得平衡。

  • 推荐策略:

    • 分块大小:512-1024 tokens

    • 重叠大小:100-200 tokens

    • 理由:中等大小的分块能容纳完整概念,适度的重叠确保边界信息不丢失。

2. 法律文档与合同分析

场景:处理法律条文、合同条款、法规文件。

  • 特点:文本结构严谨,逻辑关系紧密,术语定义精确。

  • 推荐策略:

    • 分块大小:256-512 tokens

    • 重叠大小:50-100 tokens

    • 额外建议:优先按自然段落分割,保持条款完整性

    • 理由:较小的分块提高法律条文检索的精确度。

3. 学术论文与技术文档

场景:科研论文、技术规范、API文档处理。

  • 特点:包含公式、代码、图表引用,结构层次分明。

  • 推荐策略:

    • 分块大小:800-1200 tokens

    • 重叠大小:150-250 tokens

    • 额外建议:尊重章节边界,保持技术概念的完整性

    • 理由:较大的分块确保复杂技术概念的完整表达。

4. 对话记录与聊天分析

场景:客服对话、会议记录、聊天日志分析。

  • 特点:多轮对话,上下文依赖性强,话轮转换明显。

  • 推荐策略:

    • 分块大小:200-400 tokens

    • 重叠大小:包含完整的对话轮次

    • 额外建议:按对话主题或时间窗口分割

    • 理由:较小的分块匹配对话的自然单元。

5. 代码分析与程序理解

场景:源代码分析、代码文档生成。

  • 特点:语法结构严格,函数/方法为自然单元。

  • 推荐策略:

    • 分块大小:按函数/方法单元,而非固定token数

    • 重叠大小:0-50 tokens

    • 额外建议:优先保持代码结构的完整性

    • 理由:代码的理解依赖于完整的语法结构。

6. 新闻文章与博客内容

场景:新闻聚合、内容推荐、舆情分析。

  • 特点:倒金字塔结构,段落相对独立。

  • 推荐策略:

    • 分块大小:400-600 tokens

    • 重叠大小:50-100 tokens

    • 理由:匹配新闻段落的典型长度,保持事件描述的完整性。

⚙️ 实践配置示例

法律文档处理的Spring AI配置:

1
2
3
4
5
6
7
TokenTextSplitter legalSplitter = new TokenTextSplitter.Builder()
.defaultChunkSize(400) // 较小的分块提高法律精度
.chunkOverlap(80) // 适度的重叠确保条款连贯
.minChunkSizeChars(200) // 避免过小的无意义片段
.keepSeparator(true) // 保留法律文本的格式标记
.maxNumChunks(5000) // 防止异常文档
.build();

技术文档处理的配置:

1
2
3
4
5
6
TokenTextSplitter techSplitter = new TokenTextSplitter.Builder()
.defaultChunkSize(1000) // 较大的分块保持技术概念完整
.chunkOverlap(200) // 显著的重叠确保技术上下文
.minChunkSizeChars(500) // 确保有足够的技术内容
.keepSeparator(true) // 保留代码和公式格式
.build();