使用 Hindsight 记忆构建电影推荐助手:从 retain / recall / reflect 到个性化推荐的完整实战
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
本文基于 Hindsight 官方 Cookbook 中的 Movie Recommendation Assistant 配方,一步步带你在本地 Docker 环境中搭建一个"会记住你"的电影推荐助手。它将演示 Hindsight 三个核心记忆操作——retain()(存储记忆)、recall()(检索相关记忆)、reflect()(综合洞察)——如何让推荐系统记住用户的喜好、看片历史与口味偏好,从而随时间推移给出越来越精准的建议。读完本文,你将掌握 Hindsight Python 客户端的初始化、记忆写入与检索调用方式,并能将其迁移到任意"带记忆的个性化应用"场景。
1. 配方核心:一个会成长的个性化推荐器
这个配方要解决的是推荐系统最常见的痛点:每次对话都从零开始。传统聊天式推荐器没有长期记忆,用户必须反复重复自己的偏好。而本配方借助 Hindsight,让推荐助手具备三项关键能力:
- 记住用户的偏好:喜欢哪些类型、导演和演员;
- 追踪看片历史:看过什么、喜欢什么、不喜欢什么;
- 基于心情给上下文推荐:例如用户说"今晚想看轻松点的",助手能结合历史偏好做出调整。
这与 hindsight-docs/src/pages/cookbook/recipes/quickstart.md 中描述的 Hindsight 记忆模型一脉相承:记忆被组织为World(关于世界的客观事实)、Experiences(Agent 自身的经历)与Observation(通过反思沉淀出的复杂心智模型)三类,恰好对应本配方中"用户偏好(World/Experience)+ 每轮对话记录(Experience)+ 口味总结(Observation)"的数据形态。
2. 前置条件与本地启动
开始前需要准备两样东西:
- OpenAI API key:同时供 Hindsight 服务端(用于记忆提取与反思)和演示程序(用于生成推荐)使用;
- Hindsight 本地实例:通过 Docker 一条命令启动。
在终端中启动 Hindsight:
export OPENAI_API_KEY="your-openai-api-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这条命令的几个关键点:
-p 8888:8888:API 端口(同时也是 MCP 端点),Python 客户端默认通过http://localhost:8888连接;-p 9999:9999:可选的 Web 管理界面,用于浏览记忆库中的文档,例如访问http://localhost:9999/banks/<bank_id>?view=documents查看已存储的记忆;-e HINDSIGHT_API_LLM_API_KEY:把本地的OPENAI_API_KEY注入容器,Hindsight 服务端需要用它做记忆提取(retain 时的实体/时间/关系抽取)与反思(reflect 时的综合推理);-e HINDSIGHT_API_LLM_MODEL=gpt-4o-mini:指定服务端使用的 LLM 模型;-v $HOME/.hindsight-docker:/home/hindsight/.pg0:将嵌入式 Postgres 数据目录持久化到宿主机。不加这个参数,容器停止后记忆就会丢失——这是"会记住"的前提。
3. 安装依赖并配置 API Key
在 Jupyter Notebook 中安装所需依赖:
!pip install -q hindsight-client openai nest-asyncio三个包的分工:
hindsight-client:Hindsight 的官方 Python SDK;openai:OpenAI 官方 SDK,用于生成推荐文本;nest-asyncio:在 Jupyter 中必不可少。Notebook 本身已经运行着一个 asyncio 事件循环,而 hindsight-client 内部使用loop.run_until_complete(),Python 默认不允许嵌套事件循环,nest_asyncio通过打补丁解决这一冲突。
然后配置 OpenAI API Key:
import getpass import os # Set OpenAI API key (used by both Hindsight and the demo) if not os.getenv("OPENAI_API_KEY"): os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ") print("API key configured!")4. 初始化客户端与记忆库
初始化 Hindsight 客户端和 OpenAI 客户端:
import nest_asyncio nest_asyncio.apply() from openai import OpenAI from hindsight_client import Hindsight # Initialize Hindsight client (connects to local Docker instance) hindsight = Hindsight( base_url=os.getenv("HINDSIGHT_BASE_URL", "http://localhost:8888"), ) # Initialize OpenAI client openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Unique identifier for this user's memory bank USER_ID = "movie-fan-demo" print("Clients initialized!")关键概念是USER_ID = "movie-fan-demo":Hindsight 以"记忆库(bank)"为单位隔离记忆,bank_id就是这个用户的记忆容器标识。在 hindsight-clients/python/hindsight_client/hindsight_client.py 的实现中,retain()会校验/创建对应 bank,因此即使这个 bank 尚不存在,第一次retain时也会自动就绪。多用户场景下,只需为每个用户使用不同的bank_id即可实现记忆隔离——这也是本仓库 hindsight-all/hindsight/client_wrapper.py 中BanksAPI.create()所管理的能力。
5. 定义三个核心辅助函数
本配方用三个函数分别演示 Hindsight 的三大核心操作。先看它们的语义(与 quickstart 配方一致):
retain():把新信息写入记忆。底层会调用 LLM 抽取关键事实、时间、实体与关系后落库;recall():基于查询检索相关记忆。它在底层并行执行多种检索策略(语义向量、BM25 关键词、实体/时间/因果关系的图检索、时间范围过滤)后融合打分;reflect():对已有记忆做更深层分析,形成新的连接并沉淀为 observation(观察型记忆)。
完整代码如下:
def get_recommendation(user_query: str) -> str: """ Get a movie recommendation based on user query and remembered preferences. """ # Recall relevant memories about this user's movie preferences memories = hindsight.recall( bank_id=USER_ID, query=f"movie preferences tastes genres {user_query}", budget="mid", ) # Build context from memories memory_context = "" if memories and memories.results: memory_context = "\n".join( f"- {m.text}" for m in memories.results[:5] ) # Generate recommendation with context system_prompt = f"""You are a helpful movie recommendation assistant. You remember the user's preferences and past conversations to give personalized suggestions. What you know about this user: {memory_context if memory_context else "No previous preferences recorded yet."} Give thoughtful, personalized recommendations based on their tastes. If they mention new preferences, acknowledge them.""" response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query}, ], temperature=0.7, max_tokens=500, ) recommendation = response.choices[0].message.content # Store this interaction for future context hindsight.retain( bank_id=USER_ID, content=f"User asked: {user_query}\nRecommendation given: {recommendation}", metadata={"category": "movie_recommendation"}, ) return recommendation def store_preference(preference: str) -> None: """Store an explicit user preference.""" hindsight.retain( bank_id=USER_ID, content=f"User preference: {preference}", metadata={"category": "preference"}, ) print(f"Stored preference: {preference}") def get_preference_summary() -> str: """Get a summary of what we know about the user's movie tastes.""" summary = hindsight.reflect( bank_id=USER_ID, query="Summarize this user's movie preferences, favorite genres, actors they like, and movies they've mentioned enjoying or disliking.", budget="high", ) return summary.text if hasattr(summary, 'text') else str(summary) print("Helper functions defined!")这段代码体现了一个完整的"记忆增强推荐"闭环:
- 回忆:
recall()用query=f"movie preferences tastes genres {user_query}"检索与该用户相关的记忆。这里把原始问题拼进查询词,是为了让语义检索更好地命中"口味/类型"类记忆; - 生成:把召回的记忆拼进 system prompt,让 LLM 基于记忆给出个性化推荐——冷启动时(
memory_context为空)也能正常给出建议,只是没有个性化依据; - 回写:
retain()把"用户问了什么 + 助手推荐了什么"存回记忆库,并打上metadata={"category": "movie_recommendation"}分类标签。这样每轮对话都会沉淀为下一轮的记忆素材,推荐质量随时间累积提升。
5.1 参数细节:budget 的取值与默认值
从 hindsight-clients/python/hindsight_client/hindsight_client.py 的签名可以看到两个budget参数的默认行为:
recall(budget="mid"):预算级别"low" | "mid" | "high",默认 "mid",控制召回阶段投入的检索/打分资源与返回结果的 token 上限;reflect(budget="low"):默认 "low",本例显式传入"high",因为口味总结需要更充分的记忆覆盖。
此外recall()还支持max_tokens(结果 token 上限,默认 4096)、types(按事实类型过滤 world/experience/observation)、tags与tags_match(按标签过滤)、temporal_window(时间窗口加权)等参数;reflect()还支持response_schema(结构化输出 JSON Schema)、include_facts(返回based_on字段列出回答所依据的记忆)、max_tokens等。本配方只用了最小子集,但理解这些扩展参数有助于把同一套模式复用到更复杂的场景。
5.2 metadata 的作用
retain()的metadata参数是用户自定义的键值元数据(见 hindsight_client.py)。在本例中,"category"用来区分"对话记录"与"显式偏好"两类记忆。虽然本配方未显式使用该字段过滤,但在生产场景中可以配合recall(tags=...)或客户端命名空间 API(如 client_wrapper.py 的memories.list(bank_id, search_query=...))做精细化查询。
6. 运行演示:观察跨会话学习
接下来模拟一段跨越多次"会话"的连续对话,观察助手如何逐步积累对用户口味的理解:
import time print("=" * 60) print(" Movie Recommendation Assistant with Memory") print("=" * 60) print() # Simulate a conversation over time conversations = [ "I'm looking for a movie to watch tonight. Any suggestions?", "I really loved Inception and Interstellar. Christopher Nolan is amazing!", "Can you suggest something similar to those? I like mind-bending plots.", "Actually, I'm not in the mood for something heavy. Something lighter?", "I watched The Grand Budapest Hotel last week and loved it!", "What should I watch tonight? Remember what I like!", ] for i, query in enumerate(conversations, 1): print(f"\n[Conversation {i}]") print(f"User: {query}") print("-" * 40) response = get_recommendation(query) print(f"Assistant: {response}") print() time.sleep(1)这段对话设计得非常巧妙,覆盖了记忆系统需要应对的各种情形:
- 冷启动(第 1 轮):尚无记忆,助手给出泛化建议;
- 显式偏好注入(第 2、5 轮):用户自曝喜欢 Nolan 的作品、偏爱烧脑剧情、也爱 Wes Anderson 的《布达佩斯大饭店》——这些信息经
retain进入记忆库; - 基于历史记忆的追问(第 3 轮):
recall命中第 2 轮的"Inception/Interstellar/Nolan"记忆,助手应能给出"mind-bending"风格的片单; - 口味迁移(第 4 轮):用户表示想要轻松的片子,助手需要结合已知的导演/类型偏好做"反常识"推荐;
- 显式要求记住(第 6 轮):验证助手确实记住了前面所有轮次的信息。
time.sleep(1)只是为了放慢节奏便于观察输出。
7. 查看学习到的偏好总结
用reflect()综合所有记忆,让 Hindsight 总结它学到的用户口味:
print("=" * 60) print(" What I've learned about your movie tastes:") print("=" * 60) print(get_preference_summary())这一步与recall有本质区别:recall是"找出最相关的原始记忆",而reflect是"跨记忆做推理综合",其产出(observation)本身会被持久化,成为后续recall/reflect可检索的新记忆。执行到这里,你可以看到一段类似"这位用户偏爱 Christopher Nolan 的烧脑科幻片,同时也欣赏 Wes Anderson 的视觉风格喜剧"之类的自然语言总结——这正是 Hindsight"记忆会学习"的直接体现。
8. 自定义查询与清理
体验你自己的偏好:
# Try your own query! your_query = "I'm in the mood for a sci-fi thriller" # Change this! print(f"You: {your_query}") print("-" * 40) print(f"Assistant: {get_recommendation(your_query)}")演示结束后关闭客户端连接:
hindsight.close() print("Client connection closed.")close()会关闭底层 HTTP 连接(见 hindsight_client.py 的实现:在无运行事件循环时同步关闭,在异步上下文中则调度关闭任务,异步代码建议改用aclose())。如果你还想彻底清理数据,可以删除整个记忆库,例如参考 quickstart.md 的做法通过 HTTP 删除 bank;或者直接删除本机的$HOME/.hindsight-docker数据目录(容器停止后)。
9. 从配方到生产:本配方背后的源码要点
最后,从仓库源码层面梳理本配方背后的几个可深挖要点:
- SDK 同步/异步双接口:hindsight_client.py 中
retain/recall/reflect均为同步包装,底层对应aretain/arecall/areflect异步实现;在 asyncio 应用(如 FastAPI)中应优先使用异步版本; - 批量写入能力:
retain_batch()支持一次写入多条记忆(hindsight_client.py),并支持retain_async=True后台异步处理与operation_id幂等重试——电影推荐场景若需要批量导入用户历史观影记录,可直接复用; - 命名空间客户端:本仓库的 client_wrapper.py 提供了
HindsightClient增强客户端,通过client.banks、client.memories、client.mental_models、client.directives命名空间组织管理类 API,适合在更复杂的 Agent 工程中使用; - 召回策略细节:quickstart 配方明确指出
recall并行执行语义(向量)、关键词(BM25)、图(实体/时间/因果链接)、时间范围四种检索策略再融合,这解释了为什么"记得我说过喜欢 Nolan"这类跨关键词表述也能被稳定召回。
10. 小结
本配方虽然以"电影推荐"为场景,但"recall 历史 → LLM 生成 → retain 回写"的闭环是构建任何带长期记忆的个性化 Agent 的通用范式。把USER_ID换成真实用户 ID、把记忆内容换成业务数据、把推荐 prompt 换成业务指令,你就能快速复刻出健身教练、学习伴侣、健康助理等同构应用(仓库 cookbook 目录 下还有大量同类配方可供参考)。Hindsight 的"记忆即服务"设计,让你无需关心向量库、抽取管线与反思调度的内部实现,把精力集中在业务层即可。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考