Hindsight × Strands Agents SDK 集成指南:为 Strands Agent 赋予跨会话的持久记忆
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
本技术指南围绕 Hindsight 开源仓库中的hindsight-strands集成包展开,讲解如何通过 Strands Agents SDK 的原生@tool模式,为 Agent 接入 Hindsight 的长期记忆能力(retain 存储、recall 检索、reflect 综合)。读完本文,你将掌握该集成包的安装接入、三大记忆工具的调用机制、全局配置方式,以及客户端生命周期管理的最佳实践,并能在本地或云端 Hindsight 服务上直接落地一套具备长期记忆的 Strands Agent。
集成概述:为什么需要 hindsight-strands
Strands Agents SDK 提供了一套简洁的 Python Agent 开发范式,Agent 的能力通过原生@tool装饰的普通 Python 函数声明。但 Strands 本身不提供长期记忆:每个会话结束后,Agent 的上下文便随会话丢失。hindsight-strands的作用正是把 Hindsight 的持久化记忆能力封装成 Strands 兼容的工具函数,让 Agent 在会话之间"记住"事实、偏好与决策。
从仓库结构看,该集成是 Hindsight 众多 Agent 框架集成中的一个独立可安装包,位于 hindsight-integrations/strands,核心代码仅由三个模块构成:
- hindsight_strands/tools.py —— 工具工厂
create_hindsight_tools()与记忆注入函数memory_instructions(); - hindsight_strands/config.py —— 全局配置
configure()与配置数据类HindsightStrandsConfig; - hindsight_strands/errors.py —— 统一异常类型
HindsightError。
根据 pyproject.toml 中的声明,包版本为 0.1.3,要求 Python >= 3.10,依赖strands-agents与hindsight-client>=0.4.0,并需要一台运行中的 Hindsight API 服务。
安装与快速开始
安装只需一条命令:
pip install hindsight-strands快速开始的最小示例(完整代码见 README.md):
from strands import Agent from hindsight_strands import create_hindsight_tools tools = create_hindsight_tools( bank_id="user-123", hindsight_api_url="https://api.hindsight.vectorize.io", api_key="hsk_...", # 或通过 HINDSIGHT_API_KEY 环境变量提供 ) agent = Agent(tools=tools) agent("Remember that I prefer dark mode") agent("What are my preferences?") tools.close() # 仅当客户端由 hindsight-strands 内部创建时才需要关闭上述代码运行后,Agent 会获得三个可调用工具:
| 工具名 | 职责 | 底层 API |
|---|---|---|
hindsight_retain | 把信息写入长期记忆 | client.retain() |
hindsight_recall | 检索长期记忆中相关事实 | client.recall() |
hindsight_reflect | 基于记忆综合出有依据的回答 | client.reflect() |
本地自托管接入
如果在本机通过./scripts/dev/start-api.sh启动 Hindsight 本地服务,只需把地址指向本地端口即可:
tools = create_hindsight_tools( bank_id="user-123", hindsight_api_url="http://localhost:8888", )三大记忆工具的源码级实现
三个工具都在 tools.py 中由工厂函数按开关参数动态生成,每个工具都是被 Strands@tool装饰的普通 Python 函数,bank_id与 Hindsight 客户端通过闭包在构造时捕获,无需修改 Agent 上下文。
hindsight_retain:存储记忆
hindsight_retain(content: str)接收一段文本,调用client.retain(bank_id=..., content=...)写入记忆库;若通过tags配置了标签,则一并传入tags参数。调用前会执行_ensure_bank(),确保记忆银行(bank)存在:
def _ensure_bank(bid: str) -> None: if bid in created_banks: return try: resolved_client.create_bank(bank_id=bid, name=bid) created_banks.add(bid) except Exception: created_banks.add(bid)注意created_banks集合保证了同一进程内只创建一次银行,而异常被吞掉则是为了兼容"银行已存在"的场景——这正是仓库测试 test_tools.py 中test_retain_bank_already_exists所验证的行为。成功时工具返回字符串"Memory stored successfully."。
hindsight_recall:检索记忆
hindsight_recall(query: str)调用client.recall(),并透传budget(预算档位)与max_tokens(结果 token 上限)两个参数;配置了recall_tags时还会附带tags与tags_match。返回结果会被格式化为编号列表:
lines = [] for i, result in enumerate(response.results, 1): lines.append(f"{i}. {result.text}") return "\n".join(lines)无结果时返回固定文案"No relevant memories found."(测试test_recall_no_results/test_recall_none_results覆盖了空列表与 None 两种边界)。
hindsight_reflect:综合记忆
hindsight_reflect(query: str)调用client.reflect(),返回response.text;若综合结果为空或为 None,则回退到"No relevant memories found."。与 recall 不同,reflect 返回的是模型基于记忆"想清楚之后"的连贯回答,而非原始事实列表。
错误处理契约
三个工具共享一致的错误处理模式(见源码):
- 捕获
HindsightError时直接原样抛出(不透传包装); - 捕获其他
Exception时记录 error 日志并包装为HindsightError(f"Retain failed: {e}")抛出。
对应的单元测试(如test_retain_hindsight_error_not_wrapped、test_retain_failure_logs_error)验证了这两种行为,保证 Agent 侧可以通过统一的HindsightError感知记忆操作失败。
线程桥接:Strands 事件循环与 Hindsight 客户端的兼容关键
hindsight-strands在实现上有一个值得注意的工程细节:Strands 会在自身的 asyncio 事件循环中执行工具,而 Hindsight 客户端内部同样使用 asyncio(包括asyncio.timeout),直接调用会与已运行的事件循环冲突。因此 tools.py 定义了模块级线程池与桥接函数:
_executor = concurrent.futures.ThreadPoolExecutor(max_workers=4) def _run_in_thread(fn: Any, *args: Any, **kwargs: Any) -> Any: return _executor.submit(fn, *args, **kwargs).result()所有对客户端的同步调用(retain/recall/reflect/create_bank/close)都经由_run_in_thread在独立线程中执行,从而获得干净的事件循环。这解释了为什么工具函数对外表现为纯同步接口——你可以把它理解为 Strands 工具生态与 asyncio 客户端之间的"线程桥"。
配置参考:三个入口的参数全解析
集成提供三个配置入口,均以*强制关键字参数,具体参数与默认值如下(与 README.md 的 Configuration Reference 保持一致,并结合 tools.py 与 config.py 源码核实):
create_hindsight_tools()
| 参数 | 默认值 | 说明 |
|---|---|---|
bank_id | 必填 | Hindsight 记忆银行 ID |
client | None | 预配置的 Hindsight 客户端(生命周期由调用方管理) |
hindsight_api_url | None | API 地址(传入时由集成内部创建并持有客户端) |
api_key | None | API 密钥(未传 client 时使用) |
budget | "mid" | recall/reflect 的预算档位:low / mid / high |
max_tokens | 4096 | recall 结果的最大 token 数 |
tags | None | 写入记忆时附加的标签 |
recall_tags | None | 检索时用于过滤的标签 |
recall_tags_match | "any" | 标签匹配模式:any / all / any_strict / all_strict |
enable_retain | True | 是否包含 retain 工具 |
enable_recall | True | 是否包含 recall 工具 |
enable_reflect | True | 是否包含 reflect 工具 |
只挂载需要的工具可以减小工具面,例如enable_reflect=False即可省略综合工具(README 中给出完整示例)。测试test_creates_three_tools_by_default与三个 enable 单开测试共同验证了工具按开关组合生成的行为。
memory_instructions()
| 参数 | 默认值 | 说明 |
|---|---|---|
bank_id | 必填 | Hindsight 记忆银行 ID |
client | None | 预配置的 Hindsight 客户端 |
hindsight_api_url | None | API 地址(未传 client 时使用) |
api_key | None | API 密钥 |
query | "relevant context about the user" | 用于记忆注入的 recall 查询词 |
budget | "low" | recall 预算档位(预注入场景默认更低,控制成本) |
max_results | 5 | 最多注入多少条记忆 |
max_tokens | 4096 | recall 结果的 token 上限 |
prefix | "Relevant memories:\n" | 记忆列表前的前缀文本 |
tags | None | 过滤 recall 结果的标签 |
tags_match | "any" | 标签匹配模式 |
memory_instructions()返回格式化字符串,可直接拼入 system prompt,让 Agent 在对话前就"预知"相关记忆。其实现细节(tools.py)值得注意:
- 无结果时返回空字符串
""; - 任何异常都会被静默吞掉并返回
""——注释明确说明"instructions 失败不应阻塞 Agent"; - 若客户端是内部创建的,无论成功失败都会在
finally中关闭,避免泄漏(测试test_closes_internally_created_client_on_success/test_closes_internally_created_client_on_exception验证)。
configure()
| 参数 | 默认值 | 说明 |
|---|---|---|
hindsight_api_url | 生产 API(https://api.hindsight.vectorize.io) | Hindsight API 地址 |
api_key | HINDSIGHT_API_KEY环境变量 | API 密钥 |
budget | "mid" | 默认 recall 预算 |
max_tokens | 4096 | 默认 recall token 上限 |
tags | None | 默认 retain 标签 |
recall_tags | None | 默认 recall 过滤标签 |
recall_tags_match | "any" | 默认标签匹配模式 |
verbose | False | 是否启用详细日志 |
configure()的实现(config.py)有两条优先级规则,被 test_config.py 的多个用例锁定:
- 显式参数 > 环境变量:
api_key = api_key or os.environ.get("HINDSIGHT_API_KEY"),因此传入api_key="explicit-key"会覆盖环境变量; - 配置可被替换:每次调用
configure()都会生成新的HindsightStrandsConfig实例(test_configure_replaces_previous_config),可通过get_config()读取、reset_config()复位。
配置完成后,后续创建工具无需再传连接信息:
from hindsight_strands import configure, create_hindsight_tools configure( hindsight_api_url="http://localhost:8888", api_key="your-api-key", # 或设置 HINDSIGHT_API_KEY 环境变量 budget="mid", # recall 预算:low/mid/high max_tokens=4096, # recall 结果最大 token 数 tags=["env:prod"], # 存储记忆时的标签 recall_tags=["scope:global"], # 检索过滤标签 recall_tags_match="any", # 标签匹配模式:any/all/any_strict/all_strict ) tools = create_hindsight_tools(bank_id="user-123")在 tools.py 中,显式参数与全局配置的合并遵循"显式优先"原则:例如effective_tags = tags if tags is not None else (config.tags if config else None)。测试test_retain_explicit_tags_override_config与test_retain_config_tags分别验证了显式覆盖与配置兜底两条路径。
客户端生命周期管理:v0.1.3 修复的核心问题
create_hindsight_tools()返回的不是普通 list,而是 tools.py 中定义的HindsightTools容器——它继承list,因此可以直接传给Agent(tools=tools),同时额外携带客户端清理能力。这与 Strands 集成变更日志(strands.md)中v0.1.3 的 Bug Fix 直接对应:该版本"修复了 Strands 集成正确关闭内部持有的 Hindsight 客户端,避免资源泄漏与相关稳定性问题"。
理解这个修复需要掌握_resolve_client()的所有权判定逻辑:
def _resolve_client(client, hindsight_api_url, api_key): if client is not None: return client, False # 外部客户端:owns_client = False ... return Hindsight(**kwargs), True # 内部创建:owns_client = True- 传入
client=:所有权归调用方,close()/aclose()是空操作(测试test_close_does_not_close_externally_owned_client); - 仅传
hindsight_api_url/api_key:集成内部创建客户端并持有所有权,close()/aclose()会真正关闭它(测试test_close_closes_internally_owned_client/test_aclose_closes_internally_owned_client)。
HindsightTools还实现了上下文管理器协议,支持with与async with用法;close()通过_run_in_thread在独立线程中同步关闭,aclose()则直接await self._client.aclose()。
推荐做法:FastAPI 生命周期中共享客户端
README 推荐在应用 lifespan 中创建一个共享Hindsight 客户端并显式管理其生命周期:
from contextlib import asynccontextmanager from fastapi import FastAPI from hindsight_client import Hindsight from hindsight_strands import create_hindsight_tools, memory_instructions @asynccontextmanager async def lifespan(app: FastAPI): client = Hindsight(base_url="http://localhost:8888", api_key="test-key") app.state.hindsight_client = client try: yield finally: await client.aclose() app = FastAPI(lifespan=lifespan) @app.post("/chat") async def chat(): client = app.state.hindsight_client tools = create_hindsight_tools(bank_id="user-123", client=client) memories = memory_instructions(bank_id="user-123", client=client) ...这种模式下,工具容器不再拥有客户端,关闭动作统一由 lifespan 的await client.aclose()完成;而如果直接向create_hindsight_tools()传hindsight_api_url/api_key,则必须在关闭阶段调用await tools.aclose()(或tools.close())——这正是 v0.1.3 修复所保障的行为。
类型支持与可观测性:v0.1.2 的两个改进
Strands 集成变更日志记录了v0.1.2的两个 Improvements,同样在源码中有迹可循:
PEP 561 类型标记:包内携带 py.typed 标记文件(并在 pyproject.toml 的 wheel 打包配置
packages = ["hindsight_strands"]中被一并发布),静态类型检查器据此对create_hindsight_tools、memory_instructions、configure等 API 提供完整类型推断。一致的 User-Agent:模块顶部通过
importlib.metadata读取包版本并构造标识头:
try: _VERSION = metadata.version("hindsight-strands") except metadata.PackageNotFoundError: _VERSION = "0.0.0" _USER_AGENT = f"hindsight-strands/{_VERSION}"所有内部创建的 Hindsight 客户端都会携带user_agent=_USER_AGENT(测试test_creates_client_from_url等断言了该参数),并附带 30 秒默认超时,便于服务端识别流量来源与排查问题。
版本演进时间线
结合 strands.md 变更日志,hindsight-strands的演进脉络如下:
- v0.1.1(Features):新增 Strands Agents SDK 集成,使 Hindsight 记忆工具可用于 Strands Agent——即本包的核心能力首次落地;
- v0.1.2(Improvements):发布 PEP 561
py.typed标记以改进 Python 类型支持;所有 HTTP 请求携带统一 User-Agent,提升兼容性与排障体验; - v0.1.3(Bug Fixes):修复内部持有的 Hindsight 客户端关闭逻辑,杜绝资源泄漏与相关稳定性问题。
该集成与 Hindsight 核心采用独立版本节奏——正如总变更日志 index.md 所述,"每个集成按自己的节奏发布,并有自己的变更日志"。因此升级时应以hindsight-strands自身版本为准,而不是 Hindsight API 的版本号。
测试与验证
仓库为集成配备了完整的单元测试,可作为接入时的行为规范参考:
- tests/test_config.py —— 覆盖默认值、环境变量读取、显式覆盖优先级、配置替换与复位等 20 余个用例;
- tests/test_tools.py —— 覆盖客户端解析(
_resolve_client)、工具数量与名称、三个工具的输入输出契约、银行自动创建、标签透传、错误包装、内部客户端关闭等行为。
例如,_resolve_client的测试断言了三条关键规则:显式client优先于 url/key;无 client 时回退到全局配置;配置完全缺失时抛出HindsightError(消息为"No Hindsight API URL configured. Pass client= or hindsight_api_url=, or call configure() first.")。这些测试事实均来自 test_tools.py 的TestResolveClient类。
总结
hindsight-strands以极小的 API 面(三个工具 + 一个注入函数 + 一个全局配置函数)为 Strands Agent 补齐了长期记忆能力。其设计要点可概括为:以原生@tool函数贴合 Strands 生态、以线程桥化解 asyncio 冲突、以显式所有权规则杜绝客户端资源泄漏(v0.1.3 的核心修复)、以py.typed与统一 User-Agent 提升工程体验(v0.1.2 的两项改进)。按 README 推荐在应用生命周期中共享客户端,并将memory_instructions()的结果拼入 system prompt,即可在几行代码内构建出真正"记得住"的 Strands Agent。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考