ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Crabtalk会话搜索实战:基于BM25的search_sessions工具与窗口化摘录实现

Crabtalk会话搜索实战:基于BM25的search_sessions工具与窗口化摘录实现

【免费下载链接】crabtalk

The long-running embedded agent

项目地址:https://gitcode.com/gh_mirrors/cr/crabtalk
点击查看免费下载

🦀 Crabtalk 是一个长期运行的嵌入式 agent 守护进程(Agent daemon),而会话搜索(Session Search)正是它"记得住过去"的核心能力:内置的search_sessions工具基于BM25算法对所有历史会话消息建立倒排索引,按关键词返回窗口化摘录(windowed excerpts)——命中的那条消息加上它前后若干条上下文,而不是整个会话全文。

这篇文章带你从原理到实战,完整看懂这套会话搜索是如何实现的。

🎯 会话搜索解决了什么问题?

Agent 与用户之间的对话会持续沉淀。当用户问"我们上周关于 cron 重构是怎么决定的?"时,Agent 需要一种手段去翻出那次对话——而不是一遍遍全量加载会话(既慢又撑爆上下文窗口)。

早期的方案是给会话打标签、切分 topic,但实践中"打标签"和"找内容"是两回事:标签命名会漂移(cron refactorvscron cleanup),还得靠 Agent 自觉维护。Crabtalk 的最终结论是:内容即索引——直接对消息文本建 BM25 倒排索引,用查询词去召回真正讨论过相关内容的会话,无需任何预先分类(设计动机详见 0185-session-search.md)。

💡 一句话概括:search_memory搜"沉淀下来的记忆条目",search_sessions搜"发生过的对话",两者互补,各司其职。

🧩 工具参数一览:调用 search_sessions 需要知道什么

search_sessions作为 Agent 工具暴露给 LLM,参数定义在 search.rs:

参数类型默认值说明
querystring必填要匹配的关键词或短语
limitusize5返回的会话命中数上限
context_beforeusize4每条命中消息之前包含几条消息
context_afterusize4每条命中消息之后包含几条消息
agentstring无只搜指定 agent 的会话
senderstring无只搜指定发送者发起的会话

官方工具提示词 sessions.md 给 Agent 的使用建议也很实用:先用用户自己的措辞查询,查询词保持 2~6 个单词;命中后如果上下文不够,再加大context_before/context_after。

🔎 BM25 打分:一条消息凭什么排得更靠前

底层是一个零外部依赖、手写实现的小型 BM25 倒排索引,位于 bm25.rs,与search_memory共用同一份代码:

  • IDF 公式采用 Lucene 变体:ln((n - df + 0.5)/(df + 0.5) + 1.0),+1.0保证常见词的 IDF 不为负(bm25.rs)
  • K1 = 1.2,B = 0.75,即短消息中出现关键词会被更明显地"奖励"
  • 分词器按非字母数字字符切分、小写化、过滤单字符词与约 60 个英文停用词(bm25.rs)

在原始 BM25 分数之上,还有两层"加权"让排序更符合直觉:

① 角色权重(index.rs)——同一句话,谁说的更有"分量":

消息角色权重设计含义
用户消息1.5×用户诉求最值得关注
工具调用(assistant 发起)1.3×说明"动手做了解决",是方案落地的信号
助手普通回复1.0×基准
系统消息0.5×降权

② 会话级字段加成——标题和摘要参与二次打分(index.rs):

  • summary(compact 压缩时生成的会话摘要)命中查询词 →3.0×加成
  • title(会话标题)命中查询词 →2.0×加成

所以"标题里就写着 rollback 的会话"会稳稳排在前面,哪怕正文只提过一次。这一点由测试 sessions_search.rs 明确保证。

🪟 窗口化摘录:为什么返回的永远只是"一小段"

搜索最大的敌人是失控的输出:消息里可能藏着大段工具结果、文件内容,返回原始Message对象会瞬间撑爆上下文窗口。因此命中结果被投射成固定小形状SessionHit(hit.rs),并有三条硬性上限(hit.rs):

常量值含义
MAX_SNIPPET_BYTES1024单条摘录最大字节数,超长截断并标记truncated
MAX_WINDOW_ITEMS16单个命中最多携带的消息条数
MAX_HITS_PER_QUERY20单次查询最多返回的会话数

窗口构建逻辑在 index.rs:以命中消息为锚点向前取context_before条、向后取context_after条;如果超出 16 条上限,会从两端等量裁切、保留锚点居中。

另一个关键设计是每个会话最多返回一条命中(best-hit-per-session,index.rs)——避免某个超长会话刷屏霸占整个结果列表。想看某个会话的完整内容?用分页的list_messages去钻取,搜索接口刻意不提供"加载整个会话"的原语。

工具返回给 Agent 的最终文本格式(search.rs)长这样,结构清晰、一眼可读:

## cron refactor session: crab_tester_1 · agent: crab · sender: tester updated: 2026-04-25T00:00:01Z · matched message #12 - [user #10] 上次部署超时的问题还在吗 - [tool-call:shell #11] shell({"command": "cron list"}) - [user #12] planning the cron refactor ← 命中

🔒 安全细节:哪些内容永远不会进索引

这是整个实现里最值得细看的一节(index.rs)。索引只收录可检索文本extract_indexable_text,规则是:

内容类型是否进入 BM25 索引为什么
用户 / 助手消息正文✅核心检索对象
工具调用函数名(如shell)✅支持"找我上次运行 shell 的会话"这类查询
工具调用参数、工具返回结果❌常含凭据、文件内容等敏感信息,不能被关键词搜出来
自动注入条目(环境块、记忆块等)❌系统产物,非真实对话

对应测试 sessions_search.rs 验证:一段包含Authorization: Bearer sk-very-secret-token的工具结果搜不到Bearer,但搜工具名shell依然能命中那条会话。

⚖️ 注意边界:命中后,窗口摘录里仍会显示工具输出原文——因为搜索到会话就意味着本可以恢复该会话,可见性边界与恢复会话一致,并不额外泄密。

⚡ 性能预算与冷启动重建

RFC 给搜索定了硬性性能目标,并由 criterion 基准测试持续守护(session_search.rs):

指标目标规模
查询延迟 p99≤ 50ms10 万条已索引消息
查询延迟 p99≤ 200ms100 万条
单条消息索引开销≤ 1ms任意索引规模(纯 CPU)
冷启动重建≤ 500ms(CPU 部分)10 万条

索引常驻内存,新消息在append_message时增量入索引,所以新对话随时可搜。daemon 重启后的冷启动重建由 rebuild_session_index 完成:它在锁外构建全新索引后原子换入——并发搜索者永远看到旧索引或新索引,绝不会看到"中间的空索引";重建过程中已有会话即可搜索,旧会话随重建推进逐步变得可搜。

📂 源码导读:从工具调用到倒排索引

想深入源码的话,按这条链路读效率最高:

环节文件
工具参数与结果格式化crates/crabtalk/src/hooks/sessions/search.rs
工具注册与系统提示词注入crates/crabtalk/src/hooks/sessions/mod.rs
Agent 侧使用提示词crates/crabtalk/prompts/sessions.md
Runtime 公开搜索接口crates/runtime/src/engine/session_search.rs
BM25 会话索引与窗口构建crates/runtime/src/sessions/index.rs
命中形状与硬性上限crates/runtime/src/sessions/hit.rs
通用 BM25 倒排索引crates/memory/src/bm25.rs
行为测试(排序/截断/安全)crates/runtime/tests/sessions_search.rs
设计 RFC(含性能预算)docs/src/rfcs/0185-session-search.md

📌 写在最后

Crabtalk 的会话搜索可以浓缩成三个设计决策:

  1. 用内容索引取代人工标签——BM25 召回"真正讨论过"的会话,零维护成本;
  2. 窗口化 + 硬上限——摘录永远有界(1KB 片段 / 16 条窗口 / 20 条命中),保护上下文窗口不被搜索结果反噬;
  3. 敏感内容不入库——工具参数与工具结果不进倒排索引,关键词检索搜不到凭据。

理解这套机制后,你可以更放心地让长期运行的 Agent"翻旧账"了。🦀

【免费下载链接】crabtalk

The long-running embedded agent

项目地址:https://gitcode.com/gh_mirrors/cr/crabtalk
点击查看免费下载
上一篇:什么是Raw扇区读取?OmniDrive实现CD/DVD/BD原始数据读取的原理图解
下一篇:eve 的 MCP Skills 通道:技能参考文件如何做到「字节级精确」发布——以 kennel-handbook 为例

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表