ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Hindsight+Dify:给AI助手插上可检索的长期记忆

Hindsight+Dify:给AI助手插上可检索的长期记忆 我一直有个挺直观的痛处无论换哪个AI助手它都记不住我上周说过的那句话、上个月拍过的那张照片、昨天停过的那个车位。每次对话都要重新交代上下文感觉不是在用助手而是在带一个短暂失忆的实习生。直到我翻到一个叫 Hindsight 的开源项目它做的事情正好是给AI补上长期记忆而且是把现实世界的照片、语音、位置这些原始信息变成结构化记忆。再配合 Dify 这样的Agent平台就能让智能体能随时回想用户过去的真实经历。这篇文章就讲讲 Hindsight 到底解决了什么问题、底层怎么流转、我实际部署踩过的坑以及怎么把它接入 Dify给Agent加上一个能检索的记忆库。1. 为什么我非要把 Hindsight 拽进来AI的失忆问题比想象中严重1.1 被没有记忆的AI反复折磨的日常我最早做个人助理型机器人时最大的挫败感不是模型能力不行而是它没有持续性。你今天让它记住我住在浦东公司在新天地通勤一般地铁2号线第二天再问它上班路线它一脸懵。这不是模型笨而是架构上压根没给它一个可以翻旧账的地方。临时塞对话上下文的做法我也试过比如把历史聊天记录一股脑拼进Prompt结果一旦对话拉长Token开销爆炸而且模型会被无关信息干扰。更麻烦的是现实世界的记忆不只是文字——用户拍了张停车场的照片留了句语音当时的地方、时间点都是信息。传统RAG只处理文本遇到非结构化输入就抓瞎。Hindsight 解决的就是这类问题把照片、语音、GPS位置统一翻译成结构化文本记忆再转成向量存起来之后用户只需要用自然语言去搜。1.2 Hindsight 和普通 RAG 的差别在哪很多人一提长期记忆就想到 RAG文档切块、向量化、召回。但 RAG 的假设是知识已经在文档里了而 Hindsight 接收的是现实世界未经打磨的原始输入——图像、声音、位置。它用多模态大模型把这些原始信号转成一句语义密度很高的描述然后再走嵌入式向量检索。我个人的理解是Hindsight 不是又一个文档问答系统它更像是个人生活日志的AI编译器。它能记录的不只是你说过什么还包括你见过什么、在哪里、当时在做什么。这种记忆粒度对个人助理型Agent非常关键因为它需要的上下文通常不是书本知识而是用户自己的经历碎片。1.3 你会在什么时候真正需要它我说几个真实场景各位可以对号入座。第一寻物类我昨天把车停哪儿了Hindsight 的GPS加照片描述能直接给答案。第二行程回顾上礼拜跟客户吃饭的那家餐厅叫什么它靠多模态解析照片里的店面位置。第三主动提醒如果你给Agent接上了日历和位置历史它可以提前说你上次去这家医院检查是三个月前该复查了。这些场景都有一个共性信息曾经存在但存在于现实世界里而不在对话记录里。让AI学会事后覆盖这些信息就是 Hindsight 的价值。2. Hindsight 底层到底在做什么一张照片怎么变成一条可检索的记忆2.1 采集端Flutter 移动应用 手机传感器Hindsight 的客户端用 Flutter 写了 iOS 和 Android 两个版本。用户操作很简单拍照或从相册选图、录一段语音说明、顺手带上当前GPS坐标。这里有意思的是它没有要求用户填表单所有的信息录入都贴近人的习惯——拍一张照说一句话就够了。移动端在采集时会把图片、语音文件和经纬度一起上报到后端。这个设计很务实因为手机是最容易获取现实世界数据的设备也是用户最没有记录负担的终端。你让用户写日志他坚持不了三天但让他随手拍张照、憋一句语音成本低很多。2.2 生成端GPT-4o 视觉解析 Whisper 语音转写 向量化采集到的原始数据会被送到 Supabase Edge Function 里。Edge Function 本质上就是一个跑在云端的 TypeScript/Deno 服务它串联了三件事先调 OpenAI Whisper把语音转成文本再把图片和语音文本一起丢给 GPT-4o 的多模态接口生成一段结构化的记忆描述。描述里通常包含地点、动作、人物、物品、氛围这些维度等于让模型看图说话描述生成后再调 text-embedding-3-small把它转成 1536 维的向量。这个流程的关键是最终落库的可搜索单元既不是原图也不是原始语音而是一段高密度描述加向量。原图会被存到对象存储里做备份但检索主线完全依赖语义层。好处很明显——用户以后问问题时不用精确回忆文件名或时间只要说出大概意思就能匹配到。2.3 存储与检索Postgres pgvector 和 Meilisearch 的混合结构Hindsight 的存储用了两层。一层是 Supabase 自带的 Postgres 数据库开 pgvector 扩展存向量做Embedding相似度检索另一层是 Meilisearch做全文关键词检索。查询时两边同时跑再做结果合并。很多教程只讲 pgvector我实际测下来发现纯向量检索在模糊记忆场景有短板。比如用户忘记当时的说法用了完全不同的词全文搜索向量搜索双通道召回明显更稳。这也符合混合检索Hybrid Search的一般经验语义相似度负责意思接近关键词匹配负责字面命中两条腿走路比单条腿稳。2.4 为什么它没做成重型RAG系统我一开始也困惑Hindsight 为什么不引入一套完整RAG框架而是用一个 Edge Function 加两张索引就完事了。后来想明白了Hindsight 的核心场景是个人记忆数据量级和企业文档库完全不同。个人一天撑死几十条记忆百万级向量和文档级 RAG 压根不是一回事。用轻量组件就能处理没必要把系统做重做重了反而带来部署复杂度和维护成本。这个取舍对个人开发者非常友好。3. 部署实录Supabase、Edge Functions、Meilisearch 一套跑通3.1 准备阶段账号、密钥和运行环境如果你想把 Hindsight 完整跑起来至少要准备这几样东西一个 Supabase 项目负责数据库、认证和 Edge Functions 托管、一个 OpenAI API Key用 GPT-4o 和 Whisper、一个 Meilisearch 实例可以用云服务也可以本地 Docker 拉一个、Flutter 开发环境。我在本地的版本大概是Node 18、Docker、Flutter 3.x、Supabase CLI。没有什么特殊要求唯一建议把 Supabase CLI 升级到最新版老版本对 Functions 部署支持不太友好。3.2 按顺序跑通数据库、函数、环境变量我建议的顺序是先把数据库准备好再部署函数最后配密钥。SQL 里最重要的一条是启用向量扩展create extension if not exists vector;然后创建一张记忆表。为了说明问题我给一个简化版定义create table memories ( id bigserial primary key, user_id uuid not null, description text, image_url text, lat double precision, lng double precision, created_at timestamptz default now(), embedding vector(1536) );接下来部署函数。项目根目录里一般已经有supabase/functions/insert和supabase/functions/search这样的目录。先链接你的远程项目supabase login supabase link --project-ref 你的项目引用ID supabase functions deploy insert supabase functions deploy search然后设置环境变量。这里容易犯迷糊的是本地跑supabase start时读的是项目根目录的.env而线上函数读的是云端的 Secrets两个地方必须都配置。线上这样设supabase secrets set OPENAI_API_KEYsk-你的key supabase secrets set MEILI_HOSThttps://你的实例.meilisearch.io supabase secrets set MEILI_MASTER_KEY你的主密钥缺一个环境变量对应功能就会静默失败而且日志不一定报错得很明显我踩过一次后面会详细说。3.3 移动端运行与第一段记忆的产生数据库和函数都部署完最后跑移动端cd mobile flutter pub get flutter run首次启动要登录 Supabase 的用户体系之后就能拍照创建记忆。等模拟器或真机上出现记忆创建成功的回执后可以去 Supabase Dashboard 的memories表里看一眼你会看到 description 已经被 GPT-4o 整理成一段干净的文字了。那一刻挺有成就感的——一张随手拍的照片变成了一条可以被语义搜索的结构化记忆。3.4 部署过程中我发现的两个设计巧思跑通之后回头看Hindsight 有两个设计细节值得抄作业。一是所有 AI 调用全放在 Edge Function客户端拿不到 OpenAI Key这避免把密钥直接塞进 App 里。二是用户身份通过 Supabase Auth 的 JWT 传给函数查询时按 user_id 过滤天然支持多用户隔离。这俩设计对一个会被复制去生产环境的参考项目来说安全底线算是非常扎实了。4. 让 Dify Agent 调用 Hindsight一份 OpenAPI Schema 搞定长期记忆4.1 为什么非要把 Hindsight 接进 DifyHindsight 原版是自带移动端的单体体验但我实际更需要的是在 Dify 里把 Agent 编排成一个个对外的服务。Dify 的优势在于工作流和 Agent 调度它自带模型管理、Prompt 编排、日志追踪但缺一块长期记忆能力。于是很自然的思路就出现了把 Hindsight 的记忆检索能力封装成工具Dify Agent 在回答问题时随时调用。这也是最近hindsight dify这个组合慢慢被搜起来的原因——大家需要的不是又一个演示 App而是一个能给 Dify 用的记忆插件。4.2 暴露一个安全的检索 HTTP 接口Hindsight 的 search 本身是个 Edge Function已经是一个 HTTP 接口。要让 Dify 能调用需要确认它能接受 POST JSON返回 JSON。我在网关层做了一层简单转发把必要的鉴权 Header 固定成服务端专用 Key避免 Dify 的调用者和 Supabase 的匿名用户混在一起。这里提醒一句如果你直接把带 service_role 的 Supabase 接口暴露给 Dify一旦 Dify 的 API Key 泄露攻击者就拿到了对整库数据的操作系统级别权限。建议在 Edge Function 外侧再包一层自己的鉴权逻辑或者用 Dify 自定义工具里的 Header 鉴权功能把敏感 Key 放在工具配置里而不是明文拼在 URL 上。4.3 在 Dify 里创建自定义工具Dify 的自定义工具支持导入 OpenAPI Schema。我把 Hindsight 的 search 接口整理成一份精简的 schema然后在 Dify 里选择自定义工具-导入 OpenAPI Schema再填上服务地址和鉴权 Header 就行。一个可用的 schema 大概长这样{ openapi: 3.0.0, info: { title: Hindsight Memory API, version: 1.0.0 }, paths: { /search: { post: { operationId: searchMemories, summary: 搜索用户的现实世界记忆适用于询问过去经历、时间、地点、事件时, requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { query: { type: string, description: 用户的自然语言问题 }, top_k: { type: integer, default: 5 } } } } } }, responses: { 200: { description: 匹配的记忆列表, content: { application/json: { schema: { type: object, properties: { memories: { type: array, items: { type: object, properties: { description: { type: string }, created_at: { type: string }, location_name: { type: string } } } } } } } } } } } } } }这个 schema 的 operationId 和 summary 别乱写。Dify 的 Agent 会拿这两个字段来决定什么时候调用这个工具summary 写得越具体模型越容易在恰当的时候触发它。4.4 Agent 提示词与调用测试工具配置完成后在 Dify 的 Agent 应用里把Hindsight这个工具勾选启用然后在系统提示词里明确给它定位。我用的提示词版本是这样的你是一个拥有长期记忆的助手。当用户问起过去的经历、去过的地方、看过的照片、做过的事情时你必须调用 Hindsight 工具搜索记忆再结合搜索结果回答。不要臆造记忆内容。这么写以后测试效果比我预期的好。我上传了一条语音记录星期四晚上在望京吃了家日料大概几个小时后在 Dify 里问之前吃日料那家店在哪个区Agent 会自动触发 Hindsight 工具返回记录里的地点信息。整个链路里最核心的其实是接口描述——模型能不能判断该调用就看你对工具能力的描述是否清晰。5. 联调中踩过的坑和最终沉淀的检查清单5.1 环境变量不生效最隐蔽的坑第一次部署后我上传照片Edge Function 日志里没有报错但数据库里就是没有新记录。查了半天发现是本地跑supabase serve时压根没加载我在云端设置的 Secrets。本地方案要在supabase/functions/.env里单独配一份相同的变量云端则走 Secrets。两边配置分离改一处忘另一处就会出现本地调试一切正常线上静默失败的灵异情况。5.2 向量维度对不上搜了也白搜还有一次我把 embedding 模型换成了text-embedding-3-small的旧配置生成出来的向量维度不一样但表结构里写死了vector(1536)。插入向量时直接报错检索时返回空。排查方式也很简单用一条 SQL 查vector_dims(embedding)看维度然后再去 API 返回里看实际维度。记住换 embedding 模型不是只改个名字维度、距离算法、索引类型都要重新对一遍。5.3 中文场景下 Meilisearch 的表现Hindsight 默认的全文检索集成对英文分词很友好但中文记录进来后Meilisearch 的默认分词器切出来的结果偏碎关键词匹配效果一般。我在中文环境里最终选择让 pgvector 承担主要召回Meilisearch 只做辅助。如果你的记忆内容是中文为主建议把全文检索的权重调低甚至可以先不接 Meilisearch纯向量检索也能满足大多数需求。5.4 联调检查清单每次我重建这套环境都会按下面这张表过一遍帮你省掉来回折腾的时间检查项操作动作失败时的典型现象数据库扩展确认vectorextension 已创建插入向量时报错 undefined columnembedding 维度select vector_dims(embedding)与 API 返回比对插入失败或检索空结果Edge Function 环境变量云端 Secrets 与本地.env分头配置日志正常但数据不落库服务端鉴权确认 Dify 调用用的 Key 不能有全库权限数据泄露风险或权限不足OpenAPI Schema 字段名response 字段必须与真实返回一致Dify 工具调用返回解析错误工具描述summary 写明什么时候用、不用会怎样Agent 不主动调用工具5.5 最后一层思考长期记忆真正难的是何时记和何时忘把 Hindsight 和 Dify 接好之后我还花了不少时间想一个问题记忆不是越多越好。如果每张照片、每句话都变成永久记忆用户查询时召回质量反而会下降。所以我在 Hindsight 的写入端加了一步过滤只有描述里包含明确地点、人物、事件或时间标签的记忆才落库其他寒暄类内容直接丢弃。在 Dify 侧我也给 Agent 规定了先搜索、再判断、最后回答的顺序避免模型直接拿 Prompt 里的老旧上下文硬答。记忆系统做到后面难点已经从怎么存变成了怎么筛选、怎么遗忘。如果你准备在个人项目里复刻这套方案我的建议是先跑通 Hindsight 原版然后只把 search 接口用 OpenAPI Schema 的方式暴露给 Dify。等这个链路稳定了再考虑加自动摘要、记忆过期、按周生成回顾这些进阶动作。这套组合目前是我最满意的低成本长期记忆方案至少不用每次对话都重新教一遍你的AI助手我是谁、我在哪儿、我干过什么了。
返回列表