- 人工智能
- AI Agent
- Agent 记忆
- RAG
- MCP 服务
【免费下载链接】agent-memory
Memory 是一款面向 AI 智能体的长期记忆模块,为运行在 openJiuwen 框架上的智能体提供记忆提取、存储、检索与迁移能力。
本文围绕 agent-memory 检索链路中融合(Fusion)阶段的一次关键演进展开:新增CombMAX(
score_max)融合策略,在通道内做 max 归一化、通道间取最大值,同时为全部 Fuser 实现补齐分层召回的通道内归并(layered_merge),并将相对阈值出厂默认归零。读完本文,你将理解 RRF 对单通道候选的结构性压制问题、CombMAX 的数学原理与零参数特性、分层召回下"同通道多路"的正确归并口径,以及如何通过fuser.default与算子 params 在真实配置中启用并调优该策略——配套的源码位置、测试用例与配置示例均可在本仓库内直接对照查阅。
1. 背景:融合阶段为什么需要一次结构性调整
agent-memory 的检索链路(PipelineRetriever.retrieve)在并行多路召回之后、精排预算截断之前,需要把 keyword(词法)与 vector(语义)两路候选合并为一份统一排序,这就是融合(Fusion)阶段。该阶段由 jiuwen_memory/retrieval/fuser.py 定义的Fuser契约承担:Fuser.fuse(query, candidates)接收"每路一个列表"的候选,返回统一排序后的 top 候选。
F04-score-max-fusion.md 记录了融合阶段的一次完整决策过程。它从五个相互叠加的薄弱点出发,最终沉淀出四项决策:
| # | 薄弱点 | 后果 |
|---|---|---|
| 1 | RRF 对单通道候选的结构性压制 | 单通道命中者进入融合前 20 的比例仅 1.7% |
| 2 | 加法融合(CombSUM)同样偏好多通道命中 | 换归一化方式无法根治,进 top20 仅 +0.4pp |
| 3 | 固定参数的归一化不具备跨语料通用性 | BM25 绝对分数依赖语料规模与分析器实现 |
| 4 | 分层召回下同通道多路被当作多个信号源 | 计分重复累加、归一化基准按层分裂 |
| 5 | 相对阈值出厂默认偏严 | 与上述偏置叠加时误杀显著 |
下文依次展开这些问题的机理与对应决策。
2. 问题剖析:RRF 与加法的结构性偏好
2.1 RRF 按命中路数计分,压制单通道候选
RRF(Reciprocal Rank Fusion)按名次倒数计分:同一 unit 在每路里按其名次r贡献1/(k+r+1),跨路累加得融合分。默认k=60,实现在 rrf_fuser.py。
其结构性特征是:通道命中数量的权重大于通道内相似度。文档给出了两个极端对比:
- 双通道候选即使两路均处末位(rank 80+80),得分
2/140 ≈ 0.0143; - 单通道候选 rank 10,得分
1/70 ≈ 0.0143。
两者得分几乎相同——一个两路都勉强命中的弱候选,与一个单路第 10 名(相当强的命中)被排在同一个水平线上。离线重放的量化结果更触目惊心:被召回的 gold 中,单通道命中者进入融合前 20 的比例仅 1.7%。反事实验证进一步揭示决定因素:缺席通道若以 rank 80 命中,被截断的 gold 中 97% 可越过预算线——决定因素是"另一通道是否缺席",而非候选自身强度。
2.2 加法融合(CombSUM)是同一问题的温和版
替换为归一化分数加法(CombSUM)后实测未能改善:进 top20 由 RRF 的 66.7% 仅升至 67.1%。原因是加法同样偏好多通道命中,只是程度较轻:
- 两路在场时,单通道候选得分上限是
1/2; - 双通道候选上限是
1。
一个语义极强但字面未命中的候选,会被"除以通道数"结构性折价。这是 CombSUM 的固有性质,与归一化方式无关——文档明确记录:分别以 sigmoid、min-max、max 归一化实测,单通道 gold 进 top20 分别为 6.5%、14.8%、0%,均未解决。
2.3 固定参数归一化的死穴:BM25 绝对分数不可跨系统比较
加法方案配套的 BM25 sigmoid 归一化(1/(1+e^(−steepness×(BM25−midpoint))),参数按查询实词数分档)借鉴自 mem0utils/scoring.py,在本项目语料上实测严重错配:
| BM25 原始分 | 经 midpoint=7.0 归一化 | |
|---|---|---|
| p50 | 3.32 | 0.099 |
| p90 | 6.22 | 0.384 |
| p99 | 11.54 | 0.938 |
78.3% 的候选归一化后低于 0.2,中位数 0.099;而同批 vector 通道余弦中位数为 0.607。名义等权的两个通道,实际话语权约为0.16 : 1。
根因是:BM25 绝对分数依赖语料规模与平均文档长度(本项目 unit 中位长度 45 字符)、k1/b参数、IDF 变体与分析器实现。任何以固定阈值为中心的归一化都无法跨语料通用。此外该方案还需按查询实词数分档,而实词计数依赖英文停用词表,中文查询实际恒定落入最短档位,档位自适应机制失效。
2.4 分层召回:同通道多路被当作多个信号源
L0/L1 分层召回(详见 docs/features/common/F01-memory-layer.md)后,同一通道存在多个 recall 实例(L2 content / L0 概要 / L1 片段),而KeywordRecaller/VectorRecaller的channel()对三层返回同一值——layer是 recaller 上的独立属性,融合层不可见。后果分两类:
- 计分类融合(RRF/加法):重复累加同 unit 的多层命中;
- 归一化类融合:按层各自取基准——L0 若仅召回 2 条(最高分 3),其弱候选会被归一化到 1.0,与 L2 最强候选(最高分 20)同级。
两者都是索引覆盖差异,非相关性差异:unit 是否具备 layers 取决于LayerAnnotator是否为其生成分层(低于layers_threshold不生成)。设计文档 F01 §6.3 的原文是「同 unit_id 多路多层级命中聚合——同通道取 MaxP,跨通道累加」,既有实现同通道亦执行累加,偏离了设计。
2.5 相对阈值默认值放大偏置
F02 引入的相对阈值默认为min_score_ratio=0.6/min_score_ratio_uncalibrated=0.3,按当前最高分比例裁剪,与上述偏置叠加时误杀显著。
3. 决策 1:CombMAX 融合(score_max)
3.1 公式与两步式定义
combined(u) = max_c weight_c × ( score_c(u) / max_score_c )两步均在融合阶段完成,不改变召回:
- 通道内 max 归一化:每路以本次召回的最高分为 1.0,其余按比例折算。这消除了量纲差异(BM25 无界、余弦约 [0,1])而不引入阈值参数——"多少分算高"由本批数据自己给出,不依赖语料规模、BM25 实现或分析器。
- 通道间取最大值:候选取其在各通道归一化分中的最大值,而非求和。
weight_c默认全 1.0(fusion_channel_weights可选覆盖),用于人工压制或抬升某一路,且不参与归一化——归一化基准始终是该通道自身的最高分。
方法出处:CombMAX 与 CombSUM/CombMNZ 同出 Fox & Shaw (TREC-2, 1994)。后者在"多检索系统检索同一语料"的同质信号场景通常更优;而本项目是词法与语义的异质通道且候选集取并集,结论相反。
3.2 真实查询实例
文档给出了离线重放中的真实查询When did Jolene and her partner try scuba diving lessons?:
| 候选 | BM25 | cos | 加法融合分 | 名次 | CombMAX 分 | 名次 |
|---|---|---|---|---|---|---|
| 640bdbac | 20.35 | 0.735 | 0.866 | 1 | 1.000 | 1 |
| 0c1300a9 | 20.44 | — | 0.498 | 22 | 1.000 | 2 |
| 2adc945c(gold) | — | 0.711 | 0.356 | 38 | 0.967 | 3 |
| 6d7b20ca | 9.29 | 0.660 | 0.598 | 12 | 0.898 | 4 |
gold 是全场语义第二强(0.711,最高 0.735),因 keyword 未命中,加法下得(0.711+0)/2 = 0.356,被两路均弱于它的6d7b20ca((0.5+0.66)/2 = 0.598)反超十余位。CombMAX 下 gold 的语义强度被完整保留,跃升至第 3 位。
3.3 源码实现:ScoreMaxFuser
实现在 score_max_fuser.py,核心逻辑为:
merged = merge_layered_channels(candidates) # 先归并,见决策 2 for one_channel in merged: ch = one_channel[0].channel weight = self._channel_weights.get(ch, 1.0) top = max(su.score for su in one_channel) for rank, su in enumerate(one_channel): # 通道内最高分记作 1.0;非正的最高分(全零/负分通道)整路计 0, # 避免除零并保持"无有效信号即不贡献"的语义。 normalized = (su.score / top) if top > 0 else 0.0 contribution = weight * normalized if contribution > best.get(su.unit_id, -1.0): best[su.unit_id] = contribution # 通道间取最大,而非求和实现细节值得注意:
- 零参数特性:
ScoreMaxFuser.__init__只接受可选的channel_weights,类注释明言"零参数"——不需要 RRF 的k,也不需要任何归一化基准参数。 - 非正分通道防除零:
top > 0时整路归一化计 0,保持"无有效信号即不贡献"的语义,对应单测test_score_max_handles_non_positive_channel。 - 证据保留:每个候选的
evidence记录全部通道的ChannelEvidence(channel, rank, score, weight, contribution),供披露与轨迹解释(explain()返回strategy=score_max、normalization=channel_max与通道权重)。 - 注册机制:实现通过
@FuserProducer.register("score_max")自注册,装配无需改动 producer 或 build_kernel;fuser_impl/__init__.py的import_optional触发注册。
4. 决策 2:分层召回的通道内归并(layered_merge)
4.1 统一前处理
融合前统一做一次前处理:同一通道的多路分层结果按 unit 取最高分(MaxP),归并为一路并按分数重排。该策略明确以「分越大越相关」为链路不变量;L0/L1/L2 必须使用同类后端与同一分词/度量配置。VectorRecaller在装配期拒绝 L2 等 lower-is-better 度量,不在归并阶段自动反转或换算分数。
merge_layered_channels(candidates) -> list[list[ScoredUnit]]实现在 layered_merge.py,按通道分桶、同unit_id取最高分、每桶按分数降序输出:
buckets: dict[RecallChannel, dict[str, ScoredCandidate]] = {} for one_channel in candidates: if not one_channel: continue bucket = buckets.setdefault(one_channel[0].channel, {}) for su in one_channel: current = bucket.get(su.unit_id) if current is None or su.score > current.score: bucket[su.unit_id] = su return [sorted(bucket.values(), key=lambda su: su.score, reverse=True) for bucket in buckets.values()]4.2 三个 Fuser 共用,归并对 CombMAX 不可省略
该函数由全部 Fuser 实现共用——RRFFuser、WeightedRRFFuser、ScoreMaxFuser、BM25ScoredFuser在fuse()入口处统一调用它。分层是链路级变化,不应由单一融合策略各自处理。
对 CombMAX 而言归并不可省略:它消除的不仅是重复计数,更是归一化基准按层分裂的问题——L0 若只召回 2 条(最高分 3),其弱候选会被归一化到 1.0,与 L2 最强候选(最高分 20)同级。因此执行顺序必须是「归并 → 归一化 → 取最大值」。
恒等性保证:未启用分层时每通道只有一个列表,归并为恒等变换,融合行为不变。这保证了该改动对既有(无分层)部署零影响。
对应测试:tests/unit/retrieval/test_fuser.py中的test_score_max_merges_layers_before_normalizing(L0 单条弱候选不会被抬到 1.0)与test_score_max_ignores_layer_multiplicity(多层命中不产生额外增益),以及 RRF 家族的test_rrf_fuser_merges_same_channel_layers_before_scoring。
5. 决策 3:不引入通道保送机制
加法方案曾配套「各通道 top-K 若未进入融合序前 N 位则提入窗口」的保送机制。CombMAX 下实测无效——每个通道的 top-1 归一化必然为 1.0,天然位于前排:
| 进 top50 | 进 top20 | |
|---|---|---|
| CombMAX 无保送 | 1995 (86.6%) | 1645 (71.4%) |
| CombMAX + 保送 10 | 1992 (86.5%) | 1645 (71.4%) |
故不引入,减少一个待调参数。
6. 决策 4:相对阈值出厂默认归零
min_score_ratio与min_score_ratio_uncalibrated出厂默认统一改为0.0。按最高分比例裁剪会随融合分布变化误杀尾部候选(分层召回下尤甚——有无 layers 属索引覆盖差异而非相关性差异),裁剪职责交由调用方top_k;需要时显式配置。
F02 引入的阈值机制本身保留:apply_threshold阶段、min_score绝对门、min_results回填不变,仅调整默认值。四处默认值必须同步:
- defaults.py 中
retriever.default.params(含min_score_ratio: 0.0、min_score_ratio_uncalibrated: 0.0,注释明确"相对阈值默认关闭"); PipelineRetriever.__init__签名默认值(pipeline_retriever.py);_build的cfg_get回退值(漏配时回退到出厂行为而非静默关闭防护);- 对应测试锁定。
calibrated由是否执行 rerank 决定(PipelineRetriever中calibrated=reranked),非由分数是否归一化决定。apply_threshold的实现里,绝对阈值仅在校准(已精排)路径生效,相对阈值分路取默认,未校准路径取min_score_ratio_uncalibrated。
对应集成测试覆盖新默认行为:test_pipeline_retriever.py 中的test_direct_constructor_threshold_off_by_default与test_default_config_threshold_off_end_to_end锁定默认 0 值下不裁剪的行为;原依赖出厂默认的用例改为显式传参(test_configured_calibrated_threshold_active/test_configured_uncalibrated_threshold_active),保留阈值机制本身的覆盖。
7. 配置指南:如何启用 CombMAX
7.1 融合器选型
融合器选型在命名空间实例 target(非 params):fuser.default出厂为rrf;需启用 CombMAX 时显式配:
fuser: default: score_max三个实现共享同一Fuser契约与分层归并前处理。出厂默认保持 RRF 有专门测试锁定:test_fuser.py::test_default_config_uses_rrf_fuser断言默认装配产出的fuser是RRFFuser实例。
7.2 算子实例 params(非 globals)
| 键 | 默认 | 说明 |
|---|---|---|
fusion_channel_weights | {}(全 1.0) | 通道权重,用于人工压制/抬升某一路 |
min_score_ratio | 0.0 | 校准路径(走 rerank)相对阈值 |
min_score_ratio_uncalibrated | 0.0 | 未校准路径相对阈值 |
带权重压制的完整示例:
fuser: default: target: score_max params: fusion_channel_weights: keyword: "0.5" # 压制词法通道:其归一化 1.0 被折为 0.5 vector: "1.0" # 语义通道保持全权重通道权重键支持RecallChannel成员值("vector")或成员名的大小写变体("VECTOR"/"Vector")。解析逻辑在 fuser.py 的to_recall_channel/normalize_channel_weights:先按成员值精确匹配,再回退按成员名匹配,两者皆失败抛携带合法清单的ValidationError(而非裸KeyError);非字符串键同样兜底。对应测试覆盖大小写变体与非法通道名(test_invalid_channel_name_raises_validation_error等)。
权重生效的单测示例:test_score_max_can_be_created_from_config中keyword=0.5使其归一化 1.0 折为 0.5,排序落在 vector 之后。
7.3 相对阈值显式开启
默认归零后若确需按比例裁剪,在retriever的 params 下显式配置:
retriever: default: target: pipeline params: min_score_ratio: 0.6 # 校准路径:低于最高分 60% 的候选被裁剪 min_score_ratio_uncalibrated: 0.3 # 未精排路径走较松的相对阈值注意:实例级 params 覆盖是整体替换,漏写键会回退到_build的cfg_get回退值(出厂行为),这正是决策 4 强调三处默认值一致的原因。
8. 编排位置:CombMAX 在检索链路中的落点
PipelineRetriever.retrieve中融合阶段前后的完整编排(pipeline_retriever.py):
并行召回(分层开启时同通道多实例) ← 不改动 ↓ Fuser.fuse ├─ merge_layered_channels:同通道归并 ← 决策 2 ├─ 通道内 max 归一化 ← 决策 1 └─ 通道间取最大值 ← 决策 1 ↓ 截断至精排预算 → 点读复核 → 可选精排 → apply_threshold ← 决策 4 调整其默认值 ↓ top_k 截断 → 披露源码层面的对应关系:
- 召回超采样宽度
recall_k = max(top_k × over_fetch_factor, over_fetch_floor),受recall_max硬上限封顶; - 精排预算
budget_n = max(rerank_max, top_k),永不欠 top_k; fused = self._fuser.fuse(parsed, [batch.candidates for batch in materialized.batches]),随后survivors = list(fused[:budget_n]);apply_threshold(survivors, top_k, calibrated=reranked, ...)由reranked布尔值决定走校准或未校准路径。
轨迹(trajectory)中的fuse步骤会通过explain()记录融合策略明细(如strategy=score_max、normalization=channel_max、channel_weights),使融合行为可观测、可审计。集成测试test_trajectory_records_stages_and_cost验证了fuse.detail["strategy"] == "rrf"等轨迹内容。
9. 关联改动:keyword 通道词法归一化(text_analyzer)
融合策略要真正生效,keyword 通道的分数质量同样关键。ElasticsearchFulltextStore新增text_analyzer参数(elasticsearch_fulltext.py),在线配置的 L0/L1/L2 三个独立 index 统一启用english(词干化 + 去停用词)。
此前 ES text 字段未配 analyzer(standard:不词干化、不去停用词),导致attended无法匹配attend,通用疑问词参与打分。离线重放中该改动使 346 条 vector-only 被截断单元里的102 条获得 keyword 命中。
在 deploy/docker/online/config.yml 中,三层 fulltext index 与三层 vector collection 分别落独立实例:
fulltext_store: default: target: elasticsearch params: hosts: "${ES_HOSTS:-http://elasticsearch:9200}" index: "${ES_INDEX:-agent_memory_fulltext}" # 建索引期文本分析器,决定 BM25 匹配的词条形态(查询侧自动同规则)。 text_analyzer: "${ES_TEXT_ANALYZER:-english}" layers_l0: target: elasticsearch params: index: "${ES_INDEX_L0:-agent_memory_fulltext_l0}" text_analyzer: "${ES_TEXT_ANALYZER:-english}" layers_l1: target: elasticsearch params: index: "${ES_INDEX_L1:-agent_memory_fulltext_l1}" text_analyzer: "${ES_TEXT_ANALYZER:-english}" vector_store: default: # Milvus collection: agent_memory_vectors, dim 1024, COSINE layers_l0: # collection: agent_memory_vectors_l0, dim 1024, COSINE layers_l1: # collection: agent_memory_vectors_l1, dim 1024, COSINEanalyzer 选择原则(配置注释与源码 docstring 一致):
- 英文记忆:
english(ES 内置,词干化attended→attend+ 去停用词); - 中文记忆:
ik_max_word(需 ES 安装 analysis-ik 插件,中文分词效果最好);无法装插件时用cjk(ES 内置,CJK 二元切分,优于 standard 的单字切分); - 中英混合:
ik_max_word(保留完整英文单词,但无英文词干化);或置空回退standard(即本参数引入前的行为)。
重要限制:analyzer 仅在索引创建时生效(_ensure_index中写入 text 字段的 mapping),变更后需删除或更换索引重建才能应用。查询侧用同一 analyzer 分析(_analyze_query调用indices.analyze并用内部停用词表过滤),保证索引与查询词条形态一致。对应测试锁定 mapping 写入:test_elasticsearch_fulltext.py 的test_text_analyzer_is_written_to_index_mapping断言text字段 mapping 为{"type": "text", "analyzer": "english"}。
10. 拒绝的方案与理由
文档完整归档了候选方案被否决的实验依据,这保证了最终决策的可追溯性:
| 方案 | 结论 | 依据 |
|---|---|---|
| 归一化分数加法(CombSUM) | 否决 | 相对 RRF 进 top20 仅 +0.4pp;加法固有的多通道偏好无法通过更换归一化消除 |
| BM25 sigmoid 固定参数归一化(mem0 方案) | 否决 | 与本项目分数分布错配(两通道实际话语权 0.16:1);参数随语料/后端变化;实词分档依赖英文停用词表,中文失效 |
| min-max 归一化 | 否决 | 进 top50 由 85.5% 降至 77.9%;将批内最弱候选强制归零,抹去"弱但有效匹配"与"完全不匹配"的区别 |
| CombMAX + 弱共识加成(次高分 ×0.15) | 暂缓 | gold@20 略高 0.9pp,但单通道 gold@20 由 37.4% 降至 26.0%,且重新引入一个待调参数 |
| 通道 top-K 保送 | 否决 | CombMAX 下实测无效(见决策 3) |
| 放宽精排预算到全池(50 → 127) | 否决 | 被截断候选在通道内排名亦深(75% rank > 30),纯扩池需 rerank 成本 ×2.5。注:小幅上调rerank_max50→60 已采纳——top50→top65 回收约 5–6pp,成本仅 ×1.2,见 F02-retrieval-threshold-topk-design.md 文末「精排预算调整」注记 |
| 语义路定义候选全集、keyword 仅加分(mem0 架构) | 否决 | 该架构下 92 条 keyword-only gold 将被完全排除出候选集 |
| 应用层 spaCy lemmatization | 否决 | 后端仅 ES,引擎级 analyzer 零依赖且一致性由 ES 保证;spaCy 对中文近似恒等 |
| LLM 生成同义词入索引 | 不采纳 | 产品对齐约束;同义转述差异因此成为已知上界 |
| 句级机械切块(RecursiveChunker@100 + MaxP) | 实验否决 | 切片丢失主语与指代,合并单元排名由 17 恶化至 29 |
值得注意的是,「放宽精排预算到全池」与「小幅上调rerank_max50→60」是两件不同的事:前者被否决,后者被采纳。这体现了调参决策中"成本收益比"的精细权衡。
11. 验证:离线重放与三层测试基线
11.1 离线重放(LoCoMo 10-sample 基线日志)
数据来源为 2618 条 gold(其中 2303 条被召回覆盖);三方案的召回结果完全相同——融合不改变召回,指标差异纯粹来自排序:
| 方案 | 进 top50 | 进 top20 | 单通道 gold 进 top20 |
|---|---|---|---|
| RRF(当前默认) | 1936 (84.1%) | 1536 (66.7%) | 11 (1.7%) |
| 归一化加法 + 保送 | 1969 (85.5%) | 1545 (67.1%) | 42 (6.5%) |
| CombMAX | 1995 (86.6%) | 1645 (71.4%) | 242 (37.4%) |
相对 RRF:进 top20 +109 条(+4.7pp),进 top50 +59 条(+2.5pp),单通道 gold 进 top20 +231 条。逐 sample 检查 10 个中 9 个 top20 不回归,唯一回归样本为 −0.7pp(约 2 条)。该批验证跑在 standard analyzer 的基线 run 上,方案不依赖特定分析器。
11.2 单元测试
tests/unit/retrieval/test_fuser.py 覆盖:
- 通道内 max 归一化基准(
test_score_max_normalizes_within_channel_by_top_score); - 单通道候选不被折价(
test_score_max_does_not_penalize_single_channel_candidate,复现 2.2 节真实场景); - 双通道取最大而非求和(
test_score_max_takes_best_channel_not_sum); - 分层先归并后归一化(
test_score_max_merges_layers_before_normalizing); - 多层命中无额外增益(
test_score_max_ignores_layer_multiplicity); - 非正分通道防除零(
test_score_max_handles_non_positive_channel); - 配置装配与通道权重(
test_score_max_can_be_created_from_config); - 通道名解析的变体与非法输入兜底(
test_score_max_channel_name_variants_accepted等)。
11.3 集成与配置/存储测试
- tests/integration/retrieval/test_pipeline_retriever.py:阈值默认值变更后,原依赖出厂默认的用例改为显式配置(保留阈值机制覆盖),另增用例锁定新默认下不裁剪的行为(
test_direct_constructor_threshold_off_by_default、test_default_config_threshold_off_end_to_end),并有显式开启相对阈值的端到端用例(test_configured_threshold_active_end_to_end)。 - tests/unit/config/test_online_profile.py:锁定 RRF 默认与 L0/L1/L2 持久化分表。
- tests/unit/storage/fulltext_impl/test_elasticsearch_fulltext.py:锁定
text_analyzer写入 ES mapping。
文档同时给出测试基线命令:
uv run --frozen pytest tests/unit/retrieval tests/unit/config/test_online_profile.py tests/unit/storage/fulltext_impl/test_elasticsearch_fulltext.py tests/integration/retrieval -q11.4 指标的边界说明
上述均为融合层指标,未经过 recheck、rerank 与 answerer。融合层收益向端到端答对率的传导需在线验证——此前加法方案曾出现"进池提升但 QA 层被 answerer 噪声淹没"的情况。另外,分层归并未纳入该批实验:实验时LayerAnnotator未装配,unit.layers为空,分层链路空转;归并的恒等性保证其不改变该批数据的结论。
12. 已知局限
文档如实记录了三项局限,体现设计的诚实边界:
- 通道权重未经调优。默认全 1.0,未验证词法与语义在本场景下是否应等权。
- CombMAX 不奖励多通道共识。两路均命中在理论上是更强的相关性证据,本方案不予额外加分。实测该信号的价值低于单通道候选被压制的损失,但这是数据集相关的结论。
- 仅在英文 LoCoMo 上验证。max 归一化本身不含语言相关假设(这正是相对 sigmoid 方案的改进点),但中文语料的实际收益仍需实测。
13. 延伸:BM25_scored_fuser——CombMAX 家族的同构扩展
从源码结构看,bm25_scored_fuser.py 是score_max的工程化延伸,注册名BM25_scored_fuser。它与score_max同构(通道内 max 归一化 + 通道间取最大),但额外在候选并集上补一路统一的词法信号:
combined(u) = max( max_c weight_c × norm_c(u), w_lex × norm_bm25(u) )其动机是:ES 的 keyword 通道分已是带全库统计的 LuceneBM25Similarity,重估它只会更差——本算子不重估,只补一路:向量通道召回的候选从未被词法打过分,融合时"有没有词法分"变成"是哪一路召回的"的副产品。该算子在并集上统一算一遍 Okapi BM25(IDF 与 avgdl 取自候选池而非全库,是池内相对强度),把这条轴补齐,各通道原分一律保留只增不减。参数bm25_k1(默认 1.2)、bm25_b(默认 0.75)、bm25_lexical_weight(默认 1.0,置 0 退化为score_max)。
14. 相关文档与接口契约
- 融合阶段整体架构:
docs/specs/S04-retrieval.md(检索接口契约) - 阈值与候选预算既有设计:docs/features/retrieval/F02-retrieval-threshold-topk-design.md
- 分层召回整体设计:docs/features/common/F01-memory-layer.md §6
- 检索实现设计:
docs/features/retrieval/F01-retrieval-impl-design.md - 检索模块开发约定:
jiuwen_memory/retrieval/AGENTS.md
一句话总结:CombMAX 用"通道内以本批最高分自归一化 + 通道间取最大值"替代 RRF 的名次计数与加法的通道平均,以零新增参数的成本消除了单通道强候选的结构性压制;layered_merge为全部 Fuser 统一补齐分层召回的正确归并口径;相对阈值默认归零把裁剪职责交还给调用方top_k。三者共同构成了 agent-memory 融合阶段"数据自校准、不引入跨语料假设"的决策主线。
- 人工智能
- AI Agent
- Agent 记忆
- RAG
- MCP 服务
【免费下载链接】agent-memory
Memory 是一款面向 AI 智能体的长期记忆模块,为运行在 openJiuwen 框架上的智能体提供记忆提取、存储、检索与迁移能力。
相关推荐
Apex FusedLayerNorm 与 FusedRMSNorm 完全指南:CUDA 融合归一化层的原理、API 与实战
Apex FusedLayerNorm 与 FusedRMSNorm 完全指南:CUDA 融合归一化层的原理、API 与实战 本文围绕 Apex 官方文档 do
人工智能深度学习分布式训练模型优化CUTLASS层归一化:37GEMM层归一化融合
CUTLASS层归一化:37GEMM层归一化融合 概述 还在为深度学习模型中GEMM(通用矩阵乘法)和LayerNorm(层归一化)操作的分离计算而烦恼吗?CU
算子库高性能计算Parabolic 免费完整指南:图形界面视频下载
Parabolic 免费完整指南:图形界面视频下载 Parabolic 是一个开源免费的图形界面工具,本质上是命令行程序 yt dlp 的可视化外壳。它帮你把数
桌面应用音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考