检索请求流转
本章聚焦 /chat
内部从“用户自然语言”到“可用于 Prompt 的上下文片段”全过程。核心关注:
- 查询向量生成与参数
- 语言优先检索 + 回退(Fallback)
- 二次语言过滤与 URL 规则
- 上下文拼接与来源列表构建
- 限制与边界(topK / 元数据返回)
- 可观测性与日志字段
入口与查询向量
worker.ts
中:
const embedder = createEmbedder(env);
const [qv] = await embedder.embed([message], Number(env.EMBED_DIM));
仅对当前用户输入(不含历史)生成嵌入,避免历史语义“稀释”当前意图。
优先语言检索阶段
伪代码:
try {
res = await VECTORIZE.query(qv, { topK: 8, filter: { metadata: { language } }, returnValues: false, includeMetadata: true });
if (!res.matches.length) throw new Error('empty');
usedFallback = false;
} catch {
// fallback ...
}
关键点:
- 设定
topK=8
经验值(Prompt 长度控制) - 使用 metadata.language 服务器端过滤(低延迟)
- 超时 / 空结果进入回退逻辑
回退检索与二次过滤
const all = await VECTORIZE.query(qv, { topK: 8, returnValues: false, includeMetadata: true });
const filtered = all.matches.filter(m => language === 'en' ? m.metadata?.url?.includes('/en/') : !m.metadata?.url?.includes('/en/'));
说明:
- 去掉语言过滤扩大召回
- 以 URL 规则“/en/”做客户端二次过滤,兼容旧数据语言字段缺失
- 若仍为空 → contexts 允许空,后续 Answer 逻辑可指示无匹配知识
上下文拼接与来源列表
检索结果经过二次过滤后,将相关片段拼接为最终上下文,供 Prompt 使用。常见做法:
- 按匹配分数降序排列,依次拼接内容,避免重复与冗余。
- 限制总长度(如 token 数),防止超出模型输入限制。
- 构建 sources 列表,包含每个片段的元数据(如标题、URL、语言),便于溯源与展示。
示例代码片段:
const contexts = filtered.map(m => m.value).join('\n---\n');
const sources = filtered.map(m => ({
title: m.metadata?.title,
url: m.metadata?.url,
language: m.metadata?.language,
}));
日志结构与可观测性
为便于排查与优化,建议在检索流程中记录关键日志字段:
- 查询向量摘要(如 hash)
- 使用的检索参数(topK、language、是否回退)
- 匹配片段数量与来源
- 过滤前后结果统计
- 主要异常与耗时
日志示例:
{
"query": "用户输入",
"topK": 8,
"language": "zh",
"fallback": true,
"matches": 5,
"sources": ["url1", "url2"],
"duration_ms": 120
}
改进方向
检索流程的持续优化不仅提升系统召回率,也直接影响用户体验。以下是当前流程的主要改进方向,供参考与后续迭代。
- 优化回退策略:可根据历史检索效果动态调整 topK 或过滤规则。
- 增强多语言支持:完善 metadata.language 标注,减少依赖 URL 规则。
- 上下文拼接智能化:结合语义分组、去重算法提升片段相关性。
- 日志与监控自动化:接入可观测性平台,实时分析检索质量与性能瓶颈。
小结
本章详细解析了检索请求的流转过程,从用户输入的自然语言到最终生成的上下文片段,涵盖了查询向量生成、语言优先检索、回退策略、上下文拼接等多个关键环节。通过对各个阶段的深入剖析,我们可以更好地理解检索系统的工作原理,并为后续的优化与改进提供依据。