SQLite FTS5 + BM25 构建上下文感知型轻量级语义检索

📅 发布时间:2026/9/15 5:58:53
SQLite FTS5 + BM25 构建上下文感知型轻量级语义检索
1. 项目概述什么是 context-mode它不是个功能开关而是一套数据协同范式“context-mode”这个词最近在开发者社区里频繁出现但翻遍主流文档、SDK手册甚至 GitHub 仓库的 README你都找不到一个官方定义的“Context Mode”模块。它不挂在某个 SDK 的.enable()方法后面也不出现在任何 CLI 工具的--help列表里。我第一次在蓝湖Lanhu的插件日志里看到context-mode: enabled这行输出时也以为是 UI 状态标识直到连续三天调试 Figma 插件与本地 SQLite 数据库的同步失败才意识到——这根本不是前端开关而是后端服务层对“上下文感知能力”的一种运行时声明机制。简单说context-mode 是 MCPModel-Controller-Protocol架构中服务端向客户端明确表达“当前请求具备完整语义上下文支撑能力”的协商信号。它不控制渲染逻辑不决定权限策略但它直接决定了客户端是否敢把用户当前选中的设计图层、光标位置、历史操作栈、甚至剪贴板内容作为结构化元数据一并打包发给服务端。比如你在 Figma 里框选三个按钮组件点击“生成 React 组件”如果后端响应头里没带X-MCP-Context-Mode: activeFigma 插件就会退化为纯文本调用只传“按钮”两个字过去而不是{type: Frame, children: [{type:Button, props:{size:lg, variant:primary}}, ...]}这样的完整 AST 上下文。这个信号之所以重要是因为它背后绑定了三类硬性能力一是服务端必须已加载并索引了当前项目全部元数据通常存于 SQLite FTS5二是检索引擎必须启用 BM25 算法而非默认的 simple tokenizer三是协议层必须支持二进制 payload 分帧传输MCP 协议的 v1.3 才正式定义该字段。所以当你在搜索框里输入“context-mode sqlite fts5 bm25”其实是在找一套能闭环支撑这种上下文驱动交互的轻量级数据底座方案——它要足够小嵌入式足够快毫秒级全文检索还要足够语义化BM25 对词频-逆文档频的加权比 TF-IDF 更贴合设计稿这类高重复度文本。适合谁看这篇如果你正在做以下任一事情这篇就是为你写的用 Delphi 写桌面工具却总被 SQLite 中文乱码卡住在 Blender 或 MasterGo 里开发 MCP 插件但搞不定本地数据库联动用 Cursor 或 Trae 调试 AI Agent 时发现 prompt 总是缺关键上下文或者你只是个想搞懂“为什么我的 SQLite 查不到设计稿里的组件名”的前端工程师。我们不讲抽象协议只拆真实链路从 SQLite 建库那一刻起到 BM25 返回第一条高相关结果每一步都带着实测参数和踩坑记录。2. 核心设计思路为什么必须用 SQLite FTS5 BM25 这个组合2.1 放弃 PostgreSQL / MySQL 的真实理由不是性能差而是“过载”很多人第一反应是“既然要全文检索上 Elasticsearch 不香吗”或者“PostgreSQL 的 pg_trgm 扩展不是原生支持相似度”——我试过。去年用 ES 搭了一套蓝湖 MCP 后端单节点吞吐确实高但问题出在冷启动和部署成本上。ES 需要 JVM、分片配置、GC 调优而一个设计协作插件的本地服务理想状态是双击mcp-server.exe就跑起来。更致命的是延迟ES 默认刷新间隔 1 秒而 Figma 插件要求“选中组件 → 弹出建议”必须在 300ms 内完成。我们实测过ES 在 10 万条设计元素元数据下平均响应 420ms而 SQLite FTS5 在同等数据量下P95 延迟压在 86ms。但为什么不用 MySQL它的全文索引也支持布尔模式。问题在于协议兼容性。MCP 协议规范RFC-007第 4.2 条明确要求服务端必须支持“无 schema 依赖的动态字段映射”。MySQL 的 FULLTEXT 索引必须提前声明列而设计稿元数据是高度动态的今天可能是{componentType:icon, size:24px}明天就变成{componentType:icon, size:24px, theme:dark, state:hover}。每次加字段就得ALTER TABLE而 MCP 客户端期望的是“写入即索引”无需服务端人工干预。SQLite FTS5 的content模式完美解决这个问题它允许你建一个虚拟表底层数据存在普通表里索引字段由INSERT INTO fts_table(content) VALUES (json_extract(raw_data, $.properties))动态提取完全解耦。2.2 为什么是 FTS5 而非 FTS4两个关键差异决定稳定性FTS4 和 FTS5 都支持全文检索但 FTS5 是 SQLite 官方在 2015 年重写的下一代引擎核心差异有两点第一分词器可插拔性。FTS4 只内置 simple、porter 两种分词器对中文支持极弱simple 分词器按空格切中文根本没空格。FTS5 则通过fts5vocab表和tokenizeunicode61参数原生支持 Unicode 6.1 标准的断字规则能正确识别中文、日文、韩文、阿拉伯数字混合字符串。我们测试过tokenizeunicode61 按钮组件_24pxFTS5 会切出[按钮, 组件, 24px]而 FTS4 的 simple 分词器只会返回[按钮组件_24px]整个字符串——这意味着你永远搜不到“按钮”。第二BM25 算法的原生集成。这是最关键的。FTS4 的matchinfo()函数只能返回 TF-IDF 相关度而 BM25 是目前大模型 RAG 场景下最主流的排序算法它对长文档降权、对稀有词提权的效果远超 TF-IDF。FTS5 的bm25()函数是硬编码在 C 层的调用开销几乎为零。我们对比过同一份设计稿元数据12.7 万条 JSON 记录检索关键词FTS4 TF-IDF 排序耗时FTS5 BM25 排序耗时Top3 相关性得分归一化“深色模式按钮”142ms89ms0.32 / 0.28 / 0.21“24px icon”138ms85ms0.41 / 0.37 / 0.33“hover 状态”151ms92ms0.39 / 0.35 / 0.29注意看相关性得分BM25 对“hover”这种低频但高区分度的词明显提权而 TF-IDF 把它和常见词“状态”平均加权了。这对 AI Agent 极其重要——当大模型需要从设计稿里提取“交互状态”时它必须优先看到{state:hover}而不是{type:state}这种泛化描述。2.3 为什么 BM25 必须和 SQLite 绑定脱离存储的 BM25 是空中楼阁网上很多教程教你用 Python 的rank_bm25库做离线排序但那在 MCP 场景下是死路。原因很简单BM25 公式里有三个核心参数——k1词频饱和度、b文档长度归一化系数、avgdl平均文档长度。其中avgdl必须基于当前索引的全部文档集合实时计算。如果你用 Python 加载 SQLite 数据再算 BM25每次查询都要SELECT COUNT(*) FROM elements; SELECT AVG(LENGTH(json_data)) FROM elements;这两条 SQL 在 10 万行数据下就要 200ms再加上 Python 循环计算总延迟轻松破秒。而 SQLite FTS5 的bm25()函数是内联执行的它在 B-tree 索引扫描时就同步读取了每个文档的长度信息存储在fts5_index的 segment 文件里avgdl值在索引构建时已预计算并缓存。我们反编译过 FTS5 的fts5Expr.c源码确认bm25()的调用路径不经过 VM 解释器而是直接走 C 函数指针。这意味着你写SELECT id, bm25(matchinfo(fts_elements, pcxnal)) FROM fts_elements WHERE fts_elements MATCH hover ORDER BY rank整个过程都在 SQLite 的 page cache 里完成连一次磁盘 IO 都不需要。所以“context-mode”的本质就是服务端用 SQLite FTS5 BM25 构建了一个可预测、低延迟、自包含的上下文语义空间。它不依赖外部服务不引入网络跳数所有计算都在单文件数据库内闭环。当你看到X-MCP-Context-Mode: active你应该理解为“此刻这个 SQLite 文件就是你的知识图谱它的 BM25 排序结果就是大模型最可信的上下文来源。”3. 实操细节从零搭建支持 context-mode 的 SQLite 数据库3.1 初始化数据库避开 Delphi 和 Windows 下的乱码雷区Delphi 开发者最容易栽在这里。你用TSQLConnection连接 SQLite执行CREATE TABLE elements (id INTEGER PRIMARY KEY, data TEXT)然后插入一条含中文的 JSON{name:主按钮,type:button}再用 DB Browser for SQLite 查看发现name字段显示为涓婚€”閽?——这不是编码问题是 Delphi 的AnsiString默认用系统 ANSI 代码页Windows-1252处理 UTF-8 字节流导致的二次解码错误。解决方案只有两个且必须同时生效强制 SQLite 使用 UTF-8 编码在连接字符串里添加UTF8EncodingTrueDelphi 10.4或CharacterSetUTF8旧版。不要信“自动检测”必须显式声明。建库时指定编码页用命令行工具初始化而非 Delphi 代码# 下载最新 sqlite3.exe3.40 版本旧版不支持 FTS5 unicode61 curl -O https://www.sqlite.org/2023/sqlite-tools-win32-x86-3400000.zip unzip sqlite-tools-win32-x86-3400000.zip # 创建数据库并设置编码 sqlite3.exe design.db PRAGMA encoding UTF-8;提示PRAGMA encoding必须在建任何表之前执行且只能设一次。如果已经建了表唯一办法是VACUUM INTO new.db导出新库。接着创建主表和 FTS5 虚拟表。这里有个关键细节不要用CREATE VIRTUAL TABLE fts_elements USING fts5(data, tokenizeunicode61)直接索引原始 JSON 字段。因为data字段里混着大量无意义的键名如__proto__:{}、$$hashKey:object:123它们会污染 BM25 的avgdl计算。正确做法是提取语义字段-- 1. 创建主表存储原始数据供业务逻辑读取 CREATE TABLE elements ( id INTEGER PRIMARY KEY, project_id TEXT NOT NULL, type TEXT NOT NULL, -- frame, button, text name TEXT, -- 组件名称用户可见 properties TEXT, -- JSON 字符串含 size/variant/state 等 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 2. 创建 FTS5 虚拟表仅索引语义字段 CREATE VIRTUAL TABLE fts_elements USING fts5( name, properties, contentelements, content_rowidid, tokenizeunicode61 remove_diacritics2 );注意remove_diacritics2参数它会移除中文拼音的声调符号如“你好”→“ni hao”避免因输入法差异导致匹配失败。我们实测过开启后“按钮”和“按鈕”繁体的检索召回率从 63% 提升到 98%。3.2 构建 BM25 可用的索引三步触发式同步策略FTS5 的索引不是实时的它采用“增量合并”策略。如果你用INSERT INTO elements ...写入数据FTS5 表不会自动更新。必须手动触发同步且方式不对会导致 BM25 排序失效。我们验证过四种同步方式方式命令是否推荐原因A. INSERT INTO fts_elementsINSERT INTO fts_elements(rowid, name, properties) VALUES (1, 主按钮, {size:lg})❌破坏 content 表关联rowid不一致导致MATCH查询失败B. INSERT INTO elements INSERT INTO fts_elements同上 INSERT INTO fts_elements ...❌事务不同步极易产生脏数据C. INSERT INTO elements INSERT INTO fts_elements(content)INSERT INTO fts_elements(content) VALUES (main_button)⚠️只能索引字符串无法利用json_extract动态提取D. INSERT INTO elements INSERT INTO fts_elements(docid, name, properties)INSERT INTO fts_elements(docid, name, properties) VALUES (last_insert_rowid(), 主按钮, {size:lg})✅docid严格等于主表rowid保证关联一致性所以标准流程是-- 开启事务确保原子性 BEGIN TRANSACTION; -- 1. 写入主表 INSERT INTO elements (project_id, type, name, properties) VALUES (proj_abc123, button, 主按钮, {size:lg, variant:primary, state:default}); -- 2. 同步到 FTS5用 last_insert_rowid() 获取刚插入的 rowid INSERT INTO fts_elements(docid, name, properties) VALUES (last_insert_rowid(), 主按钮, {size:lg, variant:primary, state:default}); COMMIT;注意last_insert_rowid()返回的是当前连接最后插入的 rowid不是全局的。如果你用连接池必须确保 INSERT 和同步在同一个连接内完成。我们在 Delphi 的TSQLConnection里封装了一个SyncToFTS5()方法内部用GetLastAutoGenValue()替代last_insert_rowid()避免多线程冲突。3.3 编写 context-mode 兼容的检索查询BM25 排序的完整语法真正的难点不在建库而在写对查询。网上 90% 的 SQLite FTS5 教程只教MATCH keyword但context-mode要求的是多字段加权、语义增强、结果截断。我们以一个真实场景为例Figma 插件需要根据用户当前选中的图层假设typebutton,name提交推荐相似组件。查询必须在name字段精确匹配“提交”在properties字段模糊匹配“size”、“variant”、“state”等键值对name字段赋予更高权重因为名称是用户主动输入的限制返回前 5 条且排除自身id ! ?最终 SQL 如下SELECT e.id, e.name, e.properties, -- BM25 排序name 字段权重 x2properties 权重 x1 bm25(fts_elements, 0, 2.0, 1.0) AS rank FROM fts_elements JOIN elements e ON e.id fts_elements.rowid WHERE fts_elements MATCH name:提交 OR properties:submit* OR properties:primary OR properties:lg AND e.id ! ? ORDER BY rank LIMIT 5;关键点解析bm25(fts_elements, 0, 2.0, 1.0)第一个参数是虚拟表名第二个0表示使用第一个字段name的权重为 2.0第三个1.0表示第二个字段properties权重为 1.0。权重比直接影响排序我们实测name权重设为 3.0 时同名组件召回率提升但多样性下降2.0 是平衡点。MATCH子句里的name:提交是短语匹配必须连续出现而properties:submit*是前缀匹配匹配 submit, submission 等properties:primary是精确匹配。FTS5 支持这种混合语法比 Elasticsearch 的 bool query 更轻量。JOIN elements e ON e.id fts_elements.rowid这是必须的。FTS5 虚拟表不存储原始数据只存倒排索引所有业务字段必须从主表 JOIN 获取。我们用 Python 的sqlite3模块实测了这条 SQL 在 12.7 万条数据下的性能import sqlite3 conn sqlite3.connect(design.db) conn.execute(PRAGMA journal_mode WAL) # 启用 WAL 模式提升并发 conn.execute(PRAGMA mmap_size 268435456) # 启用内存映射加速大库访问 cur conn.cursor() cur.execute( SELECT e.id, e.name, bm25(...) FROM fts_elements JOIN elements e ON e.id fts_elements.rowid WHERE fts_elements MATCH ? AND e.id ! ? ORDER BY rank LIMIT 5 , [name:提交, 12345])P95 延迟稳定在 89ms内存占用峰值 42MB全库 1.2GB。这个数字意味着即使在低端笔记本上Figma 插件也能实现亚秒级响应。4. 完整实操流程从安装 SQLite 到上线 MCP 服务4.1 Windows 下 SQLite 安装与验证绕过所有坑很多教程让你下载sqlite-dll-win32-x86-*.zip但那是 DLL 文件不是可执行工具。你需要的是sqlite-tools包。以下是零失误安装步骤下载正确包访问 https://www.sqlite.org/download.html 找到Precompiled Binaries for Windows区域下载sqlite-tools-win32-x86-*.zip不是sqlite-dll-*.zip。解压到固定路径比如C:\sqlite\并把C:\sqlite\加入系统 PATH。验证方法打开 CMD输入sqlite3 --version应返回3.40.0 2022-11-01 18:01:01...。创建测试库并验证 FTS5# 创建库 sqlite3.exe test.db # 在 sqlite3 命令行里执行 sqlite PRAGMA compile_options; # 输出必须包含 ENABLE_FTS5否则版本太低 sqlite CREATE VIRTUAL TABLE t1 USING fts5(a, b); sqlite INSERT INTO t1 VALUES(hello world, sqlite fts5); sqlite SELECT * FROM t1 WHERE t1 MATCH hello; # 应返回一行结果注意如果PRAGMA compile_options没有ENABLE_FTS5说明你下错了包。SQLite 官网的sqlite-dll包默认不编译 FTS5必须用sqlite-tools。4.2 用 DB Browser for SQLite 可视化管理解决乱码和 JSON 查看DB Browser for SQLiteDB4S是 Windows 下最友好的 GUI 工具但默认设置会引发乱码。配置步骤下载最新版v3.12.2安装时勾选“Add to PATH”。打开软件 →Edit→Preferences→General标签页Default encoding for text files改为UTF-8Default encoding for database files改为UTF-8Use system locale for number formatting取消勾选避免千分位逗号干扰 JSON查看 JSON 字段技巧右键properties列 →View cell content as→JSON。它会自动格式化缩进比纯文本易读十倍。我们曾用 DB4S 发现一个关键 Bug某次批量导入时properties字段末尾多了个不可见的\u200b零宽空格导致json_extract(properties, $.size)返回 NULL。GUI 工具的可视化能力是命令行无法替代的调试利器。4.3 构建 MCP 服务端Java Spring Boot 示例MCP 协议本身很轻量核心就三点HTTP POST/mcp/query请求体是 JSON响应体也是 JSON。我们用 Spring Boot 2.7 搭建最小可行服务RestController RequestMapping(/mcp) public class MCPController { Autowired private SQLiteService sqliteService; // 封装了上述 FTS5 查询逻辑 PostMapping(/query) public ResponseEntityMapString, Object handleQuery(RequestBody MapString, Object request) { // 1. 解析 context-mode 请求头 String contextMode requestHeader.get(X-MCP-Context-Mode); if (!active.equals(contextMode)) { return ResponseEntity.status(400) .body(Map.of(error, context-mode not active)); } // 2. 提取语义参数 String keyword (String) request.get(keyword); // 用户输入的关键词 Long excludeId ((Number) request.get(exclude_id)).longValue(); // 当前选中组件 ID // 3. 执行 BM25 查询 ListMapString, Object results sqliteService.searchByBM25(keyword, excludeId); // 4. 构建响应符合 MCP 协议 v1.3 MapString, Object response new HashMap(); response.put(status, success); response.put(results, results); response.put(context_mode, active); // 显式声明供客户端二次校验 response.put(timestamp, System.currentTimeMillis()); return ResponseEntity.ok(response); } }SQLiteService.searchByBM25()的核心就是执行前述 SQL。关键经验不要用 JPA/Hibernate 操作 FTS5 表。JPA 的Query无法解析MATCH语法且bm25()函数会被 Hibernate 当作未知函数报错。必须用原生 JDBCRepository public class SQLiteService { Autowired private JdbcTemplate jdbcTemplate; public ListMapString, Object searchByBM25(String keyword, Long excludeId) { String sql SELECT e.id, e.name, e.properties, bm25(fts_elements, 0, 2.0, 1.0) AS rank FROM fts_elements JOIN elements e ON e.id fts_elements.rowid WHERE fts_elements MATCH ? AND e.id ! ? ORDER BY rank LIMIT 5 ; return jdbcTemplate.queryForList(sql, keyword, excludeId); } }启动服务后用 curl 测试curl -X POST http://localhost:8080/mcp/query \ -H X-MCP-Context-Mode: active \ -H Content-Type: application/json \ -d {keyword: name:\提交\, exclude_id: 12345}响应体里results数组的第一项就是 BM25 排名最高的相似组件。这个服务可以打包成单文件 jar双击运行完美匹配 MCP “轻量、嵌入、去中心化”的设计哲学。5. 常见问题与独家排查技巧5.1 问题速查表90% 的故障都发生在这五个环节问题现象可能原因排查命令/步骤解决方案MATCH查询始终返回空FTS5 表未正确关联主表SELECT * FROM fts_elements LIMIT 1;看是否为空SELECT count(*) FROM elements;看主表是否有数据检查content和content_rowid参数是否匹配确认同步 INSERT 是否执行中文检索失败返回乱码SQLite 库编码非 UTF-8sqlite3.exe your.db PRAGMA encoding;重建库sqlite3 new.db PRAGMA encoding UTF-8;再导入数据BM25 排序结果与预期不符权重参数错误或字段顺序颠倒SELECT bm25(fts_elements, 0, 1.0, 1.0), bm25(fts_elements, 0, 2.0, 1.0) FROM fts_elements LIMIT 1;确认bm25()第二个参数是字段索引从 0 开始不是字段名Delphi 插件读取 SQLite 报“unable to open database file”连接字符串路径含中文或空格在 Delphi 里打印TSQLConnection.Params.Text用ExtractFilePath(Application.ExeName)拼接绝对路径避免相对路径Figma 插件调用 MCP 服务超时服务端未启用 WAL 模式sqlite3.exe your.db PRAGMA journal_mode;执行PRAGMA journal_mode WAL;重启服务5.2 独家避坑技巧那些文档里不会写的细节技巧一用fts5vocab表诊断分词效果FTS5 的fts5vocab表是调试分词器的神器。建好 FTS5 表后立即执行CREATE VIRTUAL TABLE vocab USING fts5vocab(fts_elements, col); SELECT * FROM vocab WHERE term LIKE 按钮%;它会列出所有以“按钮”开头的分词结果及出现频次。我们曾发现tokenizeunicode61会把“按钮组件”切为[按钮, 组件]但“按钮_24px”切为[按钮_24px]下划线被当作分隔符。解决方案是加tokenizeunicode61 separators_强制把下划线也当分隔符。技巧二BM25 的k1和b参数微调指南FTS5 的bm25()函数默认k11.2,b0.75但设计稿元数据有特殊性单条记录很短平均 120 字符且关键词密度高。我们通过 A/B 测试确定了最优值k10.8降低词频饱和度避免“按钮”出现 5 次就和出现 1 次的“hover”权重拉不开b0.5减小文档长度归一化影响因为所有记录长度相近没必要过度降权长文本修改方式在CREATE VIRTUAL TABLE时指定CREATE VIRTUAL TABLE fts_elements USING fts5( name, properties, tokenizeunicode61, contentelements, content_rowidid, prefix2 3, -- 支持 2-gram 和 3-gram提升短词匹配 detailcolumns -- 只索引列值不索引位置节省空间 );技巧三用fts5的automerge参数控制索引合并频率FTS5 默认每 16 次 INSERT 触发一次小合并每 32 次触发一次大合并。在高频写入场景如实时同步设计稿这会导致查询延迟毛刺。我们设为INSERT INTO fts_elements(fts_elements) VALUES(automerge4); INSERT INTO fts_elements(fts_elements) VALUES(crisismerge8);automerge4表示每 4 次 INSERT 合并一次crisismerge8表示当未合并段超过 8 个时强制合并。实测后 P95 延迟波动从 ±45ms 降到 ±12ms。技巧四Delphi 下 JSON 字段乱码的终极解法如果上述UTF8EncodingTrue仍无效问题一定出在TJSONObject.ToString()。Delphi 的ToString()默认用TEncoding.Default即系统 ANSI必须强制指定 UTF-8var json: TJSONObject; utf8Str: UTF8String; begin json : TJSONObject.Create; json.AddPair(name, 主按钮); utf8Str : UTF8Encode(json.ToString); // 关键 // 然后用 utf8Str 作为 SQL 参数 end;这个细节让我们的 Delphi 客户端上线时间提前了 3 天。6. 实战延伸如何把 context-mode 能力注入现有工具链6.1 在 Cursor/Trae 中调用本地 MCP 服务无需改插件源码Cursor 和 Trae 都支持自定义 Skill而 Skill 的本质就是 HTTP 请求。你不需要 fork 它们的仓库只需在~/.cursor/skills/目录下新建一个 JSON 文件{ name: DesignContextSearch, description: Search similar components from local design database, api: { url: http://localhost:8080/mcp/query, method: POST, headers: { X-MCP-Context-Mode: active, Content-Type: application/json } }, parameters: [ { name: keyword, type: string, description: Search keyword, e.g. name:\提交\ }, { name: exclude_id, type: number, description: ID to exclude from results } ] }保存为design-context.json重启 Cursor。在编辑器里按CmdK输入/design-context keywordname:\提交\ exclude_id12345就能直接调用你的 SQLite 服务。这就是 context-mode 的威力它把数据库变成了可编程的语义 API。6.2 用 Blender MCP 插件读取 SQLitePython 脚本示例Blender 的 Python 环境自带sqlite3模块无需额外安装。在插件脚本里这样写import sqlite3 import json def get_similar_components(component_name: str, exclude_id: int): db_path bpy.path.abspath(//design.db) # 从 .blend 文件同目录读取 conn sqlite3.connect(db_path) conn.row_factory sqlite3.Row # 启用字典式访问 cursor conn.cursor() cursor.execute( SELECT e.id, e.name, e.properties, bm25(fts_elements, 0, 2.0, 1.0) AS rank FROM fts_elements JOIN elements e ON e.id fts_elements.rowid WHERE fts_elements MATCH ? AND e.id ! ? ORDER BY rank LIMIT 3 , [fname:{component_name}, exclude_id]) results [] for row in cursor.fetchall(): # 自动解析 JSON 字段 props json.loads(row[properties]) results.append({ id: row[id], name: row[name], size: props.get(size, md), variant: props.get(variant, default) }) conn.close() return results # 在 Blender 操作符里调用 class OBJECT_OT_search_similar(bpy.types.Operator): bl_idname object.search_similar bl_label Search Similar Components def execute(self, context): similar get_similar_components(主按钮, 12345) self.report({INFO}, fFound {len(similar)} similar: {similar}) return {FINISHED}这段代码让 Blender 的建模师能在 3D 视口中一键搜索“相似按钮”所有数据来自本地 SQLite不依赖网络不上传设计稿——这正是 context-mode 设计的初心把上下文智能还给用户自己的设备。6.3 为 Java 应用发布 MCP 服务Spring Boot Starter如果你的公司已有 Java 后端不想单独起服务可以用我们开源的mcp-spring-boot-starterGitee 上搜workbudyy/mcp-spring-boot-starter。它把 SQLite FTS5 封装成 Spring Bean!-- Maven 依赖 -- dependency groupIdio.github.workbudyy/groupId artifactIdmcp-spring-boot-starter/artifactId version1