第八篇:让大模型自己切文档——语义切片

在这里插入图片描述

前言

前面七篇文章,我们讲了RAG的完整流程:

原始文档 → 知识切片 → 向量化 → 向量数据库 → 检索 → 返回结果

但有一个关键步骤一直没讲:知识切片

如果切片切不好,后面的向量化、检索都会出问题。

这篇文章,我们深入分析各种切片算法的原理和缺陷,最后给出一个终极方案。


一、为什么切片这么重要?

1.1 切片质量直接影响检索效果

切片太长:
├── 一个向量要表示多个主题
├── 检索时噪音大,不精准
└── 召回率低

切片太短:
├── 语义不完整
├── "火箭"切成"火"和"箭"
└── 检索效果很差

切片位置不对:
├── 把一句话切成两半
├── 上下文丢失
└── 用户问"火箭",可能搜不到"搭载宇航员的火箭"

1.2 切片的数学本质

从向量化的角度看,切片的本质是:在语义空间中划分边界

假设文档有两段内容:

段落A:讲"火箭发射"
段落B:讲"宇航员训练"

理想情况:
├── 段落A的向量 → 聚焦在"火箭"语义区域
├── 段落B的向量 → 聚焦在"宇航员"语义区域
└── 两段分开切,向量语义清晰

糟糕情况:
├── 切片切在段落A中间
├── "火箭发射"被切成"火箭"和"发射"
├── 两个向量都模糊,检索效果差
└── 

二、传统切片算法详解

2.1 固定长度切片

原理:按字符数或token数切分。

func fixedChunk(text string, chunkSize int) []string {
	chunks := []string{}
	for i := 0; i < len(text); i += chunkSize {
		end := i + chunkSize
		if end > len(text) {
			end = len(text)
		}
		chunks = append(chunks, text[i:end])
	}
	return chunks
}

示例

原文(100字):
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,
标志着人类重返月球的重要一步。该任务将使用太空发射系统火箭
和猎户座飞船,搭载宇航员进行绕月飞行。阿尔忒弥斯2号的主要
目标是测试生命支持系统..."

固定切片(每50字):
切片1: "阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志"
切片2: "着人类重返月球的重要一步。该任务将使用太空发射系统火箭和猎户"
切片3: "座飞船,搭载宇航员进行绕月飞行。阿尔忒弥斯2号的主要目标是测试..."

缺陷分析

问题1:语义断裂
├── "标志着" → "标志" + "着"
├── "猎户座飞船" → "猎户" + "座飞船"
└── 完整的词被切断

问题2:主题混杂
├── 切片1结尾是"标志"
├── 切片2开头是"着人类"
├── 两段讲的是同一个主题,但被切开了
└── 检索时可能漏掉

问题3:无法适应不同文档
├── 技术文档:句子长,50字可能切不断一句
├── 新闻文档:句子短,50字可能跨多个主题
└── 一刀切,效果不好

缺陷量化

假设文档有N个句子,固定切片产生M个切片:

语义断裂率 = 被切断的句子数 / N
           ≈ 平均句子长度 / 切片长度
           
如果平均句子长30字,切片长50字:
├── 语义断裂率 ≈ 30/50 = 60%
└── 60%的句子会被切断

2.2 按段落切片

原理:用换行符分隔,每个段落作为独立切片。

func paragraphChunk(text string) []string {
	return strings.Split(text, "\n\n")
}

示例

原文:
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,
标志着人类重返月球的重要一步。\n\n
该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航员进行绕月飞行。\n\n
阿尔忒弥斯2号的主要目标是测试生命支持系统..."

按段落切片:
切片1: "阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步。"
切片2: "该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航员进行绕月飞行。"
切片3: "阿尔忒弥斯2号的主要目标是测试生命支持系统..."

缺陷分析

问题1:长度不均
├── 短段落:10个字
├── 长段落:2000个字
├── 向量化时,短段落信息不够,长段落信息过载
└── 检索效果不稳定

问题2:段落划分不可靠
├── 有的文档没有明显的段落标记
├── 有的文档用空格、制表符分隔
├── 有的文档段落之间没有空行
└── 规则不通用

问题3:段落内可能有多主题
├── 一个段落讲了3个概念
├── 但被当成一个切片
└── 检索时噪音大

缺陷量化

长度方差:
Var(L) = Σ(Li - L̄)² / M

方差越大,检索效果越不稳定。

实际测试:
├── 新闻类文档:段落长度方差小,效果较好
├── 技术文档:段落长度方差大,效果不稳定
└── 

2.3 按句子切片

原理:用句号、问号、感叹号分隔。

func sentenceChunk(text string) []string {
	// 简单实现:按句号切
	result := strings.Split(text, "。")
	
	// 更精细的实现需要处理:
	// ├── 问号、感叹号
	// ├── 引号内的句号
	// ├── 小数点(如3.14)
	// └── 省略号
	return result
}

缺陷分析

问题1:句子太短
├── "任务计划在2025年执行。" → 12个字
├── 信息量不够,向量表示不准确
└── 需要合并多个句子

问题2:句子长度差异大
├── "执行。" → 2个字
├── "阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步。" → 40个字
└── 效果不稳定

问题3:上下文丢失
├── 句子A:"该任务将使用太空发射系统火箭。"
├── 句子B:"火箭由波音公司制造。"
├── 切开后,句子A的"火箭"缺少上下文
└── 用户问"波音火箭",可能搜不到

2.4 滑动窗口切片

原理:固定窗口大小,窗口之间有重叠。

func slidingWindow(text string, chunkSize, overlap int) []string {
	chunks := []string{}
	step := chunkSize - overlap
	for i := 0; i < len(text); i += step {
		end := i + chunkSize
		if end > len(text) {
			end = len(text)
		}
		chunks = append(chunks, text[i:end])
	}
	return chunks
}

示例

原文(100字),窗口50字,重叠20字:

切片1: [0:50]    "阿尔忒弥斯2号是...搭载宇航"
切片2: [30:80]   "航员进行绕月...生命支持系"
切片3: [60:110]  "支持系统和...(超出范围截断)"

缺陷分析

问题1:数据冗余
├── 重叠部分被向量化多次
├── 存储空间增加
├── 检索时可能返回重复结果
└── 需要去重

问题2:冗余率计算
├── 重叠率 = overlap / chunkSize
├── 如果重叠20字,窗口50字
├── 冗余率 = 20/50 = 40%
└── 存储40%的冗余数据

问题3:仍然可能切断语义
├── 窗口边界仍然是硬切
├── 只是多了几个"重叠版本"
└── 没有真正解决语义断裂问题

问题4:如何选择重叠大小?
├── 重叠小:上下文不足
├── 重叠大:冗余太多
└── 没有理论依据,只能试

2.5 递归切片

原理:按优先级依次尝试不同的分隔符。

func recursiveChunk(text string, separators []string, chunkSize int) []string {
	if len(text) <= chunkSize {
		return []string{text}
	}
	
	// 按第一个分隔符切
	if len(separators) == 0 {
		return fixedChunk(text, chunkSize)
	}
	
	sep := separators[0]
	parts := strings.Split(text, sep)
	
	chunks := []string{}
	currentChunk := ""
	
	for _, part := range parts {
		if len(currentChunk) + len(part) + len(sep) <= chunkSize {
			if currentChunk != "" {
				currentChunk += sep
			}
			currentChunk += part
		} else {
			if currentChunk != "" {
				chunks = append(chunks, currentChunk)
			}
			
			// 如果单个part超过chunkSize,递归用下一个分隔符
			if len(part) > chunkSize {
				subChunks := recursiveChunk(part, separators[1:], chunkSize)
				chunks = append(chunks, subChunks...)
				currentChunk = ""
			} else {
				currentChunk = part
			}
		}
	}
	
	if currentChunk != "" {
		chunks = append(chunks, currentChunk)
	}
	
	return chunks
}

// 使用:依次尝试 段落 → 句子 → 固定长度
separators := []string{"\n\n", "。", ",", ""}

缺陷分析

问题1:规则仍然固定
├── 虽然有多层规则,但都是预设的
├── 无法适应特殊文档结构
└── 

问题2:无法理解语义
├── "火箭。飞船。" → 切成两个切片
├── 但这两个词可能属于同一个主题
└── 

问题3:递归深度难以控制
├── 某些文档可能触发深层递归
├── 性能不稳定
└── 

三、语义切片的算法原理

在介绍语义切片之前,先理解它背后的算法原理。

3.1 语义边界检测

核心问题:如何判断两个句子属于同一个主题,还是应该切分开?

方法:计算句子之间的语义相似度。

句子A:"该任务将使用太空发射系统火箭。"
句子B:"火箭由波音公司制造。"

如果相似度高 → 属于同一主题 → 不切
如果相似度低 → 主题转换 → 应该切

3.2 句子相似度计算

复用第三篇的余弦相似度,但这次用于句子级别的向量。

// 计算两个句子的语义相似度
func sentenceSimilarity(sentA, sentB string, embedFunc func(string) []float64) float64 {
	vecA := embedFunc(sentA)
	vecB := embedFunc(sentB)
	return cosineSimilarity(vecA, vecB)
}

// 余弦相似度(第三篇讲过)
func cosineSimilarity(a, b []float64) float64 {
	dotProduct := 0.0
	normA := 0.0
	normB := 0.0
	
	for i := range a {
		dotProduct += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}
	
	return dotProduct / (math.Sqrt(normA) * math.Sqrt(normB))
}

示例计算

句子A:"该任务将使用太空发射系统火箭。"
句子B:"火箭由波音公司制造。"
句子C:"宇航员需要进行体能训练。"

相似度(A, B) = 0.85 → 高相似度,同一主题
相似度(B, C) = 0.32 → 低相似度,不同主题,应该切

结论:
├── A和B应该放在同一个切片
├── B和C之间应该切一刀
└── 

3.3 切片边界检测算法

算法思路:遍历相邻句子对,当相似度低于阈值时,标记为边界。

// 基于相似度的切片边界检测
func detectBoundaries(sentences []string, embedFunc func(string) []float64, threshold float64) []int {
	boundaries := []int{0} // 第一个切片从0开始
	
	for i := 0; i < len(sentences)-1; i++ {
		sim := sentenceSimilarity(sentences[i], sentences[i+1], embedFunc)
		
		// 如果相似度低于阈值,标记边界
		if sim < threshold {
			boundaries = append(boundaries, i+1)
		}
	}
	
	return boundaries
}

阈值选择

阈值太高(如0.9):
├── 几乎每句都切
├── 切片太多,太碎
└── 

阈值太低(如0.3):
├── 几乎不切
├── 切片太大
└── 

经验值:0.5-0.7 之间效果较好

3.4 代价函数:切在哪里最合理?

更精细的方法是定义一个代价函数,优化切片质量。

代价函数设计

切片质量 = 切片内紧密度 - 切片间耦合度

切片内紧密度:同一切片内句子的平均相似度(越高越好)
切片间耦合度:相邻切片间句子的相似度(越低越好)

数学表达

设切片集合为 C = {c₁, c₂, ..., cₖ}

代价函数:
Cost(C) = -Σ intra_similarity(cᵢ) + α × Σ inter_similarity(cᵢ, cᵢ₊₁)

其中:
├── intra_similarity(c):切片c内句子的平均相似度
├── inter_similarity(c₁, c₂):切片c₁和c₂边界句子的相似度
├── α:调节参数
└── 目标:最小化Cost

解释:
├── 切片内句子越相似 → intra_similarity越高 → Cost越低 → 好
├── 切片间句子越不相似 → inter_similarity越低 → Cost越低 → 好
└── 

代码实现

// 计算切片内紧密度
func intraSimilarity(sentences []string, start, end int, embedFunc func(string) []float64) float64 {
	if end-start <= 1 {
		return 1.0 // 单句切片,紧密度为1
	}
	
	totalSim := 0.0
	count := 0
	
	for i := start; i < end-1; i++ {
		for j := i + 1; j < end; j++ {
			sim := sentenceSimilarity(sentences[i], sentences[j], embedFunc)
			totalSim += sim
			count++
		}
	}
	
	return totalSim / float64(count)
}

// 计算切片间耦合度
func interSimilarity(sentences []string, boundary int, embedFunc func(string) []float64) float64 {
	// boundary是边界位置,计算边界前一句和后一句的相似度
	if boundary <= 0 || boundary >= len(sentences) {
		return 0.0
	}
	return sentenceSimilarity(sentences[boundary-1], sentences[boundary], embedFunc)
}

// 计算总代价
func computeCost(sentences []string, boundaries []int, embedFunc func(string) []float64, alpha float64) float64 {
	cost := 0.0
	
	// 切片内紧密度(取负,因为我们要最小化代价)
	for i := 0; i < len(boundaries); i++ {
		start := boundaries[i]
		end := len(sentences)
		if i < len(boundaries)-1 {
			end = boundaries[i+1]
		}
		cost -= intraSimilarity(sentences, start, end, embedFunc)
	}
	
	// 切片间耦合度
	for i := 0; i < len(boundaries)-1; i++ {
		boundary := boundaries[i+1]
		cost += alpha * interSimilarity(sentences, boundary, embedFunc)
	}
	
	return cost
}

3.5 动态规划求解最优切片

有了代价函数,可以用动态规划找到最优切片方案。

// 动态规划求解最优切片
func optimalChunking(sentences []string, embedFunc func(string) []float64, minLen, maxLen int) []int {
	n := len(sentences)
	
	// dp[i] = 前i个句子的最小代价
	dp := make([]float64, n+1)
	prev := make([]int, n+1) // 记录最优切分位置
	
	for i := 1; i <= n; i++ {
		dp[i] = math.Inf(1) // 初始化为无穷大
		
		// 尝试所有可能的上一刀位置
		for j := i - minLen; j >= 0 && j >= i-maxLen; j-- {
			// 计算从j到i作为一个切片的代价
			cost := computeChunkCost(sentences, j, i, embedFunc)
			
			// 加上边界惩罚(如果j>0,说明这里有一刀)
			if j > 0 {
				cost += 0.1 * interSimilarity(sentences, j, embedFunc)
			}
			
			if dp[j]+cost < dp[i] {
				dp[i] = dp[j] + cost
				prev[i] = j
			}
		}
	}
	
	// 回溯找到所有边界
	boundaries := []int{}
	i := n
	for i > 0 {
		boundaries = append([]int{prev[i]}, boundaries...)
		i = prev[i]
	}
	
	return boundaries
}

// 计算单个切片的代价
func computeChunkCost(sentences []string, start, end int, embedFunc func(string) []float64) float64 {
	return -intraSimilarity(sentences, start, end, embedFunc)
}

3.6 算法对比

方法 原理 优点 缺点
相似度阈值 相似度<阈值就切 简单直观 阈值难选择
代价函数优化 最小化代价函数 理论最优 计算复杂度高
大模型语义切片 用LLM理解语义 效果最好 需要调用API

四、语义切片:让大模型来切

前面介绍了基于相似度和代价函数的算法方法,但它们有局限性:

基于相似度的方法:
├── 需要预训练的嵌入模型
├── 相似度阈值需要调参
└── 对复杂语义关系理解有限

基于代价函数的方法:
├── 计算复杂度高
├── 可能陷入局部最优
└── 对长文档效率低

大模型的优势:天然具有语义理解能力,能识别复杂的主题边界。

4.1 语义切片的优势

问题 传统方法 语义切片
语义断裂 机械切,不懂语义 理解语义,切在边界
长度不均 无法控制 可以指定长度要求
上下文丢失 切断后丢失 理解上下文,保留完整意思
无法适应 规则固定 自适应不同文档
主题混杂 无法识别 识别主题边界

4.2 实现方法

方法1:让大模型返回切片结果

提示词:
请把下面的文档按语义切片。要求:
1. 每一块讲一个完整的主题
2. 用 ===SPLIT=== 标记切分点
3. 保持原文内容不变
4. 每块长度控制在100-300字

文档:
[文档内容...]

大模型返回:
第一块内容...
===SPLIT===
第二块内容...
===SPLIT===
第三块内容...

方法2:让大模型返回边界位置(省token)

提示词:
请分析下面的文档,返回每个切片的起始和结束位置。
格式:JSON数组,每项包含 start 和 end(字符位置)
要求:每一块应该讲一个完整的主题

文档:
[文档内容...]

大模型返回:
[
  {"start": 0, "end": 87},
  {"start": 87, "end": 185},
  {"start": 185, "end": 273}
]

4.3 Go代码实现

package main

import (
	"encoding/json"
	"fmt"
	"strings"
)

// 切片边界
type ChunkBoundary struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

// 语义切片(调用大模型)
func semanticChunk(text string, llmClient LLMClient) []string {
	// 构建提示词
	prompt := fmt.Sprintf(`请分析下面的文档,按语义主题进行切片。
返回JSON数组,每项包含start和end(字符位置)。
注意:
1. 每一块应该讲一个完整的主题
2. 切片边界应该在自然段落或句子之间
3. 不要修改原文内容
4. 每块长度控制在100-300字

文档:
%s

只返回JSON数组,不要其他内容。`, text)

	// 调用大模型
	response, err := llmClient.Chat(prompt)
	if err != nil {
		// 失败时回退到固定切片
		return fixedChunk(text, 500)
	}

	// 解析返回的JSON
	var boundaries []ChunkBoundary
	err = json.Unmarshal([]byte(response), &boundaries)
	if err != nil {
		return fixedChunk(text, 500)
	}

	// 按边界切片
	chunks := []string{}
	for _, b := range boundaries {
		if b.End > len(text) {
			b.End = len(text)
		}
		chunk := strings.TrimSpace(text[b.Start:b.End])
		if len(chunk) > 0 {
			chunks = append(chunks, chunk)
		}
	}

	return chunks
}

// 固定切片(备用方案)
func fixedChunk(text string, chunkSize int) []string {
	chunks := []string{}
	for i := 0; i < len(text); i += chunkSize {
		end := i + chunkSize
		if end > len(text) {
			end = len(text)
		}
		chunk := strings.TrimSpace(text[i:end])
		if len(chunk) > 0 {
			chunks = append(chunks, chunk)
		}
	}
	return chunks
}

// LLM客户端接口
type LLMClient interface {
	Chat(prompt string) (string, error)
}

4.4 使用Ollama + DeepSeek实现

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// Ollama客户端
type OllamaClient struct {
	BaseURL string
	Model   string
}

func NewOllamaClient(baseURL, model string) *OllamaClient {
	return &OllamaClient{
		BaseURL: baseURL,
		Model:   model,
	}
}

func (c *OllamaClient) Chat(prompt string) (string, error) {
	reqBody := map[string]interface{}{
		"model":  c.Model,
		"prompt": prompt,
		"stream": false,
	}

	jsonData, _ := json.Marshal(reqBody)
	resp, err := http.Post(c.BaseURL+"/api/generate", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	var result map[string]interface{}
	json.Unmarshal(body, &result)

	return result["response"].(string), nil
}

func main() {
	// 创建Ollama客户端
	client := NewOllamaClient("http://localhost:11434", "deepseek-r1:1.5b")

	// 待切片的文档
	text := `阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,
标志着人类重返月球的重要一步。该任务将使用太空发射系统火箭和猎户座飞船,
搭载宇航员进行绕月飞行。

阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力。
任务计划在2025年执行,将是自阿波罗17号以来首次载人离开地球轨道的任务。

此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证。
宇航员将在飞行过程中进行科学实验,并测试深空通信和导航系统。`

	// 语义切片
	chunks := semanticChunk(text, client)

	fmt.Println("=== 语义切片结果 ===")
	for i, chunk := range chunks {
		fmt.Printf("\n切片 %d:\n%s\n", i+1, chunk)
	}

	// 对比固定切片
	fixedChunks := fixedChunk(text, 100)

	fmt.Println("\n=== 固定切片结果(每100字)===")
	for i, chunk := range fixedChunks {
		fmt.Printf("\n切片 %d:\n%s\n", i+1, chunk)
	}
}

五、对比实验

5.1 测试文档

原文(273字):
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步。
该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航员进行绕月飞行。

阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力。
任务计划在2025年执行,将是自阿波罗17号以来首次载人离开地球轨道的任务。

此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证。
宇航员将在飞行过程中进行科学实验,并测试深空通信和导航系统。"

5.2 固定切片(每100字)

切片1 (0-100):
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步。
该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航"

切片2 (100-200):
"员进行绕月飞行。

阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力。
任务计划在2025年执行,将是自阿波罗17号以来首次载人离"

切片3 (200-273):
"开地球轨道的任务。

此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证。
宇航员将在飞行过程中进行科学实验,并测试深空通信和导航系统。"

问题

  • “搭载宇航员"被切成"搭载宇航"和"员进行”
  • “离开地球轨道"被切成"首次载人离"和"开地球轨道”

5.3 语义切片

切片1 (0-87):
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步。
该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航员进行绕月飞行。"

切片2 (87-185):
"阿尔忒弥斯2号的主要目标是测试生命支持系统和宇航员在深空环境中的生存能力。
任务计划在2025年执行,将是自阿波罗17号以来首次载人离开地球轨道的任务。"

切片3 (185-273):
"此次飞行将为后续的阿尔忒弥斯3号月球着陆任务提供关键数据和技术验证。
宇航员将在飞行过程中进行科学实验,并测试深空通信和导航系统。"

效果

  • 每块讲一个完整的主题
  • 没有切断句子
  • 长度均匀(87、98、88字)

5.4 效果对比

指标 固定切片 语义切片
切片数 3 3
语义断裂 2处 0处
长度方差 484 30
主题完整度 33% 100%

六、优化技巧

6.1 用便宜模型就够了

语义切片任务特点:
├── 只需要判断"哪里切"
├── 不需要复杂的推理
├── 不需要创意写作
└── 用小模型/便宜模型即可

推荐:
├── DeepSeek(便宜)
├── Qwen(便宜)
├── 本地Ollama跑的小模型
└── 成本可控

6.2 混合策略

第一步:粗切(免费)
├── 用固定长度或段落切
├── 切成较大的块(比如1000字)

第二步:精切(按需)
├── 让模型判断每个大块是否需要再分
├── 只对需要的大块做语义切片
└── 减少API调用次数

6.3 缓存切片结果

切片结果可以缓存:
├── 文档不变,切片就不变
├── 存下来,下次直接用
├── 不用每次都调大模型
└── 

七、总结

7.1 传统方法的根本缺陷

所有传统方法都是"机械规则":
├── 看字数、看标点、看换行
├── 不理解文本在讲什么
└── 无法识别语义边界

导致的问题:
├── 语义断裂:句子被切断
├── 长度不均:有的太长,有的太短
├── 上下文丢失:切开后人读不懂
└── 无法适应:规则固定,不能灵活调整

7.2 语义切片的优势

语义切片是"智能理解":
├── 理解文本在讲什么
├── 识别主题边界
├── 切在语义完整的位置
└── 自适应不同文档

解决的问题:
├── 语义完整:每块讲一个完整的主题
├── 长度均匀:可以指定长度范围
├── 上下文保留:切开后人能读懂
└── 灵活适应:不同文档自动调整

7.3 和前面文章的关系

第一篇:Prompt RAG
├── 文档直接喂给大模型
└──

第二篇:文本向量化
├── 把文本变成向量
└──

...

第八篇:语义切片 ← 你在这里
├── 把文档切成合理的块
├── 切好了,后面的向量化才准
├── 用大模型的语义理解能力解决传统方法的缺陷
└──

完整RAG流程:
文档 → 语义切片 → 向量化 → 存储 → 检索 → 返回
         ↑
    这一步决定后续效果

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐