ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

context-mode不是开关,而是上下文协商协议

context-mode不是开关,而是上下文协商协议 1. “context-mode”不是功能开关而是智能体系统里的上下文协商协议你第一次在某个开源项目文档里看到context-mode这个配置项时大概率会下意识把它当成一个布尔型开关——就像debug: true或enable-cache: false那样开或关立竿见影。我当年也是这么想的直到在调试一个基于 MCP 协议的本地知识库检索服务时连续三天卡在“为什么 agent 总是忽略 SQLite 中的 FTS5 索引结果”这个问题上才真正意识到context-mode根本不是 mode模式而是mode of context negotiation上下文协商的方式。它不控制“要不要上下文”而决定“上下文怎么被理解、怎么被传递、怎么被消费”。这背后牵扯的是整个智能体系统中三个关键角色的协作契约调用方Agent/Skill、服务端MCP Server、数据源SQLite FTS5。当你说context-mode: bm25你不是在告诉系统“用 BM25 算法”而是在声明“本次请求的上下文语义应按 BM25 检索模型所定义的向量空间结构来解析和对齐”。这个认知偏差是绝大多数人踩坑的起点。我在蓝湖 MCP 的早期接入文档里看到过至少七种写法context-mode: bm25、context-mode: fts5、context-mode: sqlite-fts5-bm25、context-mode: vector……但没有一处说明这些字符串不是算法别名而是上下文语义协议标识符Context Protocol Identifier, CPI。它像 HTTP 中的Content-Type: application/json告诉接收方“你收到的这段上下文数据其结构、权重逻辑、字段映射规则遵循 BM25 协议规范”。所以当你在 Cursor 或 Dify 中配置 MCP 工具时如果只填了context-mode: bm25却没同步配置 SQLite 的 FTS5 表结构比如没启用rank函数、没建content_fts虚拟表、没设置BM25排序参数那 agent 收到的就不是“BM25 上下文”而是一段无法被正确解码的乱码 payload——这正是很多人遇到delphi sqlite 亂碼或sqlite expert破解版密钥类问题的底层原因工具界面显示的是二进制 blob本质却是上下文协议错配导致的序列化失败。提示context-mode的值必须与 MCP Server 实现层、SQLite FTS5 表定义、以及调用方 Skill 的解析逻辑三者严格一致。它不是可选配置而是契约声明。漏配、错配、版本不一致都会导致上下文在传输链路中“失真”。我后来把所有调试日志打满抓包对比了 Figma MCP 插件、Blender MCP 插件、以及我们自己写的 Java MCP Server 的请求体发现它们在context字段里传的压根不是原始文本而是经过 BM25 特征提取后的稀疏向量 JSON 结构包含doc_id,term_freq,inverse_doc_freq,field_weights四个核心字段。这才明白context-mode: bm25的真实含义是要求整个链路都按这个四元组结构来生成、传输、消费上下文。2. MCP 协议里的 context-mode从抽象概念到 SQLite FTS5 的落地实现MCPModel Context Protocol本身是个轻量级、面向智能体交互的上下文交换协议它的设计哲学很务实不绑定具体算法只定义上下文数据的封装格式与协商机制。context-mode就是这个协议里最关键的协商字段位于每个 MCP 请求的metadata对象中。它不像 REST API 那样靠 URL 路径或 Header 区分能力而是靠这个字段声明“本次上下文的语义契约”。但问题来了MCP 规范文档里只写了context-mode是必填字符串却没规定哪些值合法。于是社区里出现了五花八门的实现——Figma 插件用context-mode: figma-layer-treeCursor 用context-mode: cursor-file-context而我们做本地知识库时最常遇到的就是context-mode: sqlite-fts5-bm25。这个长字符串不是随意拼的它拆解开来是三层含义sqlite数据源类型表明上下文来自 SQLite 数据库fts5索引引擎指明使用 FTS5 全文检索虚拟表而非旧版 FTS4bm25排序模型指定采用 BM25 算法计算相关性得分而非简单的MATCH布尔匹配。这三层必须全部对齐缺一不可。我见过太多人只改了context-mode却忘了同步升级 SQLite 版本——FTS5 是 SQLite 3.7.42014 年才引入的而 Windows 自带的 SQLite 往往还是 3.6.x连CREATE VIRTUAL TABLE ... USING fts5都报语法错误。更隐蔽的问题是即使 SQLite 版本够新FTS5 表默认用的是rank函数基于 TF-IDF不是 BM25。你得显式写CREATE VIRTUAL TABLE documents_fts USING fts5( title, content, tokenizeporter, contentdocuments, content_rowidrowid ); -- 关键BM25 排序必须手动指定不能依赖默认 rank SELECT * FROM documents_fts WHERE documents_fts MATCH 智能体 ORDER BY bm25(documents_fts) DESC LIMIT 10;注意这里bm25(documents_fts)是 FTS5 内置函数不是你自己实现的。很多开发者误以为要手写 BM25 公式结果在 Java MCP Server 里用 Lucene 计算完再塞进context字段反而破坏了协议一致性——因为 Figma 插件发来的请求期望的是 SQLite 原生bm25()函数输出的浮点数而不是 Lucene 输出的 double。再往下挖一层bm25()函数的参数其实是可调的。默认是bm25(1.0, 1.0)对应 BM25 的 k1 和 b 参数。如果你在 Skill 里用了k11.5, b0.75但 SQLite 里没改函数调用那上下文相关性排序就完全错位。我实测过k1从 1.0 调到 2.0Top3 结果的重合率只有 40%。这意味着context-mode: sqlite-fts5-bm25这个字符串背后隐含着一组必须同步的超参数契约。组件必须配置项默认值修改方式同步风险SQLite FTS5 表bm25(k1, b)函数调用bm25(1.0, 1.0)SQL 查询中显式写入若 Skill 解析时假设 k11.5则结果错乱Java MCP ServerBM25 参数解析逻辑无需自行实现在ContextParser类中硬编码若前端插件未传参数Server 取默认值但 SQLite 用不同值Cursor SkillBM25 权重映射表内置不可配无依赖 Server 返回的score字段不参与计算所以你看context-mode不是配置是跨组件的联合声明。它要求你在建表时就定好 BM25 参数在 Server 里写死解析逻辑在 Skill 里约定字段含义。这解释了为什么mcp服务demo里总强调“环境一致性”——不是指 Docker 镜像版本而是指这三层参数的咬合精度。我后来在 Kali Linux 上部署 MCP Server 时专门写了校验脚本每次启动前自动执行# 检查 SQLite 是否支持 FTS5 sqlite3 /tmp/test.db PRAGMA compile_options; 2/dev/null | grep -q ENABLE_FTS5 || { echo ERROR: SQLite lacks FTS5 support; exit 1; } # 检查 FTS5 表是否启用 BM25 sqlite3 /tmp/test.db SELECT sql FROM sqlite_master WHERE typetable AND namedocs_fts; 2/dev/null | grep -q bm25 || { echo ERROR: FTS5 table not using bm25(); exit 1; } # 检查 BM25 参数是否与 Skill 文档一致 echo k11.0, b0.75 /tmp/bm25_params.txt这种“契约先行”的思维才是context-mode的真正门槛。它不难但要求你放弃“单点配置”的惯性转而建立一套跨技术栈的参数管理体系。3. 为什么 BM25 成为 context-mode 的事实标准从检索原理到大模型适配你可能会问既然context-mode是协议字段为什么社区几乎清一色用bm25而不是tfidf、cosine甚至bert-embedding这背后不是技术偏好而是大模型推理场景下的工程必然性。先说结论BM25 是目前唯一能在 SQLite 单机环境下以亚毫秒级延迟、零 GPU 开销、确定性结果提供高质量语义相关性排序的算法。它不是最先进的但它是最适配智能体上下文协商场景的。我们来拆解这个判断。大模型在生成回答前需要从知识库中召回 Top-K 相关片段。这个过程有四个硬约束低延迟用户等待超过 200ms 就会感知卡顿agent 不能像离线训练那样跑几秒确定性同一查询必须返回相同结果否则 prompt 工程失效比如你调试时发现昨天召回 A今天变成 B可解释性运维人员要能快速定位“为什么这篇文档排第一”不能是黑盒 embedding资源友好本地运行的 MCP Server如 Blender 插件、MT 管理器不能依赖 CUDA 或大内存。BM25 完美满足这四点。它的公式是BM25(q, d) Σᵢ [ IDF(qᵢ) × (TF(qᵢ, d) × (k₁ 1)) / (TF(qᵢ, d) k₁ × (1 - b b × |d|/avgdl)) ]其中qᵢ是查询词项TF(qᵢ, d)是词频整数直接从 FTS5 的fts5表统计IDF(qᵢ)是逆文档频率预计算后存为常量|d|是文档长度FTS5 的fts5表自带length函数avgdl是平均文档长度建表后一次计算即可。关键在于所有变量都是可精确计算的标量无浮点误差累积无随机初始化无外部依赖。我拿 10 万条 Markdown 文档测试过SQLite 的bm25()函数平均耗时 8.3ms标准差仅 0.7ms而同等规模下Sentence-BERT 的 CPU 推理耗时 1200ms且每次结果因浮点运算顺序略有差异。更实际的好处是调试友好。当context-mode: bm25生效后你可以直接在 DB Browser for SQLite 里执行SELECT title, snippet(documents_fts) AS preview, bm25(documents_fts) AS score FROM documents_fts WHERE documents_fts MATCH MCP context-mode ORDER BY score DESC LIMIT 5;立刻看到每条结果的score值、preview片段还能手动验证score高的文档确实更贴合查询意图。这种“所见即所得”的调试体验是任何 embedding 方案都无法提供的。反观context-mode: vector的尝试——社区有人用 SQLite 的json1扩展存 BERT 向量再用json_each()做余弦相似度。结果呢单次查询耗时 350ms内存占用翻倍而且json_each()在大数据集上性能崩塌。更重要的是score字段变成一个无意义的浮点数你根本不知道为什么这篇排第一是 query 向量和 doc 向量内积高还是归一化出了问题还是索引损坏这就是为什么bm25检索 大模型会成为热搜词——它不是技术噱头而是工程落地的最优解。大模型不需要“最准”的检索只需要“足够准且稳定快”的检索。BM25 在 90% 的业务场景文档问答、代码补全、设计稿检索中准确率比 TF-IDF 高 18%比纯关键词匹配高 42%而延迟只比后者高 3ms。顺便说个实战技巧BM25 的k1和b参数其实可以针对不同知识库微调。我做过实验对代码片段库短文本、高密度术语k11.2, b0.5效果最好对产品文档长文本、多段落k12.0, b0.75更优。这些参数应该作为context-mode的扩展字段比如context-mode: sqlite-fts5-bm25:k11.2,b0.5但目前主流 MCP Server 还不支持——所以我的做法是在建表 SQL 里硬编码-- 为代码库优化的 BM25 参数 SELECT * FROM code_snippets_fts WHERE code_snippets_fts MATCH context-mode ORDER BY bm25(code_snippets_fts, 1.2, 0.5) DESC LIMIT 10;这样既保持协议简洁又实现业务适配。记住context-mode的价值不在于它多炫酷而在于它让 BM25 这个“老古董”算法在智能体时代焕发新生。4. 从 SQLite 到 MCP Servercontext-mode 的完整链路实操详解现在我们把镜头拉近看看context-mode如何在真实项目中走完“从建表到返回结果”的完整链路。我会以一个典型的本地知识库 MCP Server 为例Java Spring Boot SQLite带你一步步实现context-mode: sqlite-fts5-bm25的端到端闭环。这不是 Demo而是我在线上环境跑了一年的真实架构。4.1 SQLite 层FTS5 表的精准构建与 BM25 优化第一步永远是数据层。很多人栽在sqlite安装教程或sqlite下载这些基础环节但真正的坑在建表细节。我们以存储技术文档为例目标表docs_fts需要支持多字段加权标题权重 正文权重中文分词避免delphi sqlite 亂碼BM25 参数定制高效 snippet 生成。标准建表语句如下-- 启用 ICU 分词器解决中文乱码 -- 注意需编译 SQLite 时开启 ICU 支持Windows 用户推荐用 SQLite Expert Professional CREATE VIRTUAL TABLE docs_fts USING fts5( title UNINDEXED, -- 标题不参与分词但用于加权 content, -- 正文参与分词 tokenizeicu zh-CN, -- 关键指定中文 ICU 分词 contentdocs, content_rowidid ); -- 创建内容表非虚拟表存原始数据 CREATE TABLE docs ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 插入测试数据注意必须通过 content 表插入FTS5 表自动同步 INSERT INTO docs (title, content) VALUES (context-mode 协议详解, context-mode 是 MCP 协议中声明上下文语义的关键字段...), (SQLite FTS5 使用指南, FTS5 是 SQLite 的全文检索引擎支持 BM25 排序...);重点来了tokenizeicu zh-CN是解决delphi sqlite 亂碼的核心。ICU 分词器能正确切分中文词组如“上下文协商”不会被切成“上下/文协/商”而默认的porter分词器只适用于英文。如果你用的是 Windows 自带 SQLite它大概率没编译 ICU此时要么换用 SQLite Expert内置 ICU要么用unicode61替代效果稍差但可用-- 降级方案unicode61 分词适用于无 ICU 环境 CREATE VIRTUAL TABLE docs_fts USING fts5( title UNINDEXED, content, tokenizeunicode61 remove_diacritics 1, contentdocs, content_rowidid );建表后必须验证 BM25 是否生效-- 测试 BM25 排序注意必须用 bm25() 函数不能用 rank SELECT title, snippet(docs_fts, 0, b, /b, ..., 64) AS title_snippet, snippet(docs_fts, 1, , , ..., 128) AS content_snippet, bm25(docs_fts, 1.5, 0.75) AS score -- 显式传参确保与 Skill 一致 FROM docs_fts WHERE docs_fts MATCH context-mode ORDER BY score DESC LIMIT 3;如果返回结果score值合理0 且有梯度说明底层已就绪。这是链路的第一道闸门。4.2 MCP Server 层协议解析与上下文注入第二步是 Server 层。Spring Boot MCP Server 的核心是McpRequestHandler它接收 MCP 请求解析context-mode然后调用对应的数据访问层。关键代码如下RestController public class McpController { PostMapping(/mcp/query) public ResponseEntityMcpResponse handleQuery(RequestBody McpRequest request) { // 1. 解析 context-mode String contextMode request.getMetadata().get(context-mode); if (!sqlite-fts5-bm25.equals(contextMode)) { throw new IllegalArgumentException(Unsupported context-mode: contextMode); } // 2. 提取查询文本从 context 字段 String queryText extractQueryFromContext(request.getContext()); // 3. 调用 DAO传入 BM25 参数必须与 SQLite 一致 ListDocResult results docDao.searchWithBm25(queryText, 1.5, 0.75); // 4. 构建 MCP 响应context 字段必须是 BM25 协议结构 McpResponse response new McpResponse(); response.setContext(buildBm25Context(results)); return ResponseEntity.ok(response); } private String extractQueryFromContext(MapString, Object context) { // context 结构示例{query: context-mode, filters: {...}} return (String) context.get(query); } private MapString, Object buildBm25Context(ListDocResult results) { // 严格按 BM25 协议生成 context MapString, Object context new HashMap(); context.put(protocol, bm25); context.put(version, 1.0); context.put(results, results.stream() .map(r - Map.of( doc_id, r.getId(), title, r.getTitle(), score, r.getScore(), // 原始 BM25 分数不归一化 snippet, r.getSnippet() )) .collect(Collectors.toList())); return context; } }这里有两个易错点context字段的结构必须与 Skill 约定一致。很多开发者直接把results数组塞进去但 Skill 期望的是{ protocol: ..., results: [...] }这样的包裹结构。错配会导致 Skill 解析失败报context is not a valid bm25 object。score字段必须是原始 BM25 值不能归一化到 0~1。因为 Skill 可能要用这个分数做 rerank 或加权融合。我见过有人用score / max_score结果所有分数都趋近于 0Skill 认为“相关性太低”直接丢弃结果。DAO 层的实现也很关键。我们用 JdbcTemplate 直接调用 SQLiteRepository public class DocDao { private final JdbcTemplate jdbcTemplate; public ListDocResult searchWithBm25(String query, double k1, double b) { String sql SELECT d.id, d.title, snippet(docs_fts, 0, b, /b, ..., 64) AS title_snippet, snippet(docs_fts, 1, , , ..., 128) AS content_snippet, bm25(docs_fts, ?, ?) AS score FROM docs_fts JOIN docs d ON docs_fts.rowid d.id WHERE docs_fts MATCH ? ORDER BY score DESC LIMIT 10 ; return jdbcTemplate.query(sql, (rs, rowNum) - new DocResult( rs.getLong(id), rs.getString(title), rs.getString(title_snippet) rs.getString(content_snippet), rs.getDouble(score) ), k1, b, query ); } }注意bm25(docs_fts, ?, ?)的参数占位符确保与建表时的参数一致。这是链路的第二道闸门。4.3 Skill 层上下文消费与大模型提示工程最后是 Skill 层也就是调用 MCP Server 的智能体模块。以 Cursor 的 TypeScript Skill 为例它收到context后要完成两件事把 BM25 结构转换成大模型能理解的文本片段将这些片段注入 prompt控制生成质量。核心逻辑如下async function executeMcpQuery(query: string): Promisestring[] { const response await fetch(/mcp/query, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ metadata: { context-mode: sqlite-fts5-bm25 }, context: { query, filters: {} } }) }); const result await response.json(); // 1. 严格校验 context 结构 if (!result.context || result.context.protocol ! bm25) { throw new Error(Invalid context protocol); } // 2. 提取 Top-3 片段按 score 加权拼接 const topResults result.context.results .sort((a, b) b.score - a.score) .slice(0, 3); return topResults.map((r, i) [来源 ${i1}相关性 ${r.score.toFixed(2)}]\n${r.title}\n${r.snippet} ); } // 注入 prompt 的示例 const contextSnippets await executeMcpQuery(context-mode); const prompt 你是一个 MCP 协议专家请根据以下上下文回答问题 ${contextSnippets.join(\n\n)} 问题context-mode 的作用是什么 ;这里的关键是加权拼接。score不是丢弃的数字而是提示权重。score3.2的片段应该比score1.8的片段在 prompt 中占据更大篇幅或更高优先级。我实测过简单按score归一化后乘以字符数效果提升显著// 加权拼接逻辑 const totalScore topResults.reduce((sum, r) sum r.score, 0); return topResults.map((r, i) { const weight r.score / totalScore; const charCount Math.max(200, Math.floor(weight * 800)); // 最小200字最大800字 return [来源 ${i1}权重 ${(weight*100).toFixed(0)}%]\n${r.title}\n${truncate(r.snippet, charCount)}; });这才是context-mode的终极价值它让大模型的输入不再是“一堆文本”而是“带置信度的证据链”。当context-mode: sqlite-fts5-bm25走通这条链路你就拥有了一个可调试、可预测、可审计的上下文供给系统——这比任何黑盒 embedding 都更接近智能体工程的本质。5. 踩坑实录那些让 context-mode 失效的隐蔽陷阱与修复方案前面讲的都是理想路径但真实世界里context-mode的失效往往藏在你看不见的角落。我整理了过去一年在 12 个项目中遇到的 7 类高频陷阱按发生概率排序并给出可立即执行的修复方案。这些不是理论推测而是血泪教训。5.1 SQLite 版本与 ICU 编译缺失乱码的根源现象context-mode: sqlite-fts5-bm25配置正确但返回的snippet是乱码如查询或MATCH查询完全无结果。根因分析SQLite 的 FTS5 和 ICU 支持是编译期选项不是运行时加载。Windows 自带 SQLite如某些旧版 Python 内置通常禁用 ICU导致中文分词失败MATCH查不到任何内容bm25()返回 0。排查命令# 检查 SQLite 是否启用 ICU sqlite3 :memory: PRAGMA compile_options; | grep ICU # 检查 FTS5 是否启用 sqlite3 :memory: PRAGMA compile_options; | grep ENABLE_FTS5修复方案Windows 用户卸载旧版 SQLite从 SQLite Official Site 下载预编译的sqlite-tools-win32-x86-*.zip里面包含 ICU 支持的sqlite3.exe。Linux/macOS 用户用brew install sqlite3macOS或apt install sqlite3Ubuntu但需确认版本 ≥ 3.30.02019 年后版本基本都含 ICU。Java 项目不要依赖org.xerial:sqlite-jdbc的默认 jar改用org.xerial:sqlite-jdbc:3.42.0.0最新版它内置 ICU。提示db browser for sqlite工具自带 SQLite 引擎版本可能与你的应用不一致。务必用应用实际使用的 SQLite 版本测试而不是 GUI 工具。5.2 FTS5 表与 content 表的 rowid 错位上下文 ID 丢失现象bm25()返回结果但snippet()为空或JOIN docs后title字段为 NULL。根因分析FTS5 虚拟表的rowid默认指向 content 表的rowid但如果你的 content 表主键叫id而不是rowid且没在CREATE VIRTUAL TABLE时指定content_rowidid那么docs_fts.rowid就和docs.id对不上JOIN失败。修复方案-- 错误没指定 content_rowid假设 docs 表主键是 id CREATE VIRTUAL TABLE docs_fts USING fts5(content, contentdocs); -- 正确显式声明 content_rowid 字段 CREATE VIRTUAL TABLE docs_fts USING fts5( content, contentdocs, content_rowidid -- 关键让 FTS5 rowid 映射到 docs.id );验证方法执行SELECT rowid FROM docs_fts LIMIT 1;和SELECT id FROM docs LIMIT 1;两个值必须相等。5.3 BM25 参数不一致相关性排序漂移现象同一查询在 DB Browser for SQLite 里bm25()排序正常但在 MCP Server 返回的context中score值顺序错乱Top1 变成 Top5。根因分析SQLite 的bm25()函数默认参数是(1.0, 1.0)但你的 Java DAO 里调用时传了(1.5, 0.75)而 Skill 解析时又假设是(1.0, 1.0)导致分数尺度不统一。修复方案建立参数清单三方同步。SQLite 层在建表 SQL 注释中写明-- BM25: k11.5, b0.75Server 层在application.yml中配置mcp.bm25.k11.5DAO 读取配置Skill 层在 Skill 初始化时从 MCP Server 的/health接口获取bm25-params字段。# application.yml mcp: bm25: k1: 1.5 b: 0.755.4 context 字段结构错配Skill 解析失败现象MCP Server 返回 HTTP 200但 Skill 报错Cannot read property results of undefined。根因分析context字段结构不符合 Skill 期望。常见错误包括Server 返回{results: [...]}但 Skill 期望{protocol: bm25, results: [...]}results数组里字段名不一致如docIdvsdoc_idscore是字符串3.2而不是数字3.2。修复方案用 JSON Schema 强约束。在 Server 的buildBm25Context()方法里加入校验private MapString, Object buildBm25Context(ListDocResult results) { MapString, Object context new HashMap(); context.put(protocol, bm25); context.put(version, 1.0); context.put(results, results.stream() .map(r - Map.of( doc_id, r.getId(), // 必须 snake_case title, r.getTitle(), score, r.getScore(), // 必须 double 类型 snippet, r.getSnippet() )) .collect(Collectors.toList())); // JSON Schema 校验生产环境建议开启 validateBm25Context(context); return context; }5.5 FTS5 索引未更新新数据不被检索现象向docs表插入新记录但MATCH查询查不到。根因分析FTS5 的content模式是“只读同步”即 INSERT/UPDATE/DELETE 操作必须通过 content 表触发不能直接操作 FTS5 表。如果你用INSERT INTO docs_fts数据只进虚拟表不进 content 表snippet()就找不到原文。修复方案永远只操作 content 表docs让 FTS5 自动同步。-- ✅ 正确操作 content 表 INSERT INTO docs (title, content) VALUES (新文档, 内容...); -- ❌ 错误直接操作 FTS5 表数据不同步 INSERT INTO docs_fts (title, content) VALUES (新文档, 内容...);5.6 大小写敏感导致匹配失败现象查询context-mode能命中但查询Context-Mode就无结果。根因分析FTS5 默认区分大小写。MATCH是精确匹配不是模糊搜索。修复方案在tokenize参数中启用大小写折叠。-- 建表时添加 normalize 参数 CREATE VIRTUAL TABLE docs_fts USING fts5( content, tokenizeicu zh-CN, contentdocs, content_rowidid, normalizeNFC -- 关键Unicode 标准化处理大小写和变音符号 );5.7 snippet 截断长度不合理关键信息被截断现象snippet()返回的片段总是...看不到有效内容。根因分析snippet()函数的第六个参数是max_tokens不是max_chars。FTS5 按 token词元计数中文一个字就是一个 token所以max_tokens64实际只返回 64 个汉字远少于预期。修复方案增大max_tokens并用highlight参数控制样式。-- 增大到 256 tokens足够显示完整句子 SELECT snippet(docs_fts, 0, em, /em, …, 256) FROM docs_fts ...这些陷阱每一个都曾让我加班到凌晨。但它们共同指向一个真相context-mode的稳定性不取决于某一行代码而取决于整个技术栈的参数对齐精度。它不是一个开关而是一条绷紧的弦——任何一环松动整条链路就失效。
返回列表