RAG 的检索层不一定先下载嵌入模型。对包含明确产品名或术语的资料,可以先建立关键词索引,返回原文和出处,再交给回答模型。SQLite FTS5 可以做这个小型原型;本文用 trigram 将连续三个字符作为检索单位,演示中文“保修期”的命中。
资料全部为虚构教学条款,示例不调用模型,不声称它能理解同义词或达到语义检索效果。默认 SQLite 编译功能因 Python 发行版而异;运行前需要确认当前环境提供 FTS5 和 trigram tokenizer。

先说清中文短词的边界
本文使用连续字符片段,不是中文词语分词。少于三个字符的查询不适用于这个 trigram MATCH 演示,所以“保修”会被明确拒绝,“保修期”可用于测试;真实项目应另行定义短词检索或分词方案,而不是给查询任意添加不相干字符。
SQLite 官方 tokenizer 实现可核对 trigram 的三个字符处理;官方 trigram 测试展示 FTS5 建表、MATCH 和相关配置。它处理字面匹配,不能凭空把“保修”理解成“售后服务”。
建立索引并限制公开资料范围
代码只用 Python 标准库。保存为 fts_demo.py,执行 python fts_demo.py。三条资料中两条公开、一条内部,检索应只返回公开原文。
import sqlite3
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
print("sqlite_version:", sqlite3.sqlite_version)
conn.execute("""
CREATE VIRTUAL TABLE chunks USING fts5(
source_id UNINDEXED, page UNINDEXED, scope UNINDEXED, text,
tokenize='trigram'
)
""")
conn.executemany("INSERT INTO chunks(source_id,page,scope,text) VALUES (?,?,?,?)", [
("manual-a", 1, "public", "示例设备 A 的保修期为 12 个月。"),
("manual-b", 2, "public", "示例设备 B 的保修期为 6 个月。"),
("internal-c", 1, "internal", "内部演示设备 C 的保修期为 36 个月。"),
])
def retrieve_literal(term):
if not isinstance(term, str) or len(term.strip()) < 3:
raise ValueError("本演示要求至少三个字符的连续检索词")
literal = '"' + term.strip().replace('"', '""') + '"'
rows = conn.execute("""
SELECT source_id,page,scope,text,bm25(chunks) AS rank
FROM chunks WHERE chunks MATCH ? AND scope=?
ORDER BY rank LIMIT ?
""", (literal, "public", 5)).fetchall()
return [dict(row) for row in rows]
results = retrieve_literal("保修期")
print(results)
assert {row["source_id"] for row in results} == {"manual-a", "manual-b"}
assert all(row["scope"] == "public" and "保修期" in row["text"] for row in results)
try:
retrieve_literal("保修")
except ValueError as error:
print("short_query_rejected:", error)
else:
raise AssertionError("两字符查询不应进入本演示检索")
conn.close()
source_id、page、scope 使用 UNINDEXED,保留为检索结果与范围条件,不加入正文词项。Python sqlite3 官方文档说明参数绑定方式;代码将 MATCH 表达式和范围作为参数传入。检索词还被转成字面短语,避免把用户输入直接当作 FTS 查询语法。
结果与排序怎么核对
固定输入应得到 manual-a 和 manual-b,两条都包含原文及页号,internal-c 不返回。两字符查询应打印明确拒绝原因。若报 no such module: fts5 或 no such tokenizer: trigram,先换成支持该功能的 SQLite/Python 构建,不把空结果当成资料不存在。
SQLite 官方辅助函数实现可核对 bm25 的评分方向。这里按 rank 升序取结果;这个字面相关性分数不是余弦相似度,更不是回答正确率,不应沿用其他向量库的阈值。
接到问答前,再做两次检查
- 问“设备 A 保修期”,检查真正支持设备 A 的原文能否命中。检索词本身若不连续存在于文本,trigram 短语匹配可能没有结果;需要针对真实查询设计分词或多条件策略。
- 问资料里没有的条款,检索没有依据时让回答层说明无法确认。不要将索引中某条相近记录当作无条件答案。
检索接口返回的 source_id 和 page 应跟着片段进入回答环节,用来核对引用。生产范围条件必须来自已认证用户的真实权限,本文固定 public 仅用于公开演示资料,不能让模型自由选择 internal。
何时适合这个原型,何时需要扩展
- 产品名、编号、连续术语明确时,先用小库验证字面召回和出处。
- 同义问法、长问题和语义匹配需求明显时,比较分词检索、向量召回或组合方案,不能把这个原型包装成通用语义搜索。
- 文档更新时,必须同步删除或替换过期索引记录,并保持元数据与正文一致。
- 数据变大或并发提升时,测量真实查询与写入模式;内存库教学结果不是生产容量说明。
下一步是挑一小组有明确术语的公开资料,保存来源字段并运行上述检索。先核对字面命中、范围和引用,再接入模型回答。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/31990.html