主题
混合检索引擎
本文引用的源码文件
本文引用的文件
目录
引言
本文件面向“混合检索引擎”的技术文档,聚焦语义搜索与关键词匹配的融合算法、相似度阈值设置、结果重排序与去重策略,以及标签过滤、元数据查询、权限控制、性能优化、缓存策略和并发处理。系统采用双进程架构:Chrome 扩展端(Manifest V3)通过本地 HTTP/WebSocket 调用独立 Agent 端的记忆与知识库能力;Agent 端同时提供长期记忆检索(memory service)与知识库检索(RAG searcher),两者共享关键词匹配实现,但融合策略不同:记忆侧使用 RRF 排名融合,知识库侧使用余弦相似度加分后统一阈值过滤。
项目结构
混合检索相关代码主要分布在以下位置:
- 知识库检索入口与增强逻辑:
agent/src/rag/searcher.js - 长期记忆业务与融合流程:
agent/src/memory/service.js - 记忆关键词分词与计分:
agent/src/memory/tokens.js - 语义采信门与 RRF 融合:
agent/src/memory/fusion.js - 标签过滤与名次加成:
agent/src/memory/tags.js - 扩展端工具定义(暴露 knowledge_search 等):
src/background/tools/rag-tools.js - 知识库默认检索配置:
agent/src/rag/config.js
graph TB subgraph "扩展端" RT["rag-tools.js<br/>知识检索工具定义"] end subgraph "Agent 端" MS["memory/service.js<br/>长期记忆检索与融合"] MR["memory/tokens.js<br/>关键词分词与计分"] MF["memory/fusion.js<br/>语义采信门与RRF"] MT["memory/tags.js<br/>标签过滤与名次加成"] RS["rag/searcher.js<br/>知识库检索器"] RC["rag/config.js<br/>默认检索配置"] end RT --> RS MS --> MR MS --> MF MS --> MT RS --> RC
图示来源
- src/background/tools/rag-tools.js#L5-L66
- agent/src/memory/service.js#L1-L20
- agent/src/memory/tokens.js#L1-L15
- agent/src/memory/fusion.js#L1-L27
- agent/src/memory/tags.js#L1-L17
- agent/src/rag/searcher.js#L1-L25
- agent/src/rag/config.js#L1-L10
章节来源
- src/background/tools/rag-tools.js#L5-L66
- agent/src/rag/searcher.js#L1-L38
- agent/src/memory/service.js#L1-L20
核心组件
- 知识库检索器 Searcher:负责向量检索 + 关键词命中增强 + 阈值过滤 + 旁路探测。
- 长期记忆服务 Memory Service:负责候选收集、标签过滤、关键词通道、可选向量通道、RRF 融合、会话去重、访问计数更新。
- 关键词匹配 tokens:提供查询切段、字段权重、文档频率过滤、关键词计分。
- 融合模块 fusion:提供语义采信门(相对落差 + 条数封顶)与 RRF 排名融合。
- 标签模块 tags:提供标签硬过滤(无 query 时)与标签名次加成(有 query 时)。
- 扩展端 rag-tools:对外暴露 knowledge_search、knowledge_ingest、knowledge_list 三个工具。
章节来源
- agent/src/rag/searcher.js#L27-L77
- agent/src/memory/service.js#L461-L739
- agent/src/memory/tokens.js#L14-L39
- agent/src/memory/fusion.js#L24-L60
- agent/src/memory/tags.js#L13-L62
- src/background/tools/rag-tools.js#L5-L66
架构总览
混合检索引擎在两个层面工作:
- 知识库检索(RAG):以向量检索为主,关键词命中作为“抬分项”,最终按余弦分数 + 关键词加分统一阈值过滤,空结果时进行旁路探测放宽阈值。
- 长期记忆检索(Memory):关键词通道为必选,向量通道可选;当向量可用时采用 RRF 将关键词名次与语义名次融合,再结合标签名次加成与去重策略输出结果。
sequenceDiagram participant Client as "调用方" participant Memory as "memory/service.js" participant Tokens as "memory/tokens.js" participant Fusion as "memory/fusion.js" participant Tags as "memory/tags.js" participant Store as "本地记忆存储" Client->>Memory : recall(query, tags, memoryType, limit) Memory->>Store : 读取候选记忆 Memory->>Tags : hasTagRequest(tags) alt 无query且请求标签 Memory->>Tags : filterByTags(candidates, tags) else 有query Memory->>Tokens : extractMemoryTerms(query) Memory->>Tokens : buildDocFreqFilter(candidates, terms) Memory->>Tokens : keywordScore(m, terms, originalQuery, dfFilter) opt 向量可用 Memory->>Fusion : selectSemanticHits(cosPairs) Memory->>Fusion : rrfFuse([kwRank, vecRank]) end Memory->>Tags : applyTagRankLift(candidates, tags) end Memory->>Memory : 去重与limit截断 Memory-->>Client : results, total, tier, vectorMatched
图示来源
- agent/src/memory/service.js#L473-L739
- agent/src/memory/tokens.js#L50-L158
- agent/src/memory/fusion.js#L38-L60
- agent/src/memory/tags.js#L24-L62
详细组件分析
知识库检索器 Searcher
Searcher 的职责是执行一次检索:先嵌入查询向量,再从底层存储取 topK*2 候选,随后用关键词命中对候选进行“抬分不压分”,统一按阈值过滤;若结果为空则进行旁路探测(放宽阈值并限制返回数量),用于区分“无数据”和“被过滤”。
关键点:
- 查询嵌入维度校验:避免索引与模型不一致导致无意义结果。
- 关键词增强:英文按词边界匹配,中文按最长命中片段计分单元;高频单元剔除(超过半数 chunk 的单元视为无区分度)。
- 阈值过滤与旁路:score >= threshold 才保留;为空时尝试 threshold - 0.2 的宽松阈值,最多 3 条。
flowchart TD
Start(["search(query, options)"]) --> Embed["embedQuery(query)"]
Embed --> Validate{"维度匹配?"}
Validate --> |否| Error["抛出embeddingDimensionMismatch"]
Validate --> |是| Candidates["store.search(queryVec, query, topK*2)"]
Candidates --> Boost["_boostByKeywords(query, candidates)"]
Boost --> Filter["filter(score >= threshold)"]
Filter --> Empty{"results为空?"}
Empty --> |否| Return["返回topK结果"]
Empty --> |是| Fallback["store.search(queryVec, query, topK=3)<br/>filter(score >= threshold - 0.2)"]
Fallback --> Return
图示来源
章节来源
长期记忆检索服务 Memory Service
Memory Service 的 recall 流程分为三个阶段:
- 阶段 1(锁内只读):读取真源、收集候选、校验标签请求、校验向量缓存指纹、收集缺失项。
- 阶段 2(锁外补齐):对缺失项进行批量嵌入并写入派生缓存;失败按 modelKey 冷却 60s。
- 阶段 3(锁内收尾):关键词打分、可选语义采信、RRF 融合、标签名次加成、去重、会话去重、访问计数 RMW 写回。
关键特性:
- 关键词通道:使用 tokens.js 的字段权重、文档频率过滤、整句原样命中加分。
- 语义通道:纯 top-K 采信(相对落差 margin + 条数封顶 maxTake),不再使用绝对地板。
- RRF 融合:仅消费名次,不叠加绝对相似度分数。
- 标签角色:无 query 时硬过滤;有 query 时只做名次加成。
- 会话去重:优先返回未召回过的记忆,若无新记忆则退回旧记忆并提示。
flowchart TD
S(["recall(args, sessionId, t)"]) --> Tier["判定档位与查询向量"]
Tier --> Phase1["阶段1:锁内只读<br/>候选收集/标签判断/向量指纹校验"]
Phase1 --> Phase2["阶段2:锁外补齐缺失向量"]
Phase2 --> Phase3["阶段3:锁内收尾"]
Phase3 --> KW["关键词打分与门槛过滤"]
KW --> Fuse{"向量可用?"}
Fuse --> |否| TagLift["标签名次加成"]
Fuse --> |是| Semantic["selectSemanticHits(cosPairs)"]
Semantic --> RRF["rrfFuse([kwRank, vecRank])"]
RRF --> TagLift
TagLift --> Dedup["去重与limit截断"]
Dedup --> SessionDedup["会话级去重"]
SessionDedup --> VisitCount["访问计数RMW写回"]
VisitCount --> Format["格式化输出"]
图示来源
- agent/src/memory/service.js#L473-L739
- agent/src/memory/tokens.js#L127-L158
- agent/src/memory/fusion.js#L38-L60
- agent/src/memory/tags.js#L49-L62
章节来源
关键词匹配与计分 tokens
tokens.js 提供记忆侧关键词通道的统一实现:
- 查询切段:拉丁整词 + 中文连续段,与知识库共用同一份匹配实现。
- 字段权重:content > title = tags。
- 文档频率过滤:统计每个命中单元出现在多少候选中,过半者剔除;df ≤ 2 豁免。
- 关键词计分:字段内命中单元封顶后乘权重,跨字段累加;另加整句原样命中加分。
- 命中门槛:units ≥ 2 或存在完整单元命中(full)。
classDiagram
class Tokens {
+FIELD_WEIGHT
+ORIGINAL_QUERY_CONTENT_BONUS
+ORIGINAL_QUERY_TITLE_BONUS
+KEYWORD_MIN_UNITS
+KEYWORD_MAX_DF_RATIO
+KEYWORD_DF_EXEMPT
+memorySearchText(m) string
+extractMemoryTerms(query) Terms
+buildDocFreqFilter(candidates, terms) DFResult
+keywordScore(m, terms, originalQuery, dfFilter) Score
}
图示来源
章节来源
语义采信门与 RRF 融合 fusion
fusion.js 提供两条关键能力:
- selectSemanticHits:纯 top-K 采信,两道门——条目级相对落差 cos ≥ top1 - margin,条数封顶 maxTake。
- rrfFuse:Reciprocal Rank Fusion,仅消费名次,不消费分数,因此两路刻度不可比也不再是问题。
flowchart TD Pairs["pairs=[id,cos]"] --> Rank["按cos降序排序"] Rank --> Floor["floor=top1-margin"] Floor --> Select["filter(cos>=floor).slice(0,maxTake)"] Select --> Output["返回采信结果"]
图示来源
章节来源
标签过滤与名次加成 tags
tags.js 明确标签在不同场景下的角色:
- 无 query 时:标签是唯一选择器,保持硬过滤。
- 有 query 时:标签不再剔除候选,只按重叠比例上抬名次(TAG_RANK_LIFT)。
- 不参与 df 统计池:避免缩小判据导致误杀。
flowchart TD
Req["requestedTags"] --> Has{"hasTagRequest()"}
Has --> |否| Pass["返回全量或不变"]
Has --> |是| Mode{"是否有query?"}
Mode --> |否| Filter["filterByTags(candidates, tags)"]
Mode --> |是| Lift["applyTagRankLift(ranked, tags)"]
图示来源
章节来源
扩展端工具 rag-tools
扩展端通过 rag-tools.js 暴露三个工具:
- knowledge_search:支持 query、collectionId、topK。
- knowledge_ingest:支持 text/file/url 类型入库。
- knowledge_list:列出可用知识库及文档数量。
这些工具由后端 agent /api/rag/* 端点驱动,动态注册仅在 ragEnabled=true 且 agent /api/status ragAvailable=true 时启用。
章节来源
依赖关系分析
- Searcher 依赖 embeddingProvider 与 store(VectraStore),并使用 keyword-match 提供的切段与匹配函数。
- Memory Service 依赖 tokens.js、fusion.js、tags.js、vectors.js、tier.js、embedding-config.js 等。
- 扩展端 rag-tools 仅定义工具契约,实际执行由 Agent 端 RAG 接口完成。
graph LR RT["rag-tools.js"] --> RS["rag/searcher.js"] RS --> RC["rag/config.js"] MS["memory/service.js"] --> MR["memory/tokens.js"] MS --> MF["memory/fusion.js"] MS --> MT["memory/tags.js"]
图示来源
- src/background/tools/rag-tools.js#L5-L66
- agent/src/rag/searcher.js#L11-L25
- agent/src/memory/service.js#L10-L19
章节来源
- agent/src/rag/searcher.js#L11-L25
- agent/src/memory/service.js#L10-L19
- src/background/tools/rag-tools.js#L5-L66
性能与并发优化
- 知识库检索:
- 向量维度校验避免无效计算。
- 关键词扫描上限 KEYWORD_SCAN_LIMIT 防止大库卡顿。
- 旁路探测减少误判“无数据”的概率。
- 长期记忆检索:
- 三阶段设计避免外部嵌入拖垮进程互斥锁。
- 向量补齐失败冷却 RECALL_BACKFILL_COOLDOWN_MS 降低重复失败开销。
- 会话级去重避免重复返回相同记忆。
- 访问计数 RMW 重读真源防覆盖并发写。
- 并发与缓存:
- 进程内 Promise 链锁 withMemoryLock 串行化读写。
- 向量缓存按 modelKey 指纹校验,换模型整份视空重建。
- 懒补向量 queueVectorBackfill 异步执行,失败 warn 不影响正确性。
章节来源
- agent/src/rag/searcher.js#L50-L77
- agent/src/rag/searcher.js#L99-L101
- agent/src/memory/service.js#L174-L212
- agent/src/memory/service.js#L214-L222
- agent/src/memory/service.js#L445-L458
- agent/src/memory/service.js#L555-L578
- agent/src/memory/service.js#L693-L712
查询语法与评分机制
查询语法说明
- 知识库检索(RAG):
- 输入:query(字符串)、collectionId(可选)、topK(整数)。
- 行为:向量检索 + 关键词命中增强 + 阈值过滤 + 旁路探测。
- 长期记忆检索(Memory):
- 输入:query(可选)、tags(数组)、memoryType(all/fact/summary)、limit(整数)。
- 行为:关键词通道必选;向量通道可选;RRF 融合;标签名次加成;会话去重。
结果评分机制
- 知识库检索:
- 基础分:原始余弦相似度([-1,1],不截断)。
- 关键词加分:每命中单元加分,保底分 KEYWORD_MIN_SCORE,封顶 SCORE_MAX。
- 最终分:max(向量分, 保底) + 单元加分,统一按 threshold 过滤。
- 长期记忆检索:
- 关键词分:字段权重 × 命中单元(封顶),整句原样命中额外加分;门槛 units ≥ 2 或 full。
- 语义分:纯 top-K 采信(相对落差 margin + 条数封顶 maxTake),不叠加绝对值。
- 融合分:RRF 仅消费名次,不叠加绝对相似度分数。
- 标签加成:按重叠比例上抬有限名次(TAG_RANK_LIFT)。
章节来源
- agent/src/rag/searcher.js#L17-L21
- agent/src/rag/searcher.js#L58-L77
- agent/src/memory/tokens.js#L14-L39
- agent/src/memory/tokens.js#L127-L158
- agent/src/memory/fusion.js#L24-L43
- agent/src/memory/fusion.js#L51-L60
- agent/src/memory/tags.js#L13-L17
标签过滤、元数据与权限控制
标签过滤机制
- 无 query:标签为唯一选择器,硬过滤。
- 有 query:标签不做硬过滤,仅做名次加成(applyTagRankLift)。
- 标签大小写/空白不敏感,重叠比例 = 命中标签数 / 请求标签总数。
元数据查询
- 记忆侧参与 embedding 与关键词匹配的字段面一致:content、title、tags。
- 知识库侧 metadata.text 用于关键词扫描与内容回填。
权限控制
- 记忆写入 origin 由调用方透传 ctx 决定,绝不从 args 读取,防止模型伪造。
- 注入模式检测:命中特定注入短语的内容直接拒写。
- 速率限制:同一 (origin:sessionId) 在进程存活期内写入条数上限。
章节来源
- agent/src/memory/tags.js#L13-L62
- agent/src/memory/tokens.js#L36-L39
- agent/src/rag/searcher.js#L107-L127
- agent/src/memory/service.js#L71-L98
- agent/src/memory/service.js#L270-L296
参数调优与召回精度平衡
相似度阈值设置
- 知识库检索:threshold 控制余弦分 + 关键词加分后的保留线;fallback 使用 threshold - 0.2 最多 3 条。
- 长期记忆检索:语义侧不再使用绝对地板,改为相对落差 SEMANTIC_MARGIN 与条数封顶 SEMANTIC_MAX_TAKE。
结果重排序与去重
- 知识库检索:关键词命中增强后统一排序并按 threshold 过滤。
- 长期记忆检索:RRF 融合后按 id 去重,再按 limit 截断;会话级去重优先新记忆。
大规模数据集查询优化
- 知识库检索:KEYWORD_SCAN_LIMIT 限制全量扫描 chunk 数,避免大库卡顿。
- 长期记忆检索:向量补齐失败冷却 60s;懒补向量异步执行;访问计数 RMW 重读真源防覆盖。
召回率与精度的平衡
- 关键词通道:通过 df 过滤抑制高频无区分度单元,提升精度;整句原样命中加分提升召回。
- 语义通道:纯 top-K 采信避免绝对阈值漂移,保证任意模型开箱可用;代价是无关查询可能返回至多 maxTake 条头部。
- 标签加成:在不破坏召回的前提下提升相关标签条目名次。
章节来源
- agent/src/rag/searcher.js#L17-L21
- agent/src/rag/searcher.js#L62-L68
- agent/src/memory/fusion.js#L24-L43
- agent/src/memory/service.js#L646-L691
- agent/src/memory/service.js#L555-L578
故障排查指南
- 向量维度不匹配:Searcher 会抛出 embeddingDimensionMismatch,需检查索引与模型配置是否一致。
- 嵌入超时:Memory Service 的 embedWithTimeout 会在 MEMORY_EMBED_TIMEOUT_MS 后降级到关键词单通道。
- 向量补齐失败:按 modelKey 冷却 60s,避免频繁重试;日志包含 backfill skipped 警告。
- 标签误伤:确认 query 是否存在;有 query 时标签不应硬过滤,应使用 applyTagRankLift。
- 会话重复召回:检查 sessionId 是否正确传入;sessionRecalledMemoryIds 会记录已召回 ID。
章节来源
- agent/src/rag/searcher.js#L50-L55
- agent/src/memory/service.js#L174-L187
- agent/src/memory/service.js#L555-L578
- agent/src/memory/tags.js#L49-L62
- agent/src/memory/service.js#L445-L458
结论
混合检索引擎通过知识库侧的“向量主路径 + 关键词增强 + 阈值过滤 + 旁路探测”与记忆侧的“关键词通道 + 可选语义通道 + RRF 融合 + 标签名次加成”形成互补。关键词匹配在两侧共享实现,确保口径一致;语义采信在记忆侧采用相对落差与条数封顶,避免绝对阈值漂移;标签在无 query 时硬过滤、在有 query 时名次加成,兼顾召回与精度。性能方面通过三阶段锁设计、向量补齐冷却、懒补向量与会话去重等手段保障高并发与大库场景下的稳定性与效率。