RAG 实战:从一篇掘金文章出发,拆解检索增强生成的全链路

RAG 实战:从一篇掘金文章出发,拆解检索增强生成的全链路

你有没有遇到过这样的场景:让 AI 总结一篇长文章的核心观点,它要么回答得似是而非,要么干脆说"这篇文章超出了我的知识范围"。这不是模型不够聪明,而是它没有见过这篇文章。

大语言模型的训练数据有截止日期,且无法覆盖互联网上的每一篇文章。当你需要 AI 理解一份它从未见过的文档时,你有两个选择:

  • 微调(Fine-tuning):把文章喂给模型重新训练,耗时、烧钱,且每次新增文档都要重来一遍
  • RAG(Retrieval-Augmented Generation,检索增强生成):先把文章存进知识库,提问时检索相关内容,塞进提示词让 AI 现场阅读并回答

显然,RAG 是更务实的选择。下面我们通过一段不到 90 行的真实代码,一步步拆解 RAG 的完整链路。

RAG 六步流水线

整个 RAG 流程可以抽象为六个步骤:

复制代码
加载文档 → 文本切分 → 向量嵌入 → 存入向量库 → 相似检索 → 增强生成

每一步都有其不可替代的作用,缺任何一环,整个链路都会断裂。下面逐一展开。

第一步:文档加载------把外部知识"搬"进来

javascript 复制代码
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";

const cheerioLoader = new CheerioWebBaseLoader(
    'https://juejin.cn/post/7233327509919547452',
    { selector: '.main-area p' }
);

const documents = await cheerioLoader.load();

这里做了两件事:抓取网页提取正文

CheerioWebBaseLoader 底层使用了 Cheerio------一个在 Node.js 端运行的类 jQuery 库,让你可以用 CSS 选择器像操作前端 DOM 一样解析 HTML。selector: '.main-area p' 精确锁定了文章正文区域的所有段落标签,滤掉了导航栏、侧边栏、评论区等噪音。

这是 RAG 的第一道关卡:垃圾进,垃圾出。如果加载的内容混杂了大量无关信息,后续的检索质量无从谈起。

LangChain 的 document_loaders 模块提供了数十种加载器:PDF、Markdown、Notion、GitHub、Confluence......不管知识存在哪里,都能"搬"进管道。

第二步:文本切分------把长文"切"成可检索的块

这是整个 RAG 流程中最容易被低估、却最能影响最终效果的环节。

javascript 复制代码
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 400,          // 每个文本块的最大字符数
    chunkOverlap: 50,        // 相邻文本块之间的重叠字符数
    separators: ['。', ',', '!', '?'], // 语义分割符
});

const splitDocuments = await textSplitter.splitDocuments(documents);

这里涉及三个关键参数,每个都有它的"为什么":

chunkSize = 400

文本块不能太大,也不能太小。太大 ,检索精度下降,且容易超出 LLM 的上下文窗口;太小,语义碎片化,一段完整论述被拦腰截断。400 个字符约等于中文的 200 字,刚好容纳一个完整的观点段落。

在实际项目中,chunkSize 的调整往往是 RAG 性能调优的第一步:客服问答场景可能偏短(200-300),论文分析场景可能偏长(800-1000)。

chunkOverlap = 50

这是 RecursiveCharacterTextSplitter 最精妙的设计------相邻块之间不是硬截断,而是有 50 个字符的"重叠带"。

为什么要重叠?想象一句话正好横跨 Chunk 1 和 Chunk 2 的边界:"......父亲的去世让我意识到//(此处是分块边界)人生短暂,应该及时行乐。"如果不重叠,这句完整的因果逻辑就被一分为二,检索时无论命中哪个 Chunk,AI 都看不到完整语境。50 个字符的重叠如同"安全冗余",保证边界处的语义不被割裂。

separators: ['。', ',', '!', '?']

这些中文标点定义了切割的优先级。RecursiveCharacterTextSplitter 的"递归"体现在:它先尝试用句号切,切出来的块如果还超过 400 字,再用逗号切,以此类推。这样保证切割总是发生在最自然的语义边界上,而不是生硬地数到第 400 个字符就一刀下去。

为什么叫"Recursive"?

这是常见的面试考点。它先按最高优先级分隔符(句号)切分 → 检查每个块是否超过 chunkSize → 对超标的块,降级到下一个分隔符(逗号)继续切 → 递归直到所有块都合规。这种"分层降级"的策略,比简单按固定长度截断更尊重文档的语义结构。

第三步:向量嵌入------把文字翻译成机器"认识"的数字

javascript 复制代码
import { OpenAIEmbeddings } from "@langchain/openai";

const embeddings = new OpenAIEmbeddings({
    apiKey: process.env.OPENAI_API_KEY,
    model: process.env.EMBEDDING_MODEL_NAME,
    configuration: { baseURL: process.env.OPENAI_BASE_URL },
});

Embedding 模型的工作是:把一段文本映射成一个高维向量(如一串 1536 个浮点数)。语义相近的两段话,它们在向量空间中的距离就相近;语义无关的,距离就远。

这个过程很像给每段文字拍了一张"语义指纹"。你不需要精确匹配关键词,哪怕换了一套说法,只要意思相同,向量距离依然很近------这就是 RAG 比传统关键词搜索高明的地方。

值得注意的是,这里的 Embedding 模型和大语言模型使用了同一个兼容端点(阿里云 DashScope),但它们是两个不同的模型。Embedding 模型专做向量化,轻量、快速、便宜;Chat 模型专做对话推理。两者各司其职,不可混用------这也是面试中的高频考点。

第四步:向量存储------给"语义指纹"建一个可查询的索引

javascript 复制代码
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";

const vectorStore = await MemoryVectorStore.fromDocuments(splitDocuments, embeddings);

MemoryVectorStore.fromDocuments() 一次性完成了两件事:

  1. 调用 embeddings 模型,将每个文本块转为向量
  2. 将所有向量存入内存向量数据库

MemoryVectorStore 顾名思义,数据存在内存中,程序关闭即消失。它适合 Demo 和学习场景。生产环境通常会换成 Pinecone(云原生)、Chroma(本地持久化)、Milvus(分布式高性能)等持久化向量数据库。

第五步:相似检索------找到最相关的"知识碎片"

javascript 复制代码
const retriever = await vectorStore.asRetriever({ k: 2 });
const retrievedDocs = await retriever.invoke(question);
const scoreResults = await vectorStore.similaritySearchWithScore(question, 2);

这短短的几行代码,背后发生了什么呢?

  1. 问题向量化 :将用户问题 "父亲的去世对作者的人生态度产生了怎样的根本性逆转?" 传入同一个 Embedding 模型,得到问题向量
  2. 相似度计算:在向量库中计算问题向量与所有文档向量的距离(通常用余弦相似度或欧氏距离)
  3. Top-K 选取 :返回距离最近的 k=2 个文本块

代码还通过 similaritySearchWithScore 拿到了每个检索结果的相似度评分:

javascript 复制代码
retrievedDocs.forEach((doc, i) => {
    const scoreResult = scoreResults.find(
        ([scoredDoc]) => scoredDoc.pageContent === doc.pageContent
    );
    const score = scoreResult ? scoreResult[1] : null;
    const similarity = score ? (1 - score).toFixed(2) : "N/A";
    console.log(`\n文档 ${i+1} 相似度:${similarity}`);
});

这里的 score 是向量距离(越小越相似),1 - score 转换为相似度百分比(越高越相关)。这个评分在生产环境中是重要的诊断工具------如果 Top-1 文档的相似度只有 0.3,说明知识库里可能根本没有相关内容,此时强行生成反而会产生幻觉,应该触发"知识库未覆盖"的兜底逻辑。

第六步:增强生成------让 AI "开卷考试"

javascript 复制代码
const content = retrievedDocs
    .map((doc, i) => `[片段${i+1}]\n ${doc.pageContent}`)
    .join("\n\n----\n\n");

const prompt = `你是一个文章辅助阅读助手,根据文章内容来解答:
文章内容:
${content}

问题:
${question}
回答:`;

const response = await model.invoke(prompt);

这就是 RAG 的"R"(Retrieval)与"G"(Generation)的交接点------把检索到的文档片段拼接成"开卷材料",连同用户问题一起填入提示词模板,交给 LLM 现场作答。

这个提示词模板本身就是一种提示工程

  • 你是一个文章辅助阅读助手:角色设定,锚定 AI 的行为边界------只基于文章回答,不编造
  • ${content}${question} 用明确的标签分隔:结构化输入,减少模型混淆
  • 回答: 结尾:引导模型直接进入回答模式,减少废话

完整链路回顾

把这六步串起来,一次完整的 RAG 查询是这样的:

css 复制代码
用户提问
    │
    ▼
问题向量化 ──────────→ 在向量库中检索 Top-K 相似文档
    │                          │
    │                          ▼
    │                   返回 K 个最相关文本块
    │                          │
    ▼                          ▼
             拼装提示词模板
                 │
                 ▼
           LLM 阅读 + 回答
                 │
                 ▼
             返回最终答案

整段代码不到 90 行,却完整实现了从网页抓取到智能回答的全链路。这就是 LangChain 这类框架的价值------它把每个环节抽象为标准组件,你可以像搭积木一样组合它们,快速验证想法。


深入切割机制:三大 Splitter 横向对比

前面的示例使用了 RecursiveCharacterTextSplitter,但 LangChain 的切割器家族不止这一位成员。CharacterTextSplitterTokenTextSplitterRecursiveCharacterTextSplitter 三者各有所长,选错切割器对 RAG 效果的影响不亚于选错 Embedding 模型。下面结合真实代码逐一拆解。

前置知识:Token 不等同于字符

在深入三种切割器之前,必须先理解一个基础概念------Token 不是字符。大语言模型不是逐字阅读文本,而是将文本转化为 Token(词元)序列后再处理。同一个意思,用不同语言表达,Token 数量可以天差地别:

javascript 复制代码
import { getEncodingNameForModel, getEncoding } from 'js-tiktoken';

const enc = getEncoding('cl100k_base');

console.log('apple', enc.encode('apple').length);      // → 1 个 Token
console.log('pineapple', enc.encode('pineapple').length); // → 2 个 Token
console.log('苹果', enc.encode('苹果').length);          // → 2 个 Token

apple 是一个高频英文单词,编码器给它分配了独立的 Token ID,1 个 Token 就搞定。pineapple 虽然也是单词,但频率没那么高,被拆成了 2 个 Token。苹果 两个汉字,每个汉字各占 1 个 Token。

这个差异的意义在于:LLM 的上下文窗口是按 Token 计算的 (如 GPT-4 的 128K 窗口指的是 128K Token),而你写代码时看到的是字符数。一个中文字符约等于 1.5-2 个 Token,一段 1000 字的英文可能只有 700 Token------用字符数估算 Token 数,误差可以高达 50%。三种切割器的本质差异,正是源于"按什么单位来计数"。

Tiktoken 是 OpenAI 开源的 Token 计数库,cl100k_base 是 GPT-4 / GPT-3.5-turbo 使用的编码表。js-tiktoken 是其 JavaScript 移植版。它提供的能力很简单:给定一段文本,告诉你这段文本会被模型当成多少个 Token。但这个简单的能力,是整个切割器体系的基石。


一、CharacterTextSplitter:简单粗暴的字符计数器

CharacterTextSplitter 是最基础的切割器------它只做一件事:按字符数切。用一段日志文件来演示:

javascript 复制代码
import { CharacterTextSplitter } from '@langchain/textsplitters';
import { Document } from '@langchain/core/documents';

const logDocument = new Document({
    pageContent: `[2024-01-15 10:00:00] INFO: Application started
[2024-01-15 10:00:05] DEBUG: Loading configuration file
[2024-01-15 10:00:10] INFO: Database connection established
...`
});

const splitter = new CharacterTextSplitter({
    chunkSize: 50,       // 每个块最多 50 个字符
    chunkOverlap: 10,    // 块之间重叠 10 个字符
    separator: '\n',     // 单一分隔符
});

它的切割逻辑非常直白:

arduino 复制代码
if (当前累计字符数 + 下一段字符数 > chunkSize) {
    切割,另起一个新块
} else {
    继续往当前块里追加
}

优点:快,非常快。没有复杂的递归逻辑,不需要加载外部编码表,适合对速度要求极高的流式场景。

缺点 :完全无视语义。如果 chunkSize: 50 正好卡在一句话中间,它会毫不犹豫地切断。对于中文来说伤害稍小------每个汉字本身是一个有意义的单元,切割点即使不理想,信息损失也有限。但对于英文,切断一个单词中间是灾难性的。

适用场景:中文文本、日志文件、代码等字符与语义单元基本一一对应的内容,或者对切割精度要求不高但追求速度的原型验证。


二、TokenTextSplitter:最精准的"窗口对齐器"

TokenTextSplitter 是三种切割器里唯一直接对标 LLM 上下文窗口的。它不问"这段文本有多少字符",只问"这段文本有多少 Token":

javascript 复制代码
import { TokenTextSplitter } from '@langchain/textsplitters';

const logTextSplitter = new TokenTextSplitter({
    chunkSize: 50,               // 每个块最多 50 个 Token(不是字符!)
    chunkOverlap: 10,            // 块之间重叠 10 个 Token
    encodingName: 'cl100k_base', // 使用 GPT-4 的编码表
});

const splitDocuments = await logTextSplitter.splitDocuments([logDocument]);

const enc = getEncoding('cl100k_base');
splitDocuments.forEach(doc => {
    console.log('char length:', doc.pageContent.length);      // 字符长度
    console.log('token length:', enc.encode(doc.pageContent).length); // Token 长度 ≤ 50
});

注释中的 chunkSize: 50 指的是 50 个 Token,不是 50 个字符。这是最容易混淆的地方。同一个块,字符长度可能是 80,但 Token 长度严格不超过 50。

它的切割流程是:

复制代码
加载 tiktoken 编码表 → 将文本 Token 化 → 按 Token 累计计数 → 达到 chunkSize 时切割

这是唯一一个切割后可以直接拿去计算"还剩多少上下文空间"的切割器。 当你需要向 GPT-4 的 128K 窗口中塞入尽可能多的检索结果时,TokenTextSplitter 能精确告诉你每个 Chunk 消耗了多少 Token 配额,不会出现"我以为只用了 3000 Token,实际用了 5000"的估算偏差。

优点:Token 级别的精确控制,与 LLM 窗口天然对齐,跨语言一致性好(中英文在 Token 维度上的表现是统一的)。

缺点 :依赖外部编码表(js-tiktoken),初始化有开销;切割发生在 Token 边界而非字符边界,可能导致文本在奇怪的位置断开;不同模型的编码表不同(GPT-4 用 cl100k_base,GPT-2 用 gpt2),需要根据实际使用的模型配置。

适用场景:需要严格控制 Token 预算的生产环境、多语言混合文档、与 OpenAI 模型对接的 RAG 管道。


三、RecursiveCharacterTextSplitter:最"聪明"的语义感知器

RecursiveCharacterTextSplitter 是三种切割器里策略最复杂的。它用字符数作为上限,但在切割时有一套"分层降级"的分隔符策略:

javascript 复制代码
const logSplitter = new RecursiveCharacterTextSplitter({
    separators: ["\n", "。", ","],  // 分隔符优先级:先换行,再句号,再逗号
    chunkSize: 200,                    // 字符上限
    chunkOverlap: 20,                  // 重叠字符数
});

const logChunks = await logSplitter.splitDocuments([logDocument]);

const enc = getEncoding('cl100k_base');
logChunks.forEach(doc => {
    console.log('character length:', doc.pageContent.length);
    console.log('token length:', enc.encode(doc.pageContent).length);
});

输出会清楚地暴露字符与 Token 之间的"汇率"------同一段中文长文本,character length 可能是 180,但 token length 可能是 280。这也是为什么在生产环境中,很多人会在 RecursiveCharacterTextSplitter 验证通过后,再加一层 Token 数校验。

它的核心算法可以概括为:

perl 复制代码
function split(text, separators, chunkSize):
    // 1. 取当前优先级最高的分隔符
    separator = separators[0]

    // 2. 用该分隔符切分文本
    splits = text.split(separator)

    // 3. 合并切分结果,尽量接近但不超出 chunkSize
    for each split in splits:
        if (currentChunk.length + split.length > chunkSize):
            if (currentChunk.length < chunkSize):
                保存 currentChunk,另起新块
            else:
                // 当前块仍然超标 → 递归降级到下一个分隔符
                subSplits = split(split, separators[1:], chunkSize)
                保存 subSplits

    // 4. 根据 chunkOverlap 为相邻块添加重叠内容
    applyOverlap(allChunks, chunkOverlap)

关键在第三步的 else 分支:当使用最高优先级分隔符切出来的片段仍然超出 chunkSize 时,不会暴力截断,而是递归降级到下一个分隔符重新尝试。比如:

swift 复制代码
separators: ["\n", "。", ","]

一个超长段落
  → 先试 \n 切:只切成 1 段(因为段落内没有换行),仍超标
    → 降级到 。 切:切成 3 句,其中第 2 句还超标
      → 降级到 ,切:切成若干短句,全部合规

这种策略保证切割总是优先发生在最高语义层级------段落边界(换行)优于句子边界(句号),句子边界优于从句边界(逗号)。同样是切成 200 字符的块,Recursive 切出来的块比 CharacterTextSplitter 更"完整",比 TokenTextSplitter 更"语义连贯"。

但它有一个固有矛盾:用字符数限制,却试图在语义边界切割 。当一段话在最近的一个句号处已经 190 字符,再加一句就变成 250 字符时,切割器面临选择------是在句号处切(190 字符,语义完整但浪费了 10 字符配额),还是继续凑到接近 200 字符再切(可能切断语义)?RecursiveCharacterTextSplitter 选择了前者------优先保证语义完整性,即使单个 chunk 略小于 chunkSize。


三者对比一览

维度 CharacterTextSplitter TokenTextSplitter RecursiveCharacterTextSplitter
计量单位 字符数 Token 数 字符数
切割策略 累计字符数到阈值即切 累计 Token 数到阈值即切 分层递归 + 分隔符优先级降级
语义感知 无(但 Token 天然有一定语义) 强------优先在段落/句子/从句边界切割
精确度 低(Token 估算偏差大) 高(直接对标 LLM 窗口) 中(字符数估算,但有语义补偿)
依赖 js-tiktoken + 编码表
速度 最快 慢(需 Token 化) 中等
中文友好度 较好 较好 最好
适用场景 日志、代码、原型 生产环境、Token 预算敏感 文章、文档、对话等自然语言

如何选择?

没有万能切割器,只有合适的选择:

  • 不确定用什么?从 RecursiveCharacterTextSplitter 开始。 它的语义感知能力在大多数自然语言场景下表现最好,是 LangChain 文档的默认推荐。
  • 对接 OpenAI 模型且关注 Token 预算?用 TokenTextSplitter 当每个请求的 Token 消耗直接等于成本时,精确计数不是可选项,是必选项。
  • 处理结构化日志或代码?用 CharacterTextSplitter 这类内容的行本身就是天然的分割单元,不需要复杂的递归策略。
  • 进阶玩法:组合使用。 先用 RecursiveCharacterTextSplitter 做语义切割,再对每个 Chunk 用 TokenTextSplitter 做二次校准------确保既不破坏语义,又不超出 Token 预算。这在高要求的生产 RAG 管道中是最常见的模式。

面试考点总结

1. RAG 的核心流程是什么?每一步的作用分别是什么?

Loading → Splitting → Embedding → Storing → Retrieval → Generation。加载负责获取外部知识,切分保证检索粒度合理,嵌入将文本转为可计算的向量,存储建立索引,检索找出相关内容,生成基于检索结果回答问题。漏掉任何一步都走不通。

2. chunkSize 和 chunkOverlap 分别如何影响 RAG 效果?

chunkSize 决定了检索粒度 :太大则检索不精确且可能超出 LLM 上下文窗口,太小则语义碎片化。chunkOverlap 防止边界语义断裂:保留相邻块之间的重叠内容,确保跨块的关键信息不被截断。两者需要根据文档类型和业务场景联合调优。

3. RecursiveCharacterTextSplitter 的"递归"是什么意思?

它按分隔符优先级递归切分:先用高优先级分隔符(如句号)切,对超出 chunkSize 的块降级到下一个分隔符(如逗号)继续切,直到所有块都符合大小限制。这种方式优先在自然语义边界上切割,而非暴力截断。

4. Embedding 模型和 Chat 模型有什么区别?能否互换?

不能互换。Embedding 模型将文本映射为固定维度的向量,输出是数字数组,用于语义相似度计算;Chat 模型接收文本序列、输出文本,用于对话和推理。它们是两种不同的模型架构,职责完全不同。RAG 中两个模型各司其职:Embedding 负责"找",Chat 负责"答"。

5. 向量检索的相似度评分是怎么算出来的?

将用户问题向量化后,在向量空间中计算问题向量与所有文档向量的距离(常用余弦相似度、欧氏距离或内积),距离越小表示语义越接近。引擎返回距离最近的 K 个文档及其分数。1 - score 可近似转换为相似度百分比。如果最高分仍然很低,说明知识库可能没有覆盖该问题------此时应触发兜底策略而非强行回答。

6. MemoryVectorStore 和持久化向量数据库的区别是什么?

MemoryVectorStore 将向量和文档存在内存中,进程结束即丢失,适合原型验证。生产环境使用 Pinecone、Chroma、Milvus 等持久化方案,支持数据落盘、分布式检索、增量更新和权限控制。面试中能说出至少两种生产级向量数据库是加分项。

7. RAG 和微调(Fine-tuning)的适用场景分别是什么?

RAG 适合 :知识频繁更新、需要可解释性(能溯源到具体文档)、外部知识注入、低成本快速接入。微调适合:需要模型学习特定风格或行为模式、任务定义稳定、对延迟敏感不能加检索环节。两者并不互斥,成熟的工业方案往往是 RAG + Fine-tuning 的组合。

8. 提示词模板在 RAG 中起什么作用?

提示词模板决定了检索到的文档如何与用户问题结合。它至少承担三个职能:角色设定 (限定 AI 的行为边界,如"只基于给定文章回答")、结构化输入 (在文档和问题之间插入明确的分隔标记)、防幻觉引导(隐含地告诉模型"不要编造,不知道就说不知道")。模板本身也是提示工程的一部分,需要针对不同场景迭代优化。

9. Token 和字符有什么区别?为什么这个区别对 RAG 很重要?

Token 是 LLM 处理文本的最小语义单元,不是字符。 一个英文单词可能是 1 个 Token,一个汉字通常是 1-2 个 Token。LLM 的上下文窗口(如 GPT-4 的 128K)是按 Token 计算的,而不是字符。如果按字符数估算 Token 消耗,误差可能高达 50%。在 RAG 中,这意味着你以为塞了 10 个 Chunk 给模型,实际可能已经爆了窗口。js-tiktoken 库的作用就是精确计算任意文本的 Token 数,消除这个估算误差。面试中能说出 cl100k_base(GPT-4 编码表名称)是加分项。

10. CharacterTextSplitter、TokenTextSplitter 和 RecursiveCharacterTextSplitter 三者有什么区别?分别适用什么场景?

这是切割器三兄弟,核心区别在于计量单位切割策略

  • CharacterTextSplitter:按字符数切,简单直接,无外部依赖,速度快。适合日志、代码等结构化文本。缺点是完全无视语义边界。
  • TokenTextSplitter :按 Token 数切,依赖 js-tiktoken 编码表,切割结果直接对标 LLM 的上下文窗口。适合 Token 预算敏感的生产环境。缺点是需要加载编码表、切割点可能在语法上不自然。
  • RecursiveCharacterTextSplitter:按字符数切上限,但用递归分隔符策略优先在语义边界切割(段落 → 句子 → 从句逐级降级)。适合文章、文档等自然语言内容。它用字符数做上限、用语义做约束,是 LangChain 的默认推荐。

三者不是互斥的------生产环境中常见"Recursive 语义切割 + TokenTextSplitter 二次校准"的组合模式。

11. tiktoken 是什么?cl100k_base 又是什么?

tiktoken 是 OpenAI 开源的 Token 计数库(js-tiktoken 是其 JS 移植版),它能精确计算任意文本在特定模型下的 Token 数。cl100k_base 是 GPT-4 和 GPT-3.5-turbo 使用的编码表名称------不同的模型使用不同的编码表(如 GPT-2 用 gpt2),编码表决定了文本如何被切分为 Token。在 RAG 中使用 TokenTextSplitter 时必须指定与目标模型匹配的编码表,否则 Token 计数不准确。

12. 为什么 RecursiveCharacterTextSplitter 用字符数而不用 Token 数做上限?

这是一个设计权衡。如果要精确控制 Token 数,需要在每次切割时实时 Token 化文本------这对长文档来说是巨大的性能开销。用字符数做上限,用递归分隔符做语义补偿,是一种工程上的近似最优解 :速度快、无外部依赖、语义完整性好,代价是 Token 估算不够精确。当 Token 精度不可妥协时(如按 Token 计费的 API),改用 TokenTextSplitter 或对 Recursive 的输出做 Token 二次校验。

相关推荐
码农小旋风3 小时前
Codex小白入门使用教程
人工智能·chatgpt·claude
Lee川3 小时前
MCP 高德地图实战:当 AI 学会使用工具,一个协议如何重塑大模型的行动边界
前端·人工智能·后端
楼田莉子3 小时前
C++17新特性:__had_include/属性/求值顺序规则
开发语言·c++·后端
凌杰3 小时前
AI 学习笔记:Agent 的应用演示
人工智能
ZC跨境爬虫3 小时前
跟着 MDN 学CSS day_14:(尺寸调整技能测试与实战解析)
前端·css·ui·html·tensorflow
kyriewen3 小时前
用魔法打败魔法:我让AI替我去面试前端岗,AI面试官给我打了92分,还发了offer
前端·javascript·面试
程序员cxuan3 小时前
Codex 把我家烂网给优化后,我 TM 直接原地起飞了。
人工智能·后端·程序员
IT_陈寒4 小时前
Redis批量删除踩了坑,原来DEL命令不是万能的
前端·人工智能·后端
xinhuanjieyi4 小时前
gpt-sovits测试语音克隆
人工智能·gpt