首页
/ Mem0 Pi Agent 插件的 search 技能:紧凑语义记忆检索与 Memory ID 直查实现解析

Mem0 Pi Agent 插件的 search 技能:紧凑语义记忆检索与 Memory ID 直查实现解析

2026-09-04 12:39:21作者:江焘钦

本文以 search 技能定义 为主体,讲解 Mem0 为 Pi Agent 提供的 search(Search / Peek)技能如何工作:它如何解析用户查询、识别 Memory ID、调用 mem0_memory 工具完成语义检索,并以紧凑单行格式输出结果。结合插件 源码实现范围解析单元测试,读完本文你将掌握该技能的完整执行链路、检索参数与 scope 过滤机制,并能复现一套可被 Agent 自主调用的轻量记忆检索方案。

技能定位:介于"随手一查"与"全量浏览"之间

search 技能是 pi-agent-plugin 提供的 8 个技能之一,其 frontmatter 中声明的用途是:

Searches memories and displays compact one-liner results, or looks up a specific memory by ID. Use for quick memory lookups, checking if something was recorded, resolving [mem0:id] citations, or browsing memories without full category detail.

它与 tour 技能形成明确的分工:tourformatMemoryList + groupByCategory 按类别分组做完整走查,而 search 是"Lighter than /mem0-tour"的轻量路径——只针对一条查询返回紧凑单行结果,不展开完整类别明细。它的四类典型使用场景是:

  • 快速记忆查询(quick memory lookups);
  • 确认某件事是否被记录过(checking if something was recorded);
  • 解析 [mem0:id] 引用——即前文检索结果中携带的记忆 ID 标记;
  • 轻量浏览记忆,不需要按类别展开全部细节。

完整执行流程

Step 1:解析查询

用户以 /mem0-search <query> 的形式提供搜索词,例如 /mem0-search favorite restaurants。若未提供查询,技能要求反问:"What should I search for?"——这一点与斜杠命令实现一致:/mem0-search 命令在 commands.ts 中对空参数直接提示 Usage: /mem0-search <query> 并返回。

Memory ID 识别:如果查询匹配 UUID 模式(^[a-f0-9-]{20,}$),则将其视为一次按 ID 的直接定位,而不是语义搜索。这一判断是纯文本层的正则匹配,不产生额外 API 调用。

需要注意一个实现细节:ToolParams 中定义的动作只有 search | add | get_all | update | delete | delete_all 六种,并不存在独立的 get by id 动作。从源码结构看,"按 ID 直查"在技能层面复用 action="search" 的调用路径,由 Agent 将 ID 作为 query 传入同一工具;而斜杠命令侧则被测试明确约束为"即使是十六进制样式的字符串也走语义搜索"(见 commands.test.tsuses semantic search even for hex-looking strings 用例,并断言 mem0.get 未被调用)。两条路径对 ID 型输入的意图解读不同,这正是"技能引导 Agent"与"命令直接执行"两种模式的差异所在。

Step 2:执行搜索

技能指示 Agent 调用 mem0_memory 工具,参数为 action="search"query=<用户查询>。在 tools.ts 中,该分支的执行逻辑是:

case "search": {
  if (signal?.aborted) throw new Error("Cancelled");
  if (!params.query) throw new Error("query is required for search");
  const filters = resolveSearchFilters(scope, scopeCtx);
  const result = await mem0.search(params.query, { filters });
  const memories = result.results ?? [];
  return {
    content: [{ type: "text" as const, text: truncateOutput(formatMemoryList(memories)) }],
    details: { matchCount: memories.length },
  };
}

关键行为有三点:

  1. query 必填,缺失时直接抛错,防止 Agent 发起无效检索;
  2. 检索前通过 resolveSearchFilters 生成 scope 过滤条件(见下文"scope 与过滤条件"一节);
  3. 结果统一经 formatMemoryList 格式化,并经 truncateOutput 截断——上限为 200 行 / 50KBMAX_OUTPUT_LINES / MAX_OUTPUT_BYTES),超限后会追加 [Output truncated: showing N of M lines] 提示,避免长结果撑爆 Agent 上下文。

Step 3:紧凑格式展示

技能规定的展示格式是每行一条的单行结构:

## mem0 search: "<query>" (<N> results)

1. [preferences] Prefers window seats on flights (2026-05-15) [mem0:a3f8b2c1]
2. [goals] Wants to visit Japan in 2027 (2026-05-10) [mem0:7e2d9f4a]
3. [identity] Lives in San Francisco (2026-05-08) [mem0:c4d5e6f7]

单行格式定义为:

<number>. [<category>] <content, 80 chars> (<date>) [mem0:<short_id>]

若查询无命中,输出固定的一句话:

No memories matching "<query>".

值得对照的是工具层的原始输出格式。formatMemoryCompact 生成的是:

const cat = mem.categories?.[0] ?? "uncategorized";
const age = mem.createdAt ? ` (${formatAge(mem.createdAt)})` : "";
return `[${cat}] ${mem.memory ?? "(empty)"}${age} [mem0:${mem.id}]`;

[类别] 全文 (相对时间) [mem0:完整ID],其中时间由 formatAge 渲染为 3m ago / 5h ago / 12d ago 这样的相对时间,且空结果时返回 "No memories found."。对比之下可以发现:技能要求的是展示层二次格式化——Agent 拿到工具输出后,将内容压缩到 80 字符、把 ID 缩略为 short_id、并以日期形式呈现。也就是说,[mem0:<short_id>] 这类紧凑引用是技能约定,而非工具直接产出的字段。

[preferences][goals][identity] 这些类别标签并非随意选取,它们对应 types.ts 中定义的 10 个默认分类identitypreferencesgoalsprojectsdecisionstechnicalrelationshipsroutineslessonswork。记忆写入时通过 customCategories 传入这组分类定义(见 tools.ts 的 add 分支),检索结果中的 categories[0] 即取自同一命名空间。

scope 与过滤条件:project / session / global

search 技能调用时若未显式传 scope,则回落到配置的 defaultScopebuildToolExecuteconst scope = params.scope ?? defaultScope),默认为 project。三种 scope 到 mem0 服务端过滤条件的映射在 scoping.ts 中定义:

Scope filters 字段 语义
project(默认) { user_id, app_id } 当前项目记忆池,app_id 取 git 仓库根目录名
session { user_id, app_id, run_id } 仅本次会话,run_id 为会话文件 SHA-256 前 12 位
global { user_id, app_id: "*" } 跨所有项目

两个上下文字段的推导方式同样值得注意(detectAppId / detectRunId):

  • app_id 通过 git rev-parse --show-toplevel 取仓库根的 basename,因此 monorepo 下所有子目录共享同一记忆池,检测失败时回退为当前目录名;
  • run_id 是会话文件路径的 sha256 前 12 位,无会话文件时为 "unknown"

工具注册时还通过 promptGuidelines 明确了 scope 使用纪律:常规查询不要传 scope(自动落到项目默认值),只有用户明确要求跨项目回忆时才使用 global——这与 search 技能"轻量、聚焦当前上下文"的定位一脉相承。

检索参数:阈值、topK 与重排序

search 技能背后实际调用的 mem0 检索能力,在不同入口暴露了不同的参数组合:

斜杠命令入口(用户手动执行 /mem0-search)在 commands.ts 的 searchMemories 中固定携带完整参数:

const SEARCH_TOP_K = 10;
...
const result = await mem0.search(query, {
  filters,
  threshold: config.searchThreshold,  // 默认 0.3
  topK: SEARCH_TOP_K,                 // 固定 10
  rerank: true,
});

其中 searchThresholdconfig/index.ts 中默认为 0.3,可在 ~/.pi/agent/mem0-config.json 中覆盖(如 {"apiKey": "...", "searchThreshold": 0.2, ...}),MEM0_API_KEYMEM0_USER_ID 环境变量优先级更高。它的语义是相似度下限(0–1):没有足够相似的记忆时报告"无匹配",而不是返回最接近的无关记忆——调高更严格,调低则放宽。

Agent 工具入口(即 search 技能实际走的路径)在 tools.ts 的 search 分支 中只透传了 filters,未附带 threshold / topK / rerank,相关策略由服务端默认处理。从源码结构看,这一差异是有意为之:命令路径服务于人类一次性交互,需要客户端可控的相关性门槛;工具路径服务于 Agent 高频、多轮、多措辞的主动检索(工具描述中明确建议 "run multiple searches with different phrasings for multi-part questions"),把过滤权交给服务端默认行为更合适。

测试对行为的约束

commands.test.ts 中的 /mem0-search 用例组为这条链路提供了可验证的行为契约:

  • performs server-side semantic search with a relevance threshold:断言 mem0.search{ threshold: 0.3, topK: 10, rerank: true } 被调用,并确认结果通过 customType: "mem0-search" 反馈消息发出;
  • uses semantic search even for hex-looking strings:输入 abcd1234 这类十六进制样式字符串时,命令路径仍然执行语义搜索,mem0.getAllmem0.get 均不被调用;
  • shows a no-matches message naming the query:空结果时消息包含 No matches 并带上原始查询词,与技能定义的 No memories matching "<query>". 输出约定一致;
  • shows all results the API returns (relevance gating is server-side):客户端不做本地分数过滤,API 返回多少条就展示多少条——相关性门控完全在服务端完成。

此外,每次搜索调用都会经 telemetry 记录 result_count(命令路径)或 latency_ms + matchCount(工具路径,见 execute 中的 captureToolEvent),用于观测检索质量与延迟。

小结

search 技能把一次记忆检索拆解为"解析 → 检索 → 紧凑呈现"三步,用一行 [类别] 内容 (日期) [mem0:id] 的格式在保证信息密度的同时压低上下文开销,并以 [mem0:<short_id>] 标记为后续的 ID 直查、/mem0-pin/mem0-forget 等操作留下引用锚点。其底层由 mem0_memory 工具的 search 动作支撑,配合 scope 过滤、输出截断与遥测埋点,构成了一条从 Agent 意图到 mem0 服务端检索的完整可验证链路——理解这条链路,也就理解了 pi-agent-plugin 中"轻量检索"这一能力面的全部实现细节。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384