用 Hindsight 记忆构建个性化搜索 Agent:retain / recall / reflect 实战配方
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
本文是 Hindsight Cookbook 系列中
personalized_search配方的完整实战指南。它将带你用 Hindsight 的记忆能力(retain/recall/reflect)构建一个"认识你"的搜索助手——它会记住你的位置、饮食限制、生活方式与过往搜索,在每次搜索前自动检索相关偏好、改写查询并生成个性化摘要。读完本文,你将掌握 Hindsight 三大核心 API 的实际用法、本地 Docker 部署方式,以及一个可直接复制运行、还能自由扩展的搜索记忆系统。
一、配方概览:搜索为什么需要"记忆"
通用搜索引擎对所有用户返回相同结果,而本配方要解决的核心问题是:同一个查询,如何为不同用户返回不同答案。答案在于给搜索过程加一层"记忆":
- 记忆写入(retain):把用户偏好、历史搜索交互写入 Hindsight 的 memory bank;
- 记忆读取(recall):在搜索前按查询语义召回相关偏好与历史,作为上下文注入 LLM;
- 记忆综合(reflect):让 Hindsight 基于整库记忆生成用户画像摘要,用于审视系统学到了什么。
本配方实现的"个性化搜索"功能特性如下:
- 学习位置(location)、饮食限制(dietary restrictions)与生活方式(lifestyle);
- 基于上下文个性化搜索查询(enhanced query);
- 记住过往搜索与偏好;
- 可选集成 Tavily 进行真实网页搜索(未配置时自动降级为模拟结果)。
整个配方以 Jupyter Notebook 形式组织(本仓库 cookbook 目录下同系列配方还包括 per-user-memory.md、personal_assistant.md、quickstart.md 等,可互相参照),你也可以直接把它整理为一个 Python 脚本运行。
二、前置条件
在动手之前,你需要准备:
- OpenAI API key:同时用于 Hindsight 服务端的 retain/recall/reflect 语义处理与演示代码的查询增强/摘要生成;
- Hindsight 本地实例:通过 Docker 启动(见下一节);
- Tavily API key(可选):仅当你希望执行真实网页搜索时使用;不配置则演示使用模拟搜索结果。
依赖库方面,配方只用到hindsight-client(Hindsight 官方 Python 客户端)、openai、可选的tavily-python以及nest-asyncio(让 Jupyter/脚本中能安全运行异步代码)。
三、本地启动 Hindsight(Docker)
在运行 Notebook 之前,先在终端启动 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几个关键点需要说明:
- 端口约定:容器暴露了两个端口,
8888是 Hindsight HTTP API(客户端连接入口),9999是控制平面(Control Plane)Web UI。这一端口约定在整个仓库中保持一致——例如 docker/docker-compose/local-llm/docker-compose.yaml 与 docker/docker-compose/custom-models/docker-compose.yaml 都映射了8888:8888与9999:9999;docker/standalone/Dockerfile 中同样通过EXPOSE 8888与ENV HINDSIGHT_API_PORT=8888固定 API 端口。启动完成后,客户端应连接http://localhost:8888。 - 环境变量:
HINDSIGHT_API_LLM_API_KEY与HINDSIGHT_API_LLM_MODEL告诉服务端用哪个模型处理记忆的写入、检索与综合(此处示例为gpt-4o-mini)。 - 数据持久化:
-v $HOME/.hindsight-docker:/home/hindsight/.pg0把 Hindsight 内置的 PostgreSQL 数据目录(pg0)挂载到宿主机,容器重启后记忆不丢失。 - 如果不想用 Docker 单容器镜像,仓库还提供了完整的 docker/docker-compose 多服务编排与 docker/standalone 独立部署脚本,适用于接入外部 PostgreSQL、向量扩展、自托管 LLM 等不同场景;本配方直接用官方镜像即可。
四、安装依赖与配置 API Key
4.1 安装依赖
# Tavily is optional - demo works with simulated results if not installed !pip install -q hindsight-client openai tavily-python nest-asyncio其中hindsight-client即仓库 hindsight-clients/python 目录维护的官方 Python 客户端:上层是手写维护、易用的Hindsight包装类(hindsight_client.py),底层是基于 OpenAPI 自动生成的完整 API 客户端hindsight_client_api。
4.2 配置 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: ") # Tavily is optional - for real web search if not os.getenv("TAVILY_API_KEY"): tavily_key = getpass.getpass("Enter your Tavily API key (or press Enter to skip): ") if tavily_key: os.environ["TAVILY_API_KEY"] = tavily_key print("API keys configured!")注意:OpenAI API key 是必需的,它同时服务于两端——Hindsight 服务端负责记忆的语义化处理,演示代码负责查询改写与摘要生成。Tavily 则完全是可选项,跳过即可用模拟结果跑通全流程。
五、初始化客户端:连接本地 Hindsight
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"), ) openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Optional: Tavily for real web search try: from tavily import TavilyClient tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) HAS_TAVILY = True print("Tavily configured - using real web search!") except (ImportError, Exception) as e: HAS_TAVILY = False print("Note: Using simulated search results (Tavily not configured)") USER_ID = "search-user-demo" print("Clients initialized!")从源码看,Hindsight构造函数接受base_url、可选的api_key(作为 Bearer token 注入 Authorization 头)、请求超时timeout(默认 300 秒)与user_agent等参数(见 hindsight_client.py)。本配方中所有记忆都写入同一个bank_id="search-user-demo",这就是"按用户隔离记忆"的体现——每个用户/租户对应一个 memory bank,天然支持多用户场景。
六、三个核心 API:retain / recall / reflect
在进入业务函数之前,先理解本配方依赖的三大记忆操作。它们对应 HTTP API 的 memory 端点,并在 Python 客户端中都有同步(retain/recall/reflect)与异步(aretain/arecall/areflect)两种形式,同步版本适合脚本与 REPL。
6.1 retain —— 写入记忆
retain(bank_id, content, metadata, ...)把一条记忆写入指定 bank,服务端会自动完成分块、嵌入与事实抽取。常用参数(见 hindsight_client.py):
| 参数 | 含义 |
|---|---|
bank_id | 目标 memory bank 的唯一标识 |
content | 记忆内容:纯字符串,或有序的 content block 列表(多模态附件,需要服务端 vision 能力) |
timestamp | 可选事件时间戳 |
context | 可选的上下文描述 |
document_id | 可选,用于对记忆分组 |
metadata | 用户自定义元数据(dict[str, str]),本配方用它标注category |
entities/resolve_entities | 显式实体标注,以及是否与 bank 已有实体解析对齐 |
tags | 可选标签,recall/reflect 时可按标签过滤 |
retain_async/operation_id | 异步处理与幂等重试支持 |
6.2 recall —— 语义召回记忆
recall(bank_id, query, budget, ...)用语义相似度检索相关记忆,返回RecallResponse,其results是RecallResult列表,每项至少包含id与text字段(见 recall_response.py 与 recall_result.py),所以代码中可以直接取m.text拼上下文。常用参数(见 hindsight_client.py):
| 参数 | 含义 |
|---|---|
query | 检索查询,语义匹配 |
budget | 召回预算:"low"/"mid"/"high"(默认"mid"),控制召回多少内容(对应枚举定义见 budget.py) |
types | 事实类型过滤(world/experience/observation) |
max_tokens | 结果最大 token 数(默认 4096) |
tags/tags_match | 标签过滤与匹配模式(any/all/any_strict/all_strict/exact) |
min_scores | 各阶段分数下限,如{"semantic": 0.2, "final": 0.5} |
include_entities/include_chunks/include_source_facts | 是否附带实体观察、原始分块、来源事实 |
temporal_window | 时间窗口,用于时间感知检索 |
6.3 reflect —— 基于记忆生成回答
reflect(bank_id, query, budget, ...)基于 bank 的身份与全部记忆生成上下文相关的回答,返回ReflectResponse,其text字段是格式良好的 Markdown 文本(见 reflect_response.py)。除budget(默认"low")外,还可传response_schema做结构化输出、include_facts返回based_on依据、tags过滤等(见 hindsight_client.py)。
一句话总结三者分工:
retain让系统"记住",recall让系统"想起",reflect让系统"总结与作答"。本配方正是用这三个原语拼出完整记忆闭环。
七、定义辅助函数:记忆驱动的搜索流水线
本配方把整条流水线封装为 5 个辅助函数,逐一看它们的职责与实现。
7.1 存储偏好与交互记录
def store_preference(preference: str) -> str: """Store a user preference.""" hindsight.retain( bank_id=USER_ID, content=f"User preference: {preference}", metadata={"category": "preference"}, ) return f"Learned: {preference}" def store_interaction(query: str, response: str) -> None: """Store a search interaction.""" hindsight.retain( bank_id=USER_ID, content=f"Search query: {query}\nResult highlights: {response[:200]}", metadata={"category": "search_history"}, )两个写入函数都依赖retain,并通过metadata["category"]区分记忆类型——preference(用户偏好)与search_history(搜索历史)。由于metadata支持任意键值对,后续可以按类别筛选、审计或做标签化管理。store_interaction只截取回答前 200 字符写入,避免把冗长回答整段塞进记忆,这是控制记忆体积的实用技巧。
7.2 召回用户上下文
def get_user_context(query: str) -> str: """Retrieve relevant user context.""" memories = hindsight.recall( bank_id=USER_ID, query=f"preferences location dietary lifestyle {query}", budget="mid", ) if memories and memories.results: return "\n".join(f"- {m.text}" for m in memories.results[:6]) return ""这里有两个值得学习的细节:
- 查询增强:在用户原始查询前拼接
preferences location dietary lifestyle等提示词,引导语义检索偏向"偏好类"记忆——这是基于向量检索特性的工程技巧; - 预算与截断:
budget="mid"控制召回量级,再取results[:6]收紧到最多 6 条作为上下文,防止 prompt 过长。
7.3 个性化搜索主流程
def personalized_search(query: str) -> str: """Perform a personalized search.""" user_context = get_user_context(query) enhancement_prompt = f"""Given this user's preferences and the search query, suggest how to enhance the search. User preferences: {user_context if user_context else "No preferences recorded yet."} Search query: {query} Return a JSON object with: - "enhanced_query": The improved search query incorporating relevant preferences - "filters": Any specific filters to apply (e.g., "vegetarian", "within 5 miles") - "reasoning": Brief explanation of personalizations applied""" enhancement = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": enhancement_prompt}], temperature=0.3, max_tokens=300, ) enhanced_info = enhancement.choices[0].message.content # Perform the search if HAS_TAVILY: search_results = tavily.search( query=query, search_depth="advanced", max_results=5, ) results_text = "\n".join( f"- {r['title']}: {r['content'][:150]}..." for r in search_results.get('results', []) ) else: results_text = f"[Simulated search results for: {query}]" response_prompt = f"""Based on the search results and user preferences, provide a personalized summary. User preferences: {user_context if user_context else "No preferences recorded yet."} Query: {query} Search enhancement applied: {enhanced_info} Search results: {results_text} Provide a helpful, personalized response that takes into account their preferences.""" response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": response_prompt}], temperature=0.7, max_tokens=500, ) answer = response.choices[0].message.content store_interaction(query, answer) return answer该函数完整呈现了"记忆 → 查询改写 → 检索 → 个性化摘要 → 记忆回写"的闭环:
- 取上下文:
get_user_context(query)召回相关偏好; - 查询增强:让 LLM 基于偏好输出结构化的
enhanced_query/filters/reasoning(温度 0.3 保证稳定输出); - 执行搜索:配置了 Tavily 时调用
tavily.search(search_depth="advanced", max_results=5)取真实结果,否则用模拟文本占位——这保证了配方在无 Tavily key 时也能端到端跑通; - 个性化摘要:把偏好、增强信息、检索结果一起交给 LLM 生成贴合用户需求的回答(温度 0.7 让表达更自然);
- 记忆回写:
store_interaction(query, answer)把本次交互写回记忆,形成"越用越懂你"的正反馈。
注意这里的enhanced_info与results_text都是"文本摘要",未强制 JSON 解析——enhanced_query等内容主要通过 prompt 约束生成,演示更侧重流程完整性。生产环境可要求response_format={"type": "json_object"}并做严格解析。
7.4 生成用户偏好画像
def get_preference_profile() -> str: """Get a summary of the user's preference profile.""" profile = hindsight.reflect( bank_id=USER_ID, query="""Summarize what we know about this user: - Location and neighborhood - Dietary preferences and restrictions - Work style and schedule - Hobbies and interests - Family situation - Shopping preferences""", budget="high", ) return profile.text if hasattr(profile, 'text') else str(profile)这一步用reflect让 Hindsight 基于整库记忆综合出结构化的用户画像。budget="high"意味着给综合过程更大的召回与生成预算,适合"汇总全貌"这类需要全面视角的任务。hasattr(profile, 'text')是防御性写法——ReflectResponse的text字段是 Markdown 格式的回答(见 reflect_response.py),直接打印即可得到可读画像。
八、构建用户画像:写入首批偏好
一切就绪后,先向 Hindsight 写入一批模拟用户偏好,作为系统"学习"的第一手素材:
print("Learning user preferences...") preferences = [ "Lives in San Francisco, Mission District", "Works remotely as a software engineer", "Vegetarian, prefers organic food when possible", "Has a 5-year-old daughter named Emma", "Enjoys hiking and outdoor activities on weekends", "Prefers quiet coffee shops for remote work", "Lactose intolerant, uses oat milk", "Interested in sustainable and eco-friendly products", "Usually free on Tuesday and Thursday afternoons", "Husband is allergic to nuts", ] for pref in preferences: result = store_preference(pref) print(f" {result}")这 10 条偏好覆盖了位置、职业、饮食、家庭、爱好、购物倾向等多维信息,每条都会以metadata={"category": "preference"}写入search-user-demo这个 bank。它们是后续个性化搜索的"知识底座"。
九、验证效果:个性化搜索结果
记忆就绪后,运行三个典型的个性化搜索:
import time print("=" * 60) print(" Personalized Search Results") print("=" * 60) searches = [ "Find a good coffee shop for working remotely", "Restaurant recommendations for a family dinner", "Birthday gift ideas for a 5-year-old", ] for query in searches: print(f"\nSearch: {query}") print("-" * 40) result = personalized_search(query) print(result) time.sleep(1)观察三个查询,理解"个性化"如何发生作用:
- "Find a good coffee shop for working remotely":应命中"Prefers quiet coffee shops for remote work"与"Works remotely",从而倾向于推荐安静的、适合办公的咖啡店;
- "Restaurant recommendations for a family dinner":应命中素食、乳糖不耐、丈夫坚果过敏、5 岁女儿等信息,从而过滤掉含坚果、乳制品的餐厅并考虑家庭友好;
- "Birthday gift ideas for a 5-year-old":应命中女儿 Emma 的年龄与爱好(如户外活动),给出适龄且贴合兴趣的礼物建议。
time.sleep(1)是为了避免连续请求过密。若配置了 Tavily,这里会返回真实网页搜索结果的个性化摘要;否则返回基于模拟结果的演示文本——两种模式都能展示"偏好注入"的效果。
十、查看偏好画像:检验系统学到了什么
print("=" * 60) print(" User Preference Profile") print("=" * 60) print(get_preference_profile())这一步通过reflect(budget="high")让 Hindsight 把散落的记忆综合成一份连贯的用户画像。它既是"效果检验"——确认系统确实学到了正确信息;也是"能力演示"——体现 reflect 与简单拼接式检索的本质区别:reflect 不是列出一堆记忆原文,而是基于记忆生成有结构的归纳性回答。配合reflect的include_facts=True参数,还能进一步拿到based_on字段,追溯画像中每个结论依据了哪些具体记忆。
十一、输入你自己的搜索词
配方最后留了一个"自由发挥"入口,把任意查询换成你自己的问题:
your_search = "Best hiking trails near me" # Change this! print(f"Search: {your_search}") print("-" * 40) print(personalized_search(your_search))例如把查询改为 "Best hiking trails near me",系统应结合"Enjoys hiking and outdoor activities on weekends""Lives in San Francisco, Mission District"等偏好,给出旧金山 Mission District 附近、周末可去的徒步路线建议——这正是个性化搜索与普通搜索的体验差异所在。此外,由于每次搜索都会通过store_interaction回写记忆,多跑几次后系统对用户的了解会持续加深。
十二、清理与收尾
hindsight.close() print("Client connection closed.")close()关闭底层 HTTP 连接。在异步环境(如 FastAPI、LangGraph)中,客户端还提供aclose()与全套a*异步方法(aretain/arecall/areflect),应优先使用异步版本以避免阻塞事件循环(详见 hindsight_client.py 的文档说明)。由于记忆持久化在~/.hindsight-docker挂载卷中,即使关闭客户端甚至重启容器,search-user-demo这个 bank 的数据依然保留——再次运行 Notebook 时系统仍"记得"上次学到的偏好。
十三、进阶扩展方向
本配方是一个最小可用闭环,基于它你可以自然延伸:
- 多用户隔离:每个用户使用独立
bank_id,即可零改动扩展为多用户搜索助手;参考同系列 per-user-memory.md 配方中的多用户模式; - 标签化记忆治理:
retain支持tags,recall/reflect支持tags_match(any/all/any_strict/all_strict/exact),可以为不同类别记忆打标签并精准过滤; - 结构化输出:
reflect的response_schema参数支持 JSON Schema 结构化输出,把画像生成从"文本摘要"升级为"字段规整的用户档案"; - 异步化改造:把脚本改造为异步版本(
aretain/arecall/areflect),可嵌入 FastAPI 服务、LangGraph 或 CrewAI 等 Agent 框架; - 真实搜索增强:接入 Tavily 之外,还可以把
enhanced_query真正用于改写后的搜索词、把filters转化为搜索 API 的结构化过滤参数; - 记忆质量优化:通过
create_bank的enable_observations、enable_reranking、enable_temporal_retrieval等 bank 级开关(见 hindsight_client.py),按业务需求调节记忆的观察综合、重排与时间感知能力。
总结
本配方完整演示了 Hindsight "Agent Memory That Learns" 的核心循环:用retain沉淀偏好与交互、用recall在搜索前唤起相关记忆、用reflect综合生成画像与个性化回答,并以 Tavily(可选)作为真实搜索源。对照本仓库源码可以确认,budget的low/mid/high取值、RecallResponse.results[].text、ReflectResponse.text等接口细节均与 Python 客户端实现一致。将这套模式扩展到垂直搜索、推荐、客服等场景,即可构建出"越用越懂用户"的下一代搜索体验。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考