Embeddings 怎样分块长文本
来源:17golang原创
时间:2026-10-25 11:57:26 185浏览 收藏
长文档直接做 Embeddings,问题通常不在“向量不够多”,而在一个块里混入了多个主题,或者把关键定义截在了两个块之间。我的做法是先按标题和段落保留语义边界,再用 token 预算兜底,最后用少量重叠覆盖跨段关系。Embeddings API 的输入按 token 计费,当前文档列出的第三代模型最大输入为 8192 token,所以分块预算不能只看字符数。
官方地址:https://developers.openai.com/api/docs/guides/embeddings
- 先按标题、段落、句子切分,token 上限只负责兜底。
- overlap 用来保留跨块语义,不是越大越好;可先从 10%~15% 的块长度试起。
- 每个向量必须保存来源、位置、版本和分块参数,方便回溯与重建。
更稳的起点不是固定“每 500 字一块”,而是先保证一个块只回答一个相近问题,再在模型上限内调节长度。检索问答效果应由真实问题集决定,不能只凭块数或向量维度判断。
先把分块目标定成可检索的证据单元
一个好块应当能独立表达一个小主题:例如“退款条件”和“退款操作步骤”可以相邻,但不要把整章产品介绍、异常处理和价格说明塞进同一个向量。建议给块保留 doc_id、chunk_id、标题路径、字符区间、版本号和正文原文。这样召回后可以展示上下文,也能在文档更新时只重建变动部分。
长度要用 tokenizer 估算,而不是把中文字符数当成 token 数。以当前文档为例,8192 token 是模型输入的上限,不代表检索块应该贴着上限;还要给查询、元数据拼装和后续生成留出空间。工程上可以先把单块预算放在 400~900 token,再用评测集观察是否出现“命中标题、缺少定义”或“召回内容过宽”。
按语义边界切分,超长时再递归降级
切分顺序可以是标题路径 → 段落 → 句子 → 词或字符。先尝试把完整段落放入当前块;放不下时再拆句,单句仍超长才做硬切。下面的示例省略具体 tokenizer 实现,但把分块的关键边界写清楚,实际项目中应替换为与所选模型匹配的计数器。
def split_text(paragraphs, max_tokens=700, overlap_tokens=80):
# paragraphs 已按标题路径整理;tokenize 应使用项目实际模型的 tokenizer
chunks, current = [], []
current_tokens = 0
for paragraph in paragraphs:
paragraph_tokens = tokenize(paragraph)
if len(paragraph_tokens) > max_tokens:
# 超长段落继续按句子拆分,避免直接截断定义
sentences = split_sentences(paragraph)
else:
sentences = [paragraph]
for sentence in sentences:
sentence_tokens = tokenize(sentence)
if current and current_tokens + len(sentence_tokens) > max_tokens:
chunks.append("\n".join(current))
# 只保留尾部少量句子,形成可控的语义重叠
current = keep_tail_by_tokens(current, overlap_tokens)
current_tokens = count_tokens(current)
current.append(sentence)
current_tokens += len(sentence_tokens)
if current:
chunks.append("\n".join(current))
return [chunk for chunk in chunks if count_tokens(chunk) >= 40]
这里的 700 和 80 只是可调参数,不是通用答案。法律条款、接口参数表和故障排查步骤的最佳边界往往不同;如果一个块经常被召回却缺少前置定义,应优先调整语义切分,而不是盲目增加 overlap。

overlap 只补跨块关系,别把重复文本扩大成噪声
重叠适合处理“上一段定义、下一段条件”这类跨边界信息。可以从目标块长度的 10%~15% 开始,观察相邻块是否重复太多、向量库体积是否明显上涨。短文档不需要强行重叠;表格、代码和编号清单也不宜按普通句子切开,最好把表头或前置说明随块保存。
分块完成后先做三项清理:去掉空白和纯导航文本;合并过短且主题相同的尾块;对同一版本的重复内容做指纹去重。每个块写入向量库时,同时保存 source_start、source_end 和 chunker_version,否则以后很难解释“为什么这次命中了另一段”。
批量生成向量,再用检索反馈调参数

OpenAI 文档说明 Embeddings 返回的是浮点数向量,可保存到向量数据库用于检索;输入也可以按数组批量提交。批量处理时要保持输入块与返回结果的索引一一对应,并把模型名、分块版本和生成时间写进记录。
from openai import OpenAI
client = OpenAI()
chunks = [{"chunk_id": "doc-7-003", "text": "退款条件与例外说明"}]
# 一次提交同一批文本,按返回顺序绑定 chunk_id;生产环境还要记录模型与版本
response = client.embeddings.create(
model="text-embedding-3-small",
input=[item["text"].replace("\n", " ") for item in chunks],
)
records = []
for item, embedding in zip(chunks, response.data):
# metadata 与向量一起保存,召回后才能回到原文位置
records.append({"id": item["chunk_id"], "vector": embedding.embedding,
"metadata": {"chunker_version": "v1"}})
评测时不要只看向量数量。准备一组真实查询,记录正确块是否在前几名、定义是否与条件同时出现、回答是否需要跨三个以上块才能完成。若命中内容过碎,增大块长度或合并相邻主题;若命中内容总是混杂,缩短块并加强标题路径元数据。这样调参才有明确方向。
| 现象 | 优先调整 | 不要先做 |
|---|---|---|
| 命中标题但缺少定义 | 语义边界、少量 overlap | 直接把块扩大到模型上限 |
| 每次召回都带大量重复 | 降低 overlap、做指纹去重 | 继续增加召回数量 |
| 旧文档内容被召回 | 版本过滤与增量重建 | 只改相似度阈值 |
相关问题
中文长文本一定要按字数切吗?
不一定。字数只能做粗略限制,最终应以目标模型 tokenizer 的 token 数为准,并优先保留标题、段落和句子边界。
overlap 越大,检索效果越好吗?
不是。过大的 overlap 会制造重复向量和重复上下文;从较小比例开始,用真实查询集验证更稳妥。
分块后还要保存原文吗?
要保存。向量只适合相似度检索,回答和审计仍需要原文、来源位置、版本及分块参数来回溯。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
439 收藏
-
241 收藏
-
208 收藏
-
420 收藏
-
418 收藏
-
313 收藏
-
101 收藏
-
126 收藏
-
418 收藏
-
307 收藏
-
250 收藏
-
397 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习