Self-Query 与元数据过滤
Self-Query Retrieval 用模型将自然语言转换为一份结构化检索请求,其中包含语义查询和元数据过滤条件。检索器接到请求后,在符合条件的文档中查找相关内容。这里的模型负责理解请求并生成条件,实际查询由检索系统执行。
例如,“找 2025 年版的境内差旅提交期限”包含两类信息:“提交期限”描述要找的内容,“2025 年版”和“境内”限定文档范围。Self-Query 将后两项转换为过滤条件,让数据库限定年份和地区,再用语义查询查找提交条款。如果只计算向量相似度,其他年份、地区的相似条款也可能排在前面。
文档中需要先存好元数据
元数据(metadata)是随文档或片段保存的结构化字段。例如知识库中有以下记录:
| 片段 | 正文内容 | policy_year | travel_scope |
|---|---|---|---|
| A | 境内差旅费用的报销提交期限 | 2025 | domestic |
| B | 境内差旅费用的报销提交期限 | 2026 | domestic |
| C | 境外差旅费用的报销提交期限 | 2025 | international |
A、B、C 的文本非常接近,向量检索可能把它们都排在前面。但用户只需要 2025 年版境内条款,只有 A 符合条件。
Self-Query 无法凭空提供可靠的元数据。policy_year、travel_scope 必须在入库时正确填写;同一份制度包含境内和境外两章时,应按章节或片段标记范围,不能把整份文档统一标成境内。
将一句话拆成查询和过滤条件
要让模型生成条件,需要告诉它有哪些字段、字段含义,以及允许的值:
| 字段 | 含义 | 类型与允许值 |
|---|---|---|
policy_year | 制度版本年份 | 整数 |
travel_scope | 条款适用的差旅范围 | domestic、international |
模型根据这份 schema,将请求拆成:
{
"semantic_query": "差旅结束后的费用报销提交期限",
"filter": {
"and": [
{"field": "policy_year", "op": "eq", "value": 2025},
{"field": "travel_scope", "op": "eq", "value": "domestic"}
]
}
}semantic_query 用来计算内容相关性;filter 中的两个条件用 and 连接,表示必须同时满足。eq 表示相等,其他需求还可能使用范围比较、in、or 等操作,具体支持哪些操作由后端决定。
这些结构化条件是模型输出的中间表示。适配器还要将它翻译为目标数据库支持的过滤语法。例如概念上等价于:
在 policy_year = 2025 AND travel_scope = 'domestic' 的记录中,
查找与“差旅结束后的费用报销提交期限”最相关的片段。对于上表,过滤排除了 B 和 C,检索器再从满足条件的条目中返回相关结果。若还有 2025 年境内的住宿标准、交通补助等片段,语义查询负责把“提交期限”排到更合适的位置。
从模型输出到实际检索
完整流程包括查询解析、条件校验、后端转换和执行:
自然语言请求 + 元数据 schema
↓ 模型解析
语义查询 + 结构化过滤条件
↓ 校验字段、类型、运算符
转换成检索后端的请求
↓
过滤与相似度搜索
↓
返回符合条件的相关片段伪代码如下:
def self_query_search(question, schema, permission_filter):
parsed = parse_query(question, schema)
checked = validate_filter(parsed.filter, schema)
backend_filter = translate_filter(checked)
return vector_search(
query=parsed.semantic_query,
filter=AND(permission_filter, backend_filter),
)校验需要检查字段是否存在、值类型是否正确、枚举是否有效、运算符是否受支持。例如把年份写成字符串“去年”,或者生成不存在的 department_name 字段,都需要在执行前处理。复杂的 and、or 条件则要递归检查。模型输出可以解析为 JSON,不代表它已经是有效查询。
适配器将通过校验的有限结构转换为数据库语法,避免直接执行模型生成的任意 SQL 或表达式。访问权限由服务端根据登录身份生成,并与业务过滤条件取交集;权限字段不交给模型自行决定。
年份和有效时间要分清
“2025 年版”和“2025 年某次出差适用的制度”不一定是同一个条件。制度可能在年中修订,也可能延后生效。若用户需要查某个时点适用的条款,就应存储生效区间,例如 valid_from 和 valid_to。
采用左闭右开的有效区间 [valid_from, valid_to) 时,查询时点 t 的条件为:
valid_from <= t
AND (valid_to > t OR valid_to IS NULL)这里空的 valid_to 表示尚未设定终止时间。这种数据约定需要在 schema 中说明,模型才能将自然语言翻译成相应条件。“当前有效”还需要给定当前时间;查询历史制度时,也不能再统一加上“仅查当前有效版本”的限制。
用户表达无法确定是在问版本年份还是适用时间时,应先澄清。正确执行一个错误条件,仍然会找错文档。
为什么条件正确,结果仍可能不足
检索请求表达的是“在符合条件的条目中找最相关的若干条”,但后端的物理执行方式不同。有的先过滤,有的在向量搜索时结合过滤,有的先取相似结果再筛选。
假设先取向量 top-10,其中九条是 2026 年版,再执行 policy_year = 2025,最终只剩一条。知识库中可能还有其他相关旧版条款,只是它们没进入最初的 top-10。遇到这种情况,应检查后端如何支持过滤搜索;增大初始候选量可能缓解问题,但不能保证找全。
零结果也可能来自元数据缺失、枚举拼写不一致,或用户要求的条款尚未入库。不能因为没搜到,就静默删除用户明确指定的年份或范围。先核对解析条件与入库字段,再判断是需要调整查询还是缺少资料。
官方参考
- LangChain Self-Query API:查询构造、结构化过滤和检索器接口。部分实现还支持解析结果数量,其他能力取决于后端适配器。
- Qdrant Filtering:组合条件、范围和空值等过滤操作的后端表示。