Hindsight Chat SDK 多平台机器人:用一套 Handler 让 Slack 与 Discord 共享同一份 Agent 记忆
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
本文是 Hindsight 官方 Cookbook 中 "Chat SDK Multi-Platform Bot" 应用案例的深度技术解读。它展示如何基于 Vercel Chat SDK、Hindsight 与 Vercel AI SDK 构建一个同时在 Slack 和 Discord 上运行、共享同一个 Hindsight 记忆库的聊天机器人:在 Slack 中告诉它任何事,再到 Discord 里问它,它能完整回忆起来。读完本文,你将掌握withHindsightChat()包装器的完整工作原理、全部可配置项、bankId 隔离策略,以及端到端部署的每一步细节。
核心能力一览
| 能力 | 说明 |
|---|---|
| 跨平台记忆 | Slack 与 Discord 共享同一个 Hindsight 记忆库(memory bank) |
| LLM 驱动的回答 | 通过 Vercel AI SDK 调用 OpenAI 生成回复 |
| 自动召回(auto-recall) | 每次响应前自动检索相关记忆 |
| 自动留存(auto-retain) | 对话内容自动写入记忆库 |
| 一个 Handler 覆盖所有平台 | withHindsightChat()包装任意 Chat SDK handler |
架构:一次封装,多端复用
Slack message ─────┐ ├─→ withHindsightChat() ─→ auto-recall ─→ LLM handler ─→ auto-retain Discord message ───┘ │ ▼ Hindsight API (shared bank)从源码结构看,这一模式的核心并不复杂:所有平台的消息在进入你编写的 LLM handler 之前,先统一经过withHindsightChat()这个高阶函数;它负责把"当前消息属于哪个记忆库"解析出来、召回相关记忆、构建携带记忆上下文的ctx对象,再交给你自己的 handler。而 Slack 与 Discord 的差异(HTTP 推送 vs WebSocket Gateway)只体现在适配器层,记忆逻辑完全共用。
深入源码:withHindsightChat()的执行管线
官方示例的关键文件是lib/bot.ts。Slack 与 Discord 两个适配器注册到同一个 Chat 实例,且都使用withHindsightChat()并共享同一个 bank ID:
bot.onNewMention( withHindsightChat( { client: hindsight, bankId: () => BANK_ID, // 所有平台共用同一 bank retain: { enabled: true }, }, async (thread, message, ctx) => { const system = ctx.memoriesAsSystemPrompt(); // ... 携带记忆上下文生成 LLM 响应 } ) );在仓库的 hindsight-integrations/chat/src/wrapper.ts 中可以看到它的完整实现。每次消息到达时,包装器严格按 5 个步骤执行:
- 解析 bank ID:
bankId可以是字符串,也可以是接收message并返回字符串的函数(wrapper.ts); - 可选地自动留存入站消息(默认关闭):仅当
retain.enabled === true、消息有文本且author.isMe不为真时触发;async模式(默认true)下是 fire-and-forget,不阻塞响应(wrapper.ts); - 自动召回记忆(默认开启):以消息文本作为查询调用
client.recall(),默认budget: "mid"、includeEntities: true(wrapper.ts); - 构建
HindsightChatContext:把bankId、memories、entities以及memoriesAsSystemPrompt()/retain()/recall()/reflect()方法打包成ctx(wrapper.ts); - 调用你的 handler:
await handler(thread, message, ctx)(wrapper.ts)。
这套行为在 wrapper.test.ts 中都有对应测试佐证:静态与动态 bankId 解析、默认召回、recall: { enabled: false }跳过召回、空文本跳过召回与留存、机器人消息(isMe)不自动留存、错误被降级为 warning 且 handler 仍继续执行等。
memoriesAsSystemPrompt()的格式化细节
ctx.memoriesAsSystemPrompt()内部调用 format.ts 的formatMemoriesAsSystemPrompt()。默认 preamble 为:
You have access to the following memories about this user from previous interactions:
输出按<memories>与<entity_observations>两个 XML 风格区块组织,例如:
You have access to the following memories about this user from previous interactions: <memories> - User prefers dark mode [experience] - Likes coffee [world] </memories> <entity_observations> ## Alice - Works at Acme Corp - Prefers TypeScript </entity_observations>当没有任何记忆或实体时返回空字符串,因此你可以安全地拼接或按需追加。格式化选项见 types.ts:preamble、maxMemories、includeTypes、includeEntities。
配置参考:完整选项与上下文 API
以下配置表格来自 @vectorize-io/hindsight-chat 的 README,与源码类型定义 types.ts 完全一致。
withHindsightChat(options, handler)的 Options
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
client | HindsightClient | 必填 | Hindsight 客户端实例(指向你的 Hindsight API) |
bankId | string \| (msg) => string | 必填 | 记忆库 ID,静态字符串或解析函数 |
recall.enabled | boolean | true | 在 handler 运行前自动召回记忆 |
recall.budget | 'low' \| 'mid' \| 'high' | 'mid' | 召回处理预算 |
recall.maxTokens | number | API 默认 | 召回结果的 token 上限 |
recall.types | FactType[] | 全部 | 只召回指定事实类型(world/experience/observation) |
recall.includeEntities | boolean | true | 是否包含实体观测 |
retain.enabled | boolean | false | 自动留存入站消息 |
retain.async | boolean | true | 是否 fire-and-forget 留存 |
retain.tags | string[] | – | 为留存记忆附加标签 |
retain.metadata | Record<string, string> | – | 为留存记忆附加元数据 |
传给 handler 的ctx(第三个参数)
| 属性 / 方法 | 说明 |
|---|---|
ctx.bankId | 已解析出的 bank ID |
ctx.memories | 召回的记忆数组(RecallResult[]) |
ctx.entities | 实体观测(未启用时为null) |
ctx.memoriesAsSystemPrompt(options?) | 把记忆格式化为 LLM system prompt |
ctx.retain(content, options?) | 将内容写入记忆库 |
ctx.recall(query, options?) | 检索记忆 |
ctx.reflect(query, options?) | 基于记忆进行推理归纳 |
值得一提的是,withHindsightChat兼容onNewMention、onSubscribedMessage、onNewMessage三种事件,并且包把chat声明为 peer dependency(见 package.json),要求chat ^4.0.0、Node.js ≥ 22,因此它可适配 Slack、Discord、Teams、Google Chat、GitHub、Linear 等多种平台。
从零部署:完整步骤
第 1 步:启动 Hindsight API
export OPENAI_API_KEY=your-key docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \ -e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \ -e HINDSIGHT_API_LLM_MODEL=gpt-4o-mini \ -v $HOME/.hindsight-docker:/home/hindsight/.pg0 \ ghcr.io/vectorize-io/hindsight:latest- API 地址:
http://localhost:8888 - Web UI 地址:
http://localhost:9999
第 2 步:配置 Slack
- 打开 api.slack.com/apps 创建新应用;
- 在OAuth & Permissions中添加权限范围:
app_mentions:read、chat:write、channels:history; - 将应用安装到你的工作区;
- 复制Bot User OAuth Token与Signing Secret。
第 3 步:配置 Discord
- 打开 discord.com/developers/applications 创建新应用;
- 在Bot下点击Reset Token并复制;
- 在 Privileged Gateway Intents 中启用Message Content Intent;
- 在General Information下复制Application ID与Public Key;
- 在OAuth2 > URL Generator中选择
bot+applications.commands权限,并勾选 Send Messages、Read Message History; - 使用生成的 URL 将机器人邀请进服务器。
第 4 步:配置环境变量
cp .env.example .env.local # 编辑 .env.local 填入你的 token或手动创建.env.local:
# Slack SLACK_BOT_TOKEN=xoxb-... SLACK_SIGNING_SECRET=... # Discord DISCORD_BOT_TOKEN=... DISCORD_PUBLIC_KEY=... DISCORD_APPLICATION_ID=... DISCORD_MENTION_ROLE_IDS=... # 可选:逗号分隔的 role ID # Hindsight HINDSIGHT_API_URL=http://localhost:8888 # LLM OPENAI_API_KEY=sk-...第 5 步:暴露本地服务
Slack 与 Discord 需要通过 webhook 端点访问你的服务:
ngrok http 3000- 将 Slack 的Event Subscriptions > Request URL设为
https://your-ngrok-url/api/webhooks/slack - 将 Discord 的Interactions Endpoint URL设为
https://your-ngrok-url/api/webhooks/discord
第 6 步:安装并运行
npm install npm run dev第 7 步:启动 Discord Gateway
与 Slack 通过 HTTP 推送事件不同,Discord 需要 WebSocket 连接才能接收消息。dev server 启动后在浏览器中打开:
http://localhost:3000/api/discord/gateway该 Gateway 连接会保持 10 分钟。生产环境建议用 cron 任务定期重启它。
Bank ID 策略:隔离 vs 共享
bank ID 的选择直接决定了记忆的边界,官方文档给出了三种典型策略:
| 策略 | 示例 | 适用场景 |
|---|---|---|
| 静态 | bankId: 'demo' | 团队共享记忆 |
| 按用户 | bankId: (msg) => msg.author.userId | 每个用户隔离的记忆 |
| 跨平台身份 | 将各平台 ID 映射到规范用户 | 同一用户、同一 bank、任意平台 |
从 wrapper.ts 的实现可以看到,动态bankId函数在每次消息到达时基于message求值,因此你可以根据message.author.userId、message.threadId甚至自定义映射函数自由决定记忆的归属。要做到"Slack 和 Discord 共享记忆",只需让两个平台的 bankId 解析结果一致(如本示例的() => BANK_ID)。
错误处理:记忆失败永不拖垮机器人
这是withHindsightChat的重要设计。自动召回与自动留存发生任何错误都只会打印[hindsight-chat] Auto-recall failed:或[hindsight-chat] Auto-retain failed:警告日志,随后继续以空记忆执行你的 handler(见 wrapper.ts 与 wrapper.ts)。对应的测试用例见 wrapper.test.ts 与 wrapper.test.ts。
而你在 handler 内手动调用的ctx.retain()、ctx.recall()、ctx.reflect()则正常抛出异常,方便你按需处理。这意味着记忆系统是"尽力而为"的增强层,即使 Hindsight API 短暂不可用,聊天机器人的核心对话功能也不受影响。
动手体验
- Slack 中:
@memory-bot I'm building a Rust compiler that targets WebAssembly - 等待几秒让 Hindsight 索引记忆
- Discord 中:
@memory-bot what am I working on? - 机器人从 Slack 中召回 Rust/WebAssembly 相关记忆并作答
参考与延伸阅读
- 集成包源码与类型定义:hindsight-integrations/chat/src/wrapper.ts、hindsight-integrations/chat/src/types.ts、hindsight-integrations/chat/src/format.ts
- 集成包使用文档:hindsight-integrations/chat/README.md
- 行为验证测试:hindsight-integrations/chat/src/wrapper.test.ts、hindsight-integrations/chat/src/format.test.ts
- 官方 Cookbook 索引:cookbook/README.md
License:MIT
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考