news 2026/9/27 20:39:16

RAG 分块策略实测:固定长度、递归切分与语义切分如何选择,TaoToken 统一 Key 接入配置骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG 分块策略实测:固定长度、递归切分与语义切分如何选择,TaoToken 统一 Key 接入配置骨架

1. 为什么你的 RAG 总是答非所问:分块策略才是第一道质量闸门

RAG 分块策略决定了检索召回的上限,固定长度、递归切分与语义切分三种方案在证据完整性、上下文噪声和 Token 成本上差异巨大。如果你正在搭企业知识库、做文档问答,或者调了半天 Prompt 发现回答还是不稳定,那大概率问题不在模型,而在切分。我见过太多项目把 Chunk Size 当成一个拍脑袋的经验参数——切 500 字、重叠 50 字,然后就开始换模型、改 Prompt、加 Rerank,唯独不回头看“证据到底有没有落在正确的片段里”。

分块策略至少影响五个维度:检索召回(正确证据能否进前 K)、证据完整性(前置条件和例外情况有没有被切断)、噪声比例(无关文本占了多少上下文)、生成成本(每次注入的 Token 数)、更新成本(改一段要不要重算一堆向量)。一份制度文档里,“仅在 E102 连续出现三次时”这个条件如果被切到了下一个 Chunk,模型就只能看到“必须先停止服务”,回答自然缺胳膊少腿。

这篇会带你走完一条可复现的实测路径:先固定实验变量,再分别跑固定长度、递归切分、语义切分三种策略,用 Recall@K、MRR、引用准确率和成本四个指标做对比,最后给出一套可直接复制的config.toml与settings.json配置骨架,并通过 TaoToken 统一 Key 接入 AI 工具完成向量化和生成验证。适合已经跑通 RAG 基础链路、想系统优化检索质量的数据工程和算法同学。

2. TaoToken 前置:统一 Key 与 API 通道准备

在开始分块实验之前,先把模型调用通道固定下来。分块策略对比要求 Embedding 模型和生成模型在整个实验过程中保持一致,否则分数变化你根本分不清是切分带来的还是模型换了的。TaoToken 在这里的作用是提供一个统一的 Key 和 API 入口,让你在 Python 脚本、向量化服务、生成服务之间复用同一套凭证,不用每个工具单独配一遍。

你需要准备的东西很简单:一个 TaoToken 账号,一个 API Key,以及确认你要用的 Embedding 模型和生成模型名称。API 基础地址是https://taotoken.net/api,所有请求走这个入口。如果你还没建 Key,可以到控制台创建:

  • 创建和管理 API Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档(含各语言 SDK 示例):https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

注意:Embedding 模型和生成模型建议分开配置 Key 用途标签,方便后续按实验轮次统计成本。同一轮实验内不要更换模型版本。

如果你后续要做长期编码或 Agent 类任务,可以了解 Coding Plan;如果只是想先验证模型对话效果,可以直接用模型对话页面试几条问题:

  • 模型对话体验:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite
  • Coding Plan 详情:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

3. 可复制配置:config.toml 与 settings.json 骨架

下面这套配置骨架把分块参数、Embedding 参数、检索参数和 TaoToken 接入信息集中管理,方便你在不同策略之间切换时只改一个字段。先建目录结构:

mkdir -p rag-chunk-lab/{config,data,scripts,results} cd rag-chunk-lab

config/config.toml内容如下,重点是[chunk]段,三种策略通过strategy字段切换:

[taotoken] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" embedding_model = "your-embedding-model" chat_model = "your-chat-model" timeout_seconds = 60 [chunk] # 可选值: fixed | recursive | semantic strategy = "recursive" max_chars = 800 overlap_chars = 80 semantic_threshold = 0.45 semantic_max_chars = 900 [retrieval] top_k = 5 distance = "cosine" [eval] question_set = "data/questions.jsonl" gold_labels = "data/gold_labels.jsonl" output_dir = "results"

config/settings.json用来放运行时覆盖项和实验元信息,方便记录 Git Commit 和模型版本:

{ "experiment_id": "chunk-exp-001", "embedding_dim": 1536, "normalize": true, "record_git_commit": true, "strategies": ["fixed", "recursive", "semantic"], "top_k_list": [3, 5, 10], "notes": "同一批文档、同一问题集,仅切换 chunk.strategy" }

环境变量里设置 Key,不要写进配置文件:

export TAOTOKEN_API_KEY="你的Key"

提示:embedding_dim要和实际模型输出维度一致,本文以 1536 维为例。维度不匹配会导致向量库写入失败或检索结果异常。

4. 三种切分策略的实现与验证请求

4.1 固定长度切分

按字符数截断并保留固定重叠,实现最简单,适合快速建立基线:

def fixed_split(text: str, size: int = 500, overlap: int = 80) -> list[str]: if size <= overlap: raise ValueError("size 必须大于 overlap") chunks, start = [], 0 while start < len(text): end = min(len(text), start + size) value = text[start:end].strip() if value: chunks.append(value) if end == len(text): break start = end - overlap return chunks

固定切分适合日志、连续转写文本这类结构很弱的内容。但在制度文档里要小心:一个条款的“例外情况”被切到下一个 Chunk 后,模型很容易只看到主规则而忽略例外。

4.2 递归结构切分

优先按标题、段落、列表、句子等分隔符切分,只有当单元超过最大长度时才继续拆。分隔符顺序是关键,把句号放在标题之前会让结构信息先丢失:

SEPARATORS = ["\n## ", "\n### ", "\n\n", "\n", "。", ";", " "] def recursive_split(text: str, max_chars: int = 800) -> list[str]: text = text.strip() if len(text) <= max_chars: return [text] if text else [] for separator in SEPARATORS: parts = text.split(separator) if len(parts) == 1: continue result, current = [], "" for part in parts: candidate = f"{current}{separator}{part}" if current else part if len(candidate) <= max_chars: current = candidate else: result.extend(recursive_split(current, max_chars)) current = part result.extend(recursive_split(current, max_chars)) return result return [text[:max_chars], *recursive_split(text[max_chars:], max_chars)]

递归切分通常是通用文档的首选基线,它保留了标题层级和段落边界,同时用最大长度兜底。

4.3 语义切分

先按句子切分,再计算相邻句子的语义相似度,相似度明显下降时聚合为一个语义单元:

def semantic_split(sentences, embeddings, threshold=0.45): groups, current = [], [] for index, sentence in enumerate(sentences): current.append(sentence) if index == len(sentences) - 1: break similarity = cosine(embeddings[index], embeddings[index + 1]) if similarity < threshold: groups.append("".join(current).strip()) current = [] if current: groups.append("".join(current).strip()) return groups

语义阈值不能凭感觉定。阈值过低,多个主题会被粘在一起;阈值过高,Chunk 数量暴涨,检索噪声和索引成本都会上升。实际使用时还要叠加最大 Token 限制。

4.4 通过 TaoToken 发起向量化与生成请求

用统一 Key 调用 Embedding 接口,把切好的 Chunk 批量向量化:

import os, requests BASE = "https://taotoken.net/api" HEADERS = { "Authorization": f"Bearer {os.environ['TAOTOKEN_API_KEY']}", "Content-Type": "application/json", } def embed(texts: list[str], model: str) -> list[list[float]]: resp = requests.post( f"{BASE}/embeddings", headers=HEADERS, json={"model": model, "input": texts}, timeout=60, ) resp.raise_for_status() return [item["embedding"] for item in resp.json()["data"]]

生成阶段同样走统一通道,把 Top-K 召回的 Chunk 拼进上下文:

def answer(question: str, contexts: list[str], model: str) -> str: prompt = "仅根据以下资料回答,资料不足时明确说明。\n\n" prompt += "\n---\n".join(contexts) prompt += f"\n\n问题:{question}" resp = requests.post( f"{BASE}/chat/completions", headers=HEADERS, json={ "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0, }, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]

5. 验证请求与成功结果:指标计算与效果对比

跑完三种策略后,用同一问题集计算 Recall@K、MRR、引用准确率和成本。问题集要覆盖四类:原文复述、跨段推理、带条件的故障处理、知识库外问题。每条问题标注正确证据 Chunk:

{ "id": "q-017", "question": "E102连续出现三次后,重启服务前还需要做什么?", "gold_chunk_ids": ["doc-8-p18-c03", "doc-8-p18-c04"], "answer_points": ["检查采集链路", "确认影响范围", "再重启采集服务"], "is_answerable": true }

Recall@K 计算正确证据是否被召回:

def recall_at_k(gold: set, retrieved: list, k: int) -> float: top = set(retrieved[:k]) return len(gold & top) / len(gold) if gold else 0.0

MRR 更重视正确证据是否靠前:

def reciprocal_rank(gold: set, retrieved: list) -> float: for rank, cid in enumerate(retrieved, start=1): if cid in gold: return 1.0 / rank return 0.0

下面是一份实验记录格式示例,数值用于说明记录方式,你需要替换成自己数据集的实际结果:

策略Chunk 数Recall@5MRR平均输入 TokenP95 检索延迟
固定 500/8012400.780.66312082ms
递归 8009800.840.73268075ms
语义 + 最大 90011300.860.76281096ms

从这组数据不能直接得出“语义切分永远最好”。它只说明在当前数据集上语义切分可能提高证据命中,但索引和检索成本也更高。还要看知识库外问题的拒答率、引用准确率和不同文档类型的分组结果。如果制度文档 Recall 高但引用准确率低,可能是 Chunk 包含了多个相互冲突的条款;如果故障手册 Recall 低,可能是“现象—原因—处理步骤”被拆成了三个不相邻的片段。

验证成功的标志是:同一文档重复导入 Chunk 数保持不变;改一个段落只更新受影响的向量;标题、页码、章节版本能在回答中正确引用;知识库外问题会拒答而不是返回相似但错误的内容。

6. 本篇常见错排查

报错一:size must be greater than overlap。固定切分里size <= overlap会直接抛错。检查config.toml里max_chars和overlap_chars的关系,重叠一般取最大长度的 10% 到 15%。

报错二:向量维度不匹配。写入向量库时报dimension mismatch,通常是settings.json里的embedding_dim和实际模型输出不一致。先用一条文本调 Embedding 接口,打印len(embedding)确认。

报错三:递归切分返回空列表。如果文本全是分隔符或空白,recursive_split可能返回空。在入口处加if not text.strip(): return [],并在入库前过滤空 Chunk。

报错四:语义切分 Chunk 数量暴涨。阈值设太高(比如 0.7)会把每个句子都切成独立单元。先把阈值降到 0.4 到 0.5 之间试,再叠加semantic_max_chars兜底。

报错五:401 鉴权失败。检查TAOTOKEN_API_KEY环境变量是否导出成功,请求头是否是Bearer前缀。用curl快速验证:

curl -s https://taotoken.net/api/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" | head -c 300

报错六:检索延迟随 Chunk 数线性上升。Chunk 数增加十倍时 P95 延迟如果超出可接受范围,先检查向量索引类型和是否做了归一化。必要时对检索结果做 Rerank 而不是无限增大 Top-K。

排障和接入相关问题,优先查接入文档和 API Key 管理页:

  • API Keys 管理:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

7. 混合策略与上线前验证清单

实际工程中最稳妥的方案通常不是三选一,而是分层处理:先解析标题、表格、列表、代码块和页码,以章节或业务小节作为一级 Chunk;一级 Chunk 超过 Token 上限时用递归切分;对长段落或无结构文本用语义切分;为每个子 Chunk 保存父章节标题和来源位置;检索子 Chunk,回答时回填父章节摘要。这样既保留文档结构,又避免单个片段过长。父子 Chunk 要注意权限继承,子 Chunk 不能因为回填父章节而跨越原有权限边界。

上线前逐条核对:同一文档重复导入 Chunk 数是否稳定;改一个段落是否只更新受影响的向量;标题、页码、章节和版本信息能否正确引用;知识库外问题是否拒答;不同租户是否得到不同证据集合;Embedding 模型升级后旧向量是否与新向量混用;Chunk 数增加十倍时 P95 延迟是否仍可接受;评测集是否和调参数据隔离。

分块策略没有脱离业务的万能答案。固定切分适合建立基线,递归切分适合大多数结构化文档,语义切分适合主题变化明显且对证据完整性要求高的内容。建议先用递归结构切分跑通全链路,再建立小型评测集,最后针对失败样本选择性引入语义切分。先测量,再优化,比盲目更换模型更省时间和成本。

如果你要长期跑编码或 Agent 类任务,可以进一步了解 Coding Plan 的额度方案;如果只是想快速验证某条问题在统一通道下的回答效果,直接到模型对话页面试几条:

  • 模型对话:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite
  • Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 20:33:01

光模块从400G到1.6T:垂直整合、LPO与硅光的技术博弈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 20:32:10

PA2(上)课程实验通关指南:从环境搭建到调试避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/27 20:32:10

告别VeriStand、dSPACE、LabVIEW:自主工具链迁移实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华