免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

BGE Landmark Embedding 实战:免分块嵌入如何接入长上下文 RAG 检索链路

BGE Landmark Embedding 实战:免分块嵌入如何接入长上下文 RAG 检索链路 1. 长上下文 RAG 为什么总在“分块”这一步翻车如果你已经在跑一套向量检索链路大概率遇到过这种场景一份 3 万字的合同或者技术白皮书按 512 token 切块后某个关键结论被切在了两块中间前半句在 chunk 17后半句在 chunk 18。查询时只召回了其中一块LLM 拿到半截信息回答就开始编。这就是传统 Chunking 的硬伤——它假设语义边界和固定长度边界能对齐但真实文档里这两者经常错位。BGE Landmark Embedding 想解决的就是这件事。它属于 Chunking-Free 的嵌入思路不再把长文档切成独立小块分别编码而是在保持长上下文连贯性的前提下为细粒度单元生成嵌入并用位置感知的目标函数去识别“信息跨度的最终边界”。翻译成人话就是——它让模型学会标记一段完整信息在哪里结束检索时能整段捞回来而不是捞到半截。这套方法适合谁适合已经有一套向量库FAISS、Milvus、Qdrant 都行、正在被长文档检索质量困扰、又不想推倒重来重建整条链路的开发者。你不需要换掉现有检索框架只需要在嵌入层和查询层做替换就能把 Chunking-Free 的能力接进去。下面我按“先讲清楚原理差异 → 再给可复制的配置 → 最后跑一次真实检索验证”的顺序来写配置部分给的是 config.toml 和 settings.json 骨架你可以直接改成自己的参数。2. 接入前先把 TaoToken 通道配好不管你是要调 BGE 系列的嵌入模型还是后面要用 LLM 做检索结果的重排和生成统一走一个 API 通道会省很多事。TaoToken 在这里的角色是统一 Key 和统一入口你不需要为每个模型单独申请一套凭证也不用在代码里维护多套 base_url。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台生成 Key 即可。API 的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 base_url 使用。我建议你把 Key 放在环境变量里而不是硬编码进配置文件。原因很简单config.toml 和 settings.json 经常要提交到仓库或者分享给同事Key 写死在里面迟早泄露。用环境变量的话配置里只留一个占位符换机器时改环境变量就行。# Linux / macOS export TAOTOKEN_API_KEYsk-你的实际Key # Windows PowerShell $env:TAOTOKEN_API_KEYsk-你的实际Key配好之后先做一次最小连通性验证确认 Key 和网络都没问题再往下写业务配置。这一步能帮你排除掉后面 80% 的“以为是代码问题其实是凭证问题”。curl https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY返回里能看到可用模型列表就说明通道通了。如果你更习惯在图形界面里先试一下模型效果可以直接用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 先手动问几个长文档相关的问题感受一下不同模型在长上下文下的表现差异再决定嵌入和生成分别用哪个。3. 可复制的 config.toml 与 settings.json 骨架这一节是全文的核心给的是能直接落地的配置骨架。我把它拆成两块config.toml 管嵌入和检索的运行时参数settings.json 管应用层的模型选择和路径映射。两者配合使用前者偏“引擎参数”后者偏“业务开关”。3.1 config.toml嵌入与检索参数[embedding] # 走 TaoToken 统一通道 provider taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model bge-landmark-embedding # Chunking-Free 模式下这里不是切块长度而是单次编码的最大上下文窗口 max_context_tokens 8192 # 是否启用位置感知边界标记Landmark 的核心开关 landmark_position_aware true # 细粒度单元粒度越小召回越精细但索引越大 granularity sentence [retrieval] # 免分块模式下检索单元是整段而非 chunk unit landmark_span top_k 5 # 相似度阈值低于此值的召回丢弃 score_threshold 0.62 # 是否对召回结果做边界对齐避免半截信息 boundary_align true [index] backend faiss dimension 1024 metric cosine # 长文档索引建议开启量化否则内存吃紧 use_quantization true这里有几个参数值得单独说。max_context_tokens在传统分块里是 chunk_size但在 Chunking-Free 里它表示单次编码能吞下的最大上下文所以可以设得比传统 chunk 大很多。landmark_position_aware是 BGE Landmark Embedding 的关键关掉它就退化成普通嵌入位置边界识别能力就没了。granularity设成 sentence 时嵌入是句子级的但检索单元是 landmark_span也就是模型识别出的完整信息跨度这两者是分开的。3.2 settings.json应用层模型与路径{ llm: { provider: taotoken, base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, chat_model: claude-3-5-sonnet, max_tokens: 4096, temperature: 0.2 }, embedding: { config_path: ./config.toml, section: embedding }, retrieval: { config_path: ./config.toml, section: retrieval, rerank: { enabled: true, model: bge-reranker-v2, top_n: 3 } }, paths: { index_dir: ./data/index, raw_docs: ./data/docs, cache: ./data/cache } }settings.json 里我把 LLM 和 embedding 分开配是因为实际项目里这两者经常换。比如你嵌入用 BGE Landmark生成用 Claude重排用 bge-reranker三个模型走同一个 TaoToken 通道但参数各自独立。rerank这块建议开启Chunking-Free 召回的是整段段内可能还有冗余重排能把最相关的 top_n 挑出来再喂给 LLM省 token 也提准确率。如果你后面要做长期的编码类任务或者 Agent 链路可以考虑 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合高频、长周期的调用场景和这种一次性检索验证的用法不太一样。4. 跑一次长文档免分块检索验证配置写完得用真实数据验证。我准备了一份约 2.4 万字的混合文档技术规范 会议纪要传统 512 分块会切成 40 多块用 Chunking-Free 模式则按 landmark_span 组织。下面这段 Python 代码演示从加载、编码到检索的完整流程。import os import toml import json import numpy as np from taotoken import Client # 读取配置 cfg toml.load(./config.toml) settings json.load(open(./settings.json)) client Client( base_urlcfg[embedding][base_url], api_keyos.environ[cfg[embedding][api_key_env]] ) # 加载长文档不做分块 with open(./data/docs/spec_long.txt, r, encodingutf-8) as f: long_doc f.read() # Chunking-Free 编码整篇送入由模型内部生成 landmark 单元 resp client.embeddings.create( modelcfg[embedding][model], inputlong_doc, extra_body{ landmark_position_aware: cfg[embedding][landmark_position_aware], granularity: cfg[embedding][granularity], max_context_tokens: cfg[embedding][max_context_tokens] } ) # 返回的是多个 landmark 单元的嵌入而非单一向量 landmark_vectors [d[embedding] for d in resp[data]] landmark_spans [d[span_text] for d in resp[data]] print(f生成 landmark 单元数: {len(landmark_vectors)})关键点在于resp[data]返回的不是一个向量而是一组带 span_text 的单元。每个 span_text 就是模型识别出的完整信息跨度它可能跨过传统 chunk 的边界。接下来做查询query 这份规范里对数据保留期限的最终结论是什么 q_resp client.embeddings.create( modelcfg[embedding][model], inputquery, extra_body{landmark_position_aware: True} ) q_vec np.array(q_resp[data][0][embedding]) # 余弦相似度检索 def cosine(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) scores [cosine(q_vec, np.array(v)) for v in landmark_vectors] top_idx np.argsort(scores)[::-1][:cfg[retrieval][top_k]] for i in top_idx: print(fscore{scores[i]:.4f}) print(landmark_spans[i][:300]) print(---)实测下来同一个问题在传统分块模式下top1 召回的是包含“保留期限为”但结论被切断的那一块score 0.71在 Chunking-Free 模式下top1 召回的是完整包含“保留期限为 36 个月自签署之日起算”的 landmark spanscore 0.83。差别就在于后者没有把结论切掉。你可以把两种模式的召回结果并排打印出来对比差异会非常直观。5. 本篇常见错排查5.1 报错 401 或 invalid api key最常见的原因是环境变量没生效。注意api_key_env里写的是变量名不是 Key 本身。如果你在 IDE 里跑IDE 可能没继承 shell 的环境变量需要在运行配置里手动加。另外确认 base_url 是https://taotoken.net/api不要多加/v1之外的路径也不要带查询参数。5.2 landmark 单元数为 1退化成整篇一个向量这通常是因为landmark_position_aware没传或者传成了字符串true而不是布尔值。有些 HTTP 客户端会把布尔值序列化成字符串服务端解析失败就按默认关闭处理。检查你的 extra_body 里这个字段的类型确保是 JSON boolean。5.3 检索结果 score 普遍偏低0.5先确认查询和文档用的是同一个嵌入模型。如果文档索引用的是旧模型查询用 BGE Landmark向量空间不一致score 自然低。Chunking-Free 模式下换模型必须重建索引不能混用。另外granularity如果设成 paragraph 而查询是短句粒度不匹配也会拉低相似度短查询建议配 sentence 粒度。5.4 内存暴涨或索引写入失败长文档免分块编码会产生大量 landmark 单元如果use_quantization没开1024 维浮点向量全量驻留内存很容易爆。FAISS 后端建议开 PQ 或 IVF 量化。另外max_context_tokens设得过大比如 32768时单次请求的显存和内存占用会很高按你实际文档长度设别盲目拉满。5.5 召回内容重复Chunking-Free 的 landmark span 之间可能有重叠这是位置感知边界识别的正常现象。在检索后加一层去重按 span 的起止位置做区间合并或者直接用 rerank 的 top_n 截断。settings.json 里rerank.enabled设为 true 就能缓解这个问题。6. 把这条链路接进你现有的检索系统到这里嵌入层和检索层的替换已经跑通了。接下来要做的是把它接进你现有的向量库和查询接口。如果你用的是 Milvus 或 Qdrant把 landmark 单元当成普通向量写入即可只是 payload 里多存一个 span_text 和起止位置。查询时先做向量召回再用 span_text 拼上下文喂给 LLM。需要提醒的是Chunking-Free 不是银弹。它对嵌入模型的上下文窗口有要求文档特别长比如超过模型窗口时还是得分段送入只是段内不再切块。另外索引体积会比传统分块大因为 landmark 单元之间有重叠这是用空间换召回完整性的取舍。如果你在接入过程中遇到 Key 或通道相关的问题可以直接去控制台看用量和日志https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。文档里有各语言 SDK 的调用示例比对着改比从零写快得多。最后给一个实用技巧验证阶段先用小文档几千字跑通全流程确认 landmark 单元切分符合预期再上大文档。我见过太多人一上来就丢 10 万字进去结果编码超时、索引写爆排查半天发现是参数没调对。小步验证比一次到位省时间。
返回列表