RAG 文档分块完全指南:从一次"知识库问答读到半句话、半张表格、答案没法溯源"看懂 Chunking 策略

2024 年我做一个企业知识库问答系统用 RAG 把公司的几百份文档喂给大模型。第一版我做得很省事把每份文档按固定 500 个字符机械地切成一块块 chunk 逐块向量化存进向量库用户提问时检索出最相关的几块塞给模型作答。本地我拿几篇排版规整的文档测了测真不错问什么答什么有来有回。我心里很踏实RAG 的分块嘛就是把文档按固定长度切成一段段不就行了。可等这个系统真正上线面对公司里那些格式五花八门的真实文档一串问题冒了出来。第一种最先把我打懵用户问一个问题检索回来的 chunk 正好从一句话的中间断开前半句在上一块后半句在下一块模型拿到的是半句没头没尾的话根本看不懂。第二种一个完整的操作步骤被固定长度拦腰切成了两块检索只命中其中一块模型就只答了一半的步骤。第三种一份文档里的表格被从中间切断表头留在上一块数据行落在下一块模型读到那半张没有表头的数据输出的全是乱的。第四种最隐蔽每个 chunk 里只有光秃秃的正文不知道它来自哪份文档属于哪一章节模型答出来的东西既没法告诉用户出处在哪我也没法判断这块内容是不是早就过时了。我盯着这一连串问题想了很久才彻底想明白第一版错在我以为 RAG 的分块就是把文档按固定长度机械地切成一段段。可它不是。文档是有结构的它有标题的层级有段落的边界有列表有表格有代码块这些都是不可切断的语义单元固定长度一刀切这把刀根本不看这些结构。真正做好 RAG 的分块核心不是按长度切得整齐而是理解文档的结构沿着它的天然边界下刀把每一块的大小控制在合适区间并让边界互相重叠还要给每一块带上能溯源的元数据。本文从头梳理为什么固定长度一刀切是错的怎么按文档结构递归地切分 chunk 大小和重叠该怎么定每个 chunk 该带上哪些元数据表格和代码这类特殊内容怎么切以及检索粒度父子分块 chunk 更新这些把分块真正做对要避开的坑。

2024 年我做一个企业知识库问答系统,用 RAG 把公司的几百份文档喂给大模型。第一版我做得很省事:把每份文档,按固定 500 个字符,机械地切成一块块 chunk,逐块向量化、存进向量库;用户提问时,检索出最相关的几块,塞给模型作答。本地我拿几篇排版规整的文档测了测——真不错:问什么答什么,有来有回。我心里很踏实:"RAG 的分块嘛,就是把文档按固定长度,切成一段段,不就行了。"可等这个系统真正上线、面对公司里那些格式五花八门的真实文档,一串问题冒了出来。第一种最先把我打懵:用户问一个问题,检索回来的 chunk,正好从一句话的中间断开——前半句在上一块、后半句在下一块,模型拿到的是半句没头没尾的话,根本看不懂。第二种:一个完整的操作步骤,"第一步……第二步……第三步……",被固定长度拦腰切成了两块,检索只命中其中一块,模型就只答了一半的步骤。第三种:一份文档里的表格,被从中间切断,表头留在上一块、数据行落在下一块,模型读到那半张没有表头的数据,输出的全是乱的。第四种最隐蔽:每个 chunk 里只有光秃秃的正文,不知道它来自哪份文档、属于哪一章节——模型答出来的东西,既没法告诉用户"出处在哪",我也没法判断"这块内容是不是早就过时了"。我盯着这一连串问题想了很久才彻底想明白,第一版错在一个根本的认知上:我以为"RAG 的分块,就是把文档按固定长度,机械地切成一段段"。这句话把"文档"当成了一长串没有结构的字符流。可它不是文档是有结构的:它有标题的层级、有段落的边界、有列表、有表格、有代码块——这些都是不可切断的"语义单元"。固定长度一刀切,这把刀根本不看这些结构,它会从句子中间、从步骤中间、从表格中间,任意地剁下去。真正做好 RAG 的分块,核心不是"按长度切得整齐",而是理解文档的结构、沿着它的天然边界下刀、把每一块的大小控制在合适区间并让边界互相重叠、还要给每一块带上能溯源的元数据。这篇文章就把 RAG 的文档分块梳理一遍:为什么"固定长度一刀切"是错的、怎么按文档结构递归地切分、chunk 大小和重叠该怎么定、每个 chunk 该带上哪些元数据、表格和代码这类特殊内容怎么切,以及检索粒度、父子分块、chunk 更新这些把分块真正做对要避开的坑。

问题背景

先把那串问题的现象和我的误判讲清楚,后面所有的设计都是冲着纠正这个误判去的。

现象:把文档按固定 500 字符机械切块之后,上线冒出一串问题:检索回来的 chunk 从句子中间断开,模型看到半句话;一个完整的操作步骤被拦腰切成两块,模型只答了一半;表格被从中间切断,表头和数据分了家,模型读出来全是乱的;chunk 里只有正文、没有来源信息,答案无法溯源、也无法判断是否过时。

我当时的错误认知:"RAG 的分块,就是把文档按固定长度,机械地切成一段段。"

真相:分块(chunking)是 RAG 流程里极容易被低估的一环。很多人觉得它只是个"把长文本切短"的预处理小步骤,随便切切就行。可实际上,分块的质量,直接决定了检索的质量,而检索的质量,又决定了整个 RAG 的回答质量。原因在于:大模型最终看到的,不是完整的文档,而是被切出来、又被检索回来的那几个 chunk。如果 chunk 本身就是残缺的——半句话、半张表、半个步骤——那么无论你的向量检索多准、模型多强,它能拿到的素材就是残缺的,答案自然也好不了。更关键的是,文档从来不是均匀的字符流:它有标题层级、有段落、有列表、有表格、有代码块。这些是语义上的"完整单元",一旦被从中间切断,语义就碎了。所以分块的本质,不是"切得整齐",而是"切得不破坏语义"

要把 RAG 的文档分块做对,需要几块认知:

  • 为什么"固定长度一刀切"是错的——它会切断句子、步骤、表格;
  • 按结构切分——沿着标题、段落这些天然边界下刀;
  • 大小与重叠——chunk 不太大不太小,边界要互相缝合;
  • 元数据——每个 chunk 要带上来源、标题路径,才能溯源;
  • 特殊内容、父子分块、chunk 更新这些工程坑怎么处理。

一、为什么"固定长度一刀切"是错的

先把这件最根本的事钉死:一份文档,在你眼里是有结构、有层次的——你一眼能看出哪里是标题、哪里是一个完整的步骤、哪里是一张表格。但在"按固定长度切"的程序眼里,文档只是一根长长的字符串,它数到第 500 个字符,就"咔"地剁一刀,完全不管这第 500 个字符此刻正落在哪里。它可能落在一个词的中间,可能落在一句话的中间,可能落在"第二步"和"第三步"的中间,也可能落在一张表格的表头和数据之间。这把刀是"瞎"的——它对文档的语义结构一无所知。

下面这段代码,就是我那个"上线就切碎语义"的第一版:

# 反面教材:把文档按固定字符数,机械地一刀一刀切
def naive_chunk(text, size=500):
    chunks = []
    for i in range(0, len(text), size):
        chunks.append(text[i:i + size])
    return chunks
    # 破绽一:第 500 个字符,可能正落在一句话、一个词的中间。
    # 破绽二:一个完整的步骤列表、一张表格,会被任意切断成两块。
    # 破绽三:切出来的 chunk 只有正文,不知道它来自哪篇文档、哪一章。

这段代码在本地拿排版规整的文档测试时表现尚可,因为那几篇文档段落短小、没有表格、没有跨段的长步骤,固定长度的刀恰好没切到要害。它的问题不在代码本身,而在一个被忽略的前提:它默认"文档是一根可以任意位置切断的均匀字符流"。可真实的文档充满了不可切断的语义单元。于是那串问题就有了解释:读到半句话,是因为刀切在了句子中间;步骤只答一半,是因为一个列表被切进了两个 chunk;表格读成乱码,是因为表头和数据被切散了。问题的根子清楚了:做好分块的工程量,全在"承认文档有结构、有不可切断的语义单元"之后——你不让刀去看文档的结构,就只能切出一地语义的碎片。先从让刀"长出眼睛"说起。

二、按结构切分:沿文档的天然边界下刀

让分块的刀"长出眼睛",核心思路是递归切分(recursive splitting):不要一上来就按字符数硬切,而是优先沿着文档"最大的、语义最完整的边界"切;如果切出来的块还是太大,再退一步,沿着"次一级的边界"切;层层递进,直到每一块都足够小。文档的边界,是有语义强弱之分的:段落之间的空行(最强)、单个换行句号叹号问号空格、最后才是单个字符(最弱、最不得已):

def recursive_split(text, max_size=500):
    """递归分块:优先沿"强"边界切,切不够小,再退用"弱"一级边界。"""
    # 分隔符按"语义强度"从强到弱排列
    separators = ["\n\n", "\n", "。", "!", "?", " ", ""]

    def _split(s, seps):
        if len(s) <= max_size:
            return [s] if s.strip() else []
        sep = seps[0]
        parts = s.split(sep) if sep else list(s)
        chunks, buf = [], ""
        for p in parts:
            piece = p + sep
            if len(buf) + len(piece) <= max_size:
                buf += piece                  # 还装得下,继续攒
            else:
                if buf:
                    chunks.append(buf)
                if len(piece) > max_size:
                    # 单段仍超长 —— 退一级,用更弱的分隔符继续切
                    chunks.extend(_split(piece, seps[1:]))
                    buf = ""
                else:
                    buf = piece
        if buf:
            chunks.append(buf)
        return chunks

    return _split(text, separators)

下面这张图,把一份文档被结构化切分的完整流程串起来:

这里的认知要点是:递归切分的智慧,在于它永远"优先保全大的语义单元"。它把一整段、一整句尽量留在同一个 chunk 里,只有当一个单元实在太大、装不下时,才不得已退一级、动用更弱的刀。这样切出来的 chunk,边界总是落在语义的"接缝"上,而不是"中间"。但光"切在接缝上"还不够——切出来的块,大小是否合适、相邻块的边界要不要缝合,还得继续打磨。

三、chunk 大小与重叠:不太大,不太小,边界要缝合

chunk 切多大,是个需要权衡的取舍:切太大,一个 chunk 里塞了太多内容,真正相关的那句话被一堆无关文字稀释,向量检索时相关性就模糊了,而且喂给模型时浪费上下文、引入噪声;切太小,一个 chunk 只有一两句话,缺少必要的上下文,模型看不懂它在说什么。经验上,一个 chunk 落在两三百个 token 上下,是个比较稳的区间。另一个关键设计是重叠:让相邻的两个 chunk,共享一小段重叠的内容,这样即便某句话不幸被切在了边界上,它在前后两个 chunk 里也都能留下完整的一份:

def split_with_overlap(text, size=500, overlap=80):
    """滑动窗口切分:相邻 chunk 重叠 overlap 个字符,缝合被切断的边界。"""
    chunks = []
    start = 0
    while start < len(text):
        end = start + size
        chunks.append(text[start:end])
        if end >= len(text):
            break
        # 关键:下一块的起点往回退 overlap,和上一块的尾部重叠
        start = end - overlap
    return chunks
    # 重叠让被切断的句子,在相邻两块里都能各留下完整的一份

还有一个容易被忽略的细节:切分应该按 token 数算,而不是按字符数算。因为大模型的上下文、向量模型的输入,计量单位都是 token;而一个中文字、一个英文单词,占的 token 数并不一样。按字符切,你算不准一个 chunk 到底占多少 token:

import tiktoken

_enc = tiktoken.get_encoding("cl100k_base")


def chunk_by_tokens(text, max_tokens=300, overlap_tokens=40):
    """按 token 数而非字符数切分 —— 因为模型和向量库都按 token 计量。"""
    tokens = _enc.encode(text)
    chunks = []
    start = 0
    while start < len(tokens):
        window = tokens[start:start + max_tokens]
        chunks.append(_enc.decode(window))
        if start + max_tokens >= len(tokens):
            break
        start += max_tokens - overlap_tokens   # 往回退,留出重叠
    return chunks

这里的认知要点是:chunk 的大小不是越大越好,也不是越小越好,而是要在"上下文足够"和"噪声足够少"之间找平衡。重叠则是给切分这件有损的事买的一份保险——它用一点点存储冗余,换来"边界上的句子不会丢"。而无论切多大,都该用 token 计量,因为那才是模型和向量库真正"看见"的单位。切好了、大小也合适了,但每个 chunk 此刻还是一段"无名无姓"的文本——它得有身份。

四、给每个 chunk 带上元数据:让它能溯源

开头第四个问题——"chunk 不知道自己来自哪"——根子是我只存了 chunk 的正文,丢掉了它的身份信息。一个合格的 chunk,不该只是一段文本,它还应该背着一身元数据:它来自哪份文档、文档的标题是什么、它在文档里属于哪一章哪一节、文档什么时候更新的。这些元数据有两个大用处:一是溯源(回答用户时能附上"出处:某文档 第几章"),二是过滤(检索时可以限定"只在某个部门的文档里找"、"排除掉一年前的旧文档"):

from dataclasses import dataclass, field


@dataclass
class Chunk:
    """一个 chunk 不只是文本,还要背上一身能溯源、能过滤的元数据。"""
    text: str
    doc_id: str                            # 来自哪份文档
    doc_title: str                         # 文档标题
    heading_path: list = field(default_factory=list)  # 章节标题路径
    chunk_index: int = 0                   # 在文档里的第几块
    updated_at: str = ""                   # 文档的更新时间


def build_heading_path(blocks):
    """遍历文档的标题块,为每段正文算出它所属的"标题路径"。"""
    path = []          # 形如 ["第2章 安装部署", "2.1 环境准备"]
    for block in blocks:
        if block["type"] == "heading":
            level = block["level"]
            path = path[:level - 1]        # 回退到上一级标题
            path.append(block["text"])
        else:
            block["heading_path"] = list(path)
    return blocks

这里的 heading_path 尤其有用。它记录了一个 chunk "沿着标题一路走下来的位置",比如 ["第2章 安装部署", "2.1 环境准备"]。这个路径,本身就是一段高质量的上下文——把它拼在 chunk 正文前面一起向量化,能让一段原本"不知所云"的正文,瞬间有了归属。这里的认知要点是:一个没有元数据的 chunk,是一段"失忆"的文本——它能被检索到,却说不清自己是谁、从哪来。给它带上来源和标题路径,它才从一段孤立的文字,变成一条可溯源、可过滤、可信任的知识。常规正文的分块讲完了,但文档里还有表格、代码这些"硬骨头"。

五、特殊内容:表格与代码块怎么切

开头第三个问题——"表格被切成乱码"——暴露了一个事实:文档里有些内容,是绝对不能用通用规则去切的。最典型的就是表格代码块表格的要害在于:表头和数据行是一体的,一旦把表头切走、只留数据,这些数据就失去了含义。正确的做法是:在切分之前,先把表格整体识别出来、抠出来,作为一个不可分割的 chunk:

import re


def protect_tables(text):
    """把 Markdown 表格整体抠出来,绝不让它在中间被切断。"""
    table_pattern = re.compile(r"(^\|.+\|\s*$\n?)+", re.MULTILINE)
    segments = []
    last = 0
    for m in table_pattern.finditer(text):
        if m.start() > last:
            segments.append({"type": "text", "content": text[last:m.start()]})
        # 整张表作为一个不可分割的 chunk
        segments.append({"type": "table", "content": m.group()})
        last = m.end()
    if last < len(text):
        segments.append({"type": "text", "content": text[last:]})
    return segments

代码块也是一样:一段代码从中间切断,就成了无法运行、无法理解的残片。Markdown 里的代码块有明确的围栏标记,可以据此把整段代码保护起来:

def protect_code_blocks(text):
    """以三反引号围栏为界,把代码块整体保留,不在代码中间下刀。"""
    segments = []
    in_code = False
    buf = []
    for line in text.split("\n"):
        if line.strip().startswith("```"):
            buf.append(line)
            if in_code:
                # 围栏闭合,整段代码作为一个独立 chunk
                segments.append({"type": "code", "content": "\n".join(buf)})
                buf = []
            in_code = not in_code
        else:
            buf.append(line)
    if buf:
        segments.append({"type": "text", "content": "\n".join(buf)})
    return segments

这里的认知要点是:分块不是"一套规则切遍所有内容"。文档里存在一些"原子单元"——表格、代码块、有时还有一个完整的有序列表——它们的内部结构是一体的,切断即损毁。正确的顺序是:先把这些原子单元整体识别、隔离出来,再对剩下的普通正文施加通用的切分规则。分块的主体讲完了,最后是几个真正上规模后才会撞见的工程坑。

六、工程坑:父子分块、检索粒度与 chunk 更新

五块设计之外,还有几个工程坑,不处理就会让 RAG 的检索要么不准、要么过时坑 1:检索要的"小",和喂给模型要的"大",是矛盾的——用父子分块化解。chunk 切小,向量检索更精准(一个小块主题单一,相似度算得准);可 chunk 切小,喂给模型时上下文又不够。这两个需求是打架的。父子分块(small-to-big)是经典解法:用切得很小的"子块"去做向量检索,一旦命中,却把它所属的、更大的"父块"喂给模型——检索的精准和上下文的完整,两头都占:

def build_parent_child(doc_text, doc_id):
    """父子分块:用小块拿去检索,命中后却把它所在的大块喂给模型。"""
    parents = recursive_split(doc_text, max_size=1500)   # 大块:上下文足
    index = []
    for p_idx, parent in enumerate(parents):
        parent_id = f"{doc_id}-p{p_idx}"
        # 每个大块,再切成更小的检索块
        for child in split_with_overlap(parent, size=300, overlap=50):
            index.append({
                "child_text": child,         # 拿它做向量检索:小而精准
                "parent_id": parent_id,
                "parent_text": parent,       # 命中后真正喂给模型:大而完整
            })
    return index

坑 2:chunk 入库时,元数据要能用于过滤。第四节给 chunk 备好的元数据,只有真正写进向量库、并被检索时用上,才有意义。入库时要把元数据一并存好:

def index_chunks(collection, chunks):
    """把带元数据的 chunk 写进向量库 —— 元数据要能用于检索时过滤。"""
    collection.add(
        documents=[c.text for c in chunks],
        metadatas=[{
            "doc_id": c.doc_id,
            "doc_title": c.doc_title,
            "heading_path": " > ".join(c.heading_path),
            "updated_at": c.updated_at,
        } for c in chunks],
        ids=[f"{c.doc_id}-{c.chunk_index}" for c in chunks],
    )

坑 3:文档会更新,chunk 不能只增不改。知识库里的文档会被修订。如果文档改了,你只往向量库里追加新 chunk、却不删旧的,那么同一份文档的新旧两版 chunk 会同时存在,检索时新旧混杂、甚至检出已经作废的旧内容。正确做法是:文档更新时,按内容 hash 判断它到底变没变;变了,就先把这份文档的所有旧 chunk 删干净,再重新切分入库:

import hashlib


def sync_document(collection, doc_id, new_text, splitter):
    """文档更新时:内容没变就跳过,变了就先删旧 chunk 再重建。"""
    new_hash = hashlib.md5(new_text.encode("utf-8")).hexdigest()
    old = collection.get(where={"doc_id": doc_id}, limit=1)
    if old["metadatas"]:
        old_hash = old["metadatas"][0].get("content_hash")
        if old_hash == new_hash:
            return "unchanged"               # 内容没变,不必重建
    # 内容变了:先把这份文档的所有旧 chunk 删干净,再重新切分入库
    collection.delete(where={"doc_id": doc_id})
    chunks = splitter(new_text)
    # ... 给每个 chunk 带上 content_hash,再调 index_chunks 入库
    return "reindexed"

坑 4:不同文档类型,该用不同的分块策略。一篇散文式的说明文档,和一份 API 接口手册、一份 FAQ 问答集,结构完全不同。FAQ 最自然的 chunk 单元是"一问一答",API 手册是"一个接口"。别指望一套分块规则打天下,要按文档类型,挑配套的切法。坑 5:切完要抽样检查 chunk 质量。分块策略写完,别直接信它。随机抽几十个 chunk 出来,人眼看一看:有没有半句话?有没有被切断的表格?有没有空洞无意义的碎片?chunk 的质量,是 RAG 效果的地基,这个地基值得你花时间亲眼验过。

关键概念速查

概念 / 手段 说明
固定长度切分 按字符数机械下刀,会切断句子、步骤、表格
递归分块 按分隔符语义强度从强到弱,逐级切到合适大小
chunk 重叠 相邻块共享一段重叠内容,缝合被切断的边界
按 token 切分 用 token 而非字符计量,贴合模型与向量库
标题路径 记录 chunk 所属章节层级,可拼进正文增强上下文
chunk 元数据 来源文档、更新时间等,支撑溯源与检索过滤
表格保护 整张表作为不可分割单元,表头与数据不分家
代码块保护 以围栏为界整体保留,不在代码中间下刀
父子分块 小块用于精准检索,命中后喂大块保证上下文
chunk 同步 文档更新时按内容 hash 判断,变了才删旧重建

避坑清单

  1. 固定长度一刀切会切断句子、步骤和表格,把语义剁成碎片。
  2. 优先按文档结构递归切分,沿标题、段落这些天然边界下刀。
  3. 相邻 chunk 要重叠一段,缝合被切在边界上的句子。
  4. chunk 不是越大越好也不是越小越好,大噪声多小缺上下文。
  5. 按 token 数而非字符数切分,贴合模型和向量库的计量。
  6. 每个 chunk 必须带元数据,否则答案无法溯源、无法过滤。
  7. 表格要整体保留,表头和数据行被切散就读不懂了。
  8. 代码块要以围栏为界整体保护,别在代码中间下刀。
  9. 父子分块:小块用于精准检索,大块用于喂给模型。
  10. 文档更新要按内容 hash 判断,先删旧 chunk 再重建。

总结

回头看那串"半句话、半个步骤、半张表格、无名无姓的 chunk"的问题,以及我后来在分块上接连踩的坑,最该记住的不是某一个切分参数,而是我动手前那个想当然的判断——"RAG 的分块,就是把文档按固定长度,机械地切成一段段"。这句话错在它把"文档"看成了一根可以任意位置剪断的均匀绳子。我以为切分只是个无足轻重的预处理,切得整齐就行。可我忽略了:文档是有骨架的——标题是它的关节,段落是它的肌理,表格和代码是它的器官;一把不看结构的刀,切的不是绳子,是在剁一个有血有肉的东西固定长度一刀切,切出来的不是知识的片段,而是知识的碎尸——每一块都似是而非,拼不回完整的意思。

所以做 RAG 的文档分块,真正的工程量不在"写一个按长度切的循环"那几行代码上。那几行,谁都会写。真正的工程量,在于你要承认"文档是有结构的、有不可切断的语义单元",并让你的刀学会看着结构下手:它有标题层级,你就递归地、沿着从强到弱的边界切;它的句子会被切在边界上,你就让相邻块重叠一段去缝合;它的每一块需要身份,你就给它带上来源和标题路径;它有表格和代码这些原子单元,你就先把它们整体隔离、再切别的;检索要小、喂给模型要大,你就用父子分块两头都占;文档还会更新,你就按 hash 判断、先删旧再重建。这篇文章的几节,其实就是顺着这条线展开的:先想清楚"一刀切"为什么错,再讲怎么按结构递归切、大小和重叠怎么定、元数据怎么带、表格代码怎么特殊处理,最后是父子分块、检索粒度、chunk 更新这几个把分块做扎实的工程细节。

你会发现,给 RAG 切文档,和现实里"把一本厚书,拆成一张张便于查阅的卡片"完全相通。一个粗心的人会怎么做?他拿把尺子,量着每 10 厘米就剪一刀(这就是固定长度一刀切)。结果呢?一句话被剪成两半、一张插图被拦腰剪断、连目录都被剪得七零八落(这就是切碎的句子和表格),而且每张卡片上都没写它是从哪本书、哪一页来的,过后谁也说不清这张卡片在讲什么(这就是丢了元数据)。而一个细心的人怎么做?他会先翻一遍书,看清它的章节结构(这就是理解文档结构),然后沿着每一节、每一段的天然接缝去拆(这就是按结构递归切分);遇到跨页的表格和插图,他会把它们完整地单独取出来(这就是表格代码的特殊保护);每张卡片的角上,他都工工整整写好"出自第几章第几节"(这就是标题路径元数据)。两个人都把书拆成了卡片,可前者拆出的是一堆废纸,后者拆出的是一套真正能用来查阅的知识卡片

最后想说,RAG 的分块做没做对,差距永远不会在"本地拿几篇规整文档一测就准"时暴露——本地你挑的那几篇文档段落短、没表格、没长列表,固定长度的刀恰好没切到要害,你会觉得"按长度切一切"已经是全部。它只在真实的、文档格式五花八门、有大量表格代码和跨段步骤、还会不断被修订的知识库里才显形。那时候它会用最伤回答质量的方式给你结账:做不好,你的问答系统会把半句话、半张表喂给模型,模型只能连蒙带猜地答出残缺甚至错误的内容,用户还无从知道答案的出处、更无从分辨它是不是过时的;而做了,你的检索会稳稳地召回一个个语义完整的 chunk:每一块该长则长、该短则短,表格和代码完好无损,每条答案都能清清楚楚地标明"出自某文档某章节"。所以别等"用户抱怨答案残缺又对不上"找上门,在你写下那行按长度切的循环时就该想清楚:我面对的不是一根均匀的字符流,而是一份有骨架、有器官、会更新的结构化文档——它的句子、它的步骤、它的表格,这一个个语义单元,我的刀是不是都替它们让开了?这些问题有了答案,你切出来的才不只是一堆"长度整齐"的文本块,而是一套真正完整、可溯源、撑得起高质量问答的知识地基

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

数据库读写分离完全指南:从一次"用户下单后看不到自己的订单、主库宕机丢了几千条数据"看懂主从复制

2026-5-22 1:25:01

技术教程

数据库死锁完全指南:从一次"大促下单成片失败、库存还被扣两次"看懂死锁的根治

2026-5-22 1:39:39

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索