Hindsight 三大核心方法实战指南:Retain、Recall 与 Reflect 的完整使用与源码解析
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
Hindsight 是一个为 AI Agent 设计的"会学习的记忆系统",其全部能力浓缩在三个核心操作中:retain(存储信息)、recall(检索记忆)、reflect(基于记忆推理回答)。本文以 Hindsight 官方开发者文档的主方法(Main Methods)章节为骨架,结合仓库中 Python/Node.js 客户端与 Rust CLI 的真实实现,完整讲解这三个方法的调用方式、全部参数、返回结构与底层工作原理,帮助你掌握如何用 Hindsight 为 Agent 构建持久化、可检索、可推理的记忆层。
前置条件:本文示例假定你已完成 Hindsight 的安装并跑通了 Quick Start,本地 API 服务与客户端均已就绪。
三大方法概览:从存储到推理的完整闭环
Hindsight 的记忆模型是一个"存储 → 检索 → 推理"的三段式流水线:
| 阶段 | 你要做的事 | 输入 | 输出 | 是否调用 LLM |
|---|---|---|---|---|
| Retain | 存储信息 | 原始文本/文档/对话 | 记忆 ID(Memory IDs) | 是(事实抽取) |
| Recall | 查找信息 | 自然语言查询 | 排序后的事实 + 观察(ranked facts + observations) | 否 |
| Reflect | 推理信息 | 问题/提示词 | 基于证据的推理回答 | 是(生成) |
三者中,Retain 使用 LLM 做事实抽取,Recall 纯检索不使用 LLM,Reflect 使用 LLM 做生成;Recall 与 Reflect 都会消费"观察(observations)",而只有 Reflect 会应用记忆库的"倾向(disposition)"来塑造推理风格。
在仓库的 Python 客户端中,这三个方法定义于 hindsight_client.py,是Hindsight客户端类上的同步方法(另有aretain/arecall/areflect异步版本);而 client_wrapper.py 中的HindsightClient在此基础上扩展出banks、mental_models、directives、memories等命名空间,核心的三个 retain/recall/reflect 则直接继承自基类。
Retain:把信息存入记忆库
Retain 接收对话、文档、事实等原始内容,将其写入一个记忆库(memory bank)。关键认知是:Hindsight 并不会原样存储原始文本,而是通过 LLM 从中抽取结构化事实、识别实体(entities),并在知识图谱中建立连接——非结构化信息由此转化为可查询的结构化记忆。这一点在 retain.md 中有明确说明,也与客户端实现中retain最终将单个 item 包装为 batch 提交给retain_memories接口的行为一致。
Python 用法
# 存储单条事实 client.retain( bank_id="my-bank", content="Alice joined Google in March 2024 as a Senior ML Engineer" ) # 存储一段对话 conversation = """ User: What did you work on today? Assistant: I reviewed the new ML pipeline architecture. User: How did it look? Assistant: Promising, but needs better error handling. """ client.retain( bank_id="my-bank", content=conversation, context="Daily standup conversation" ) # 批量存储多个条目 client.retain_batch( bank_id="my-bank", items=[ {"content": "Bob prefers Python for data science"}, {"content": "Alice recommends using pytest for testing"}, {"content": "The team uses GitHub for code reviews"} ] )Node.js 用法
// 存储单条事实 await client.retain('my-bank', 'Alice joined Google in March 2024 as a Senior ML Engineer'); // 存储一段对话 const conversation = ` User: What did you work on today? Assistant: I reviewed the new ML pipeline architecture. User: How did it look? Assistant: Promising, but needs better error handling. `; await client.retain('my-bank', conversation, { context: 'Daily standup conversation' }); // 批量存储多个条目 await client.retainBatch('my-bank', [ { content: 'Bob prefers Python for data science' }, { content: 'Alice recommends using pytest for testing' }, { content: 'The team uses GitHub for code reviews' } ]);CLI 用法
# 存储单条事实 hindsight memory retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer" # 从文件存储 hindsight memory retain-files my-bank conversation.txt --context "Daily standup" # 存储多个文件(目录) hindsight memory retain-files my-bank docs/Go 客户端
仓库提供了 Go 客户端(位于 hindsight-clients/go),但官方 API 文档中 Go 的 retain 示例区块当前是占位状态(源码片段尚未生成),因此这里不给出具体 Go 代码;其余操作同理。
retain 的核心参数(源码级解读)
对照 hindsight_client.py 中retain()的签名,Python 客户端支持以下参数:
| 参数 | 类型 | 说明 |
|---|---|---|
bank_id | str | 目标记忆库 ID(必填) |
content | str 或list[ContentBlock] | 记忆内容。既可以是普通字符串,也可以是有序的 content block 列表,使图片内联在它实际出现的位置(需要服务端具备视觉能力的 retain LLM) |
timestamp | datetime | 事件发生时间;缺省时服务端取当前时间 |
context | str | 来源场景标签,如"team meeting"、"slack" |
document_id | str | 逻辑文档 ID,用于分组与幂等 upsert |
metadata | dict[str, str] | 任意键值对元数据,随记忆返回 |
entities | list[dict] | 希望保证被识别的实体列表,如[{"text": "...", "type": "..."}] |
resolve_entities | bool | 提供的实体是否与库内已有实体做解析合并(默认 True);False 表示按原文精确存储 |
tags | list[str] | 可见性作用域标签,用于 recall/reflect 时过滤 |
update_mode | str | "replace"(默认,整体替换旧文档)或"append"(增量拼接后重新处理) |
retain_async | bool | True 时后台异步处理 |
operation_id | str | 调用方提供的 UUID,用于异步重试幂等 |
几个值得深挖的行为细节:
- timestamp 的三种形态:省略/
null时使用入库时刻;传 ISO 8601 字符串则采用该时刻;传"unset"则完全不记录时间戳,适合参考文档、书籍等"永恒材料"。时间戳会被注入 LLM 事实抽取提示词,让模型能以它为锚点解析"上周一"这类相对时间表达,也是"What happened last spring?"这类时间召回查询能够工作的前提。 - context 是高杠杆输入:它被直接注入 LLM 提示词,同一句话在不同 context 下抽取出的记忆可能截然不同——例如
"the project was terminated"在"performance review"与"product roadmap"两种 context 下会产生不同的记忆。 - document_id 是幂等性的关键:提供
document_id时执行 upsert——若库中已存在同 ID 文档,会先删除旧文档及其全部记忆再重新处理;省略时每次请求分配随机 UUID,重复摄入会产生重复记忆。 - update_mode 的选择:
"append"适合日志、日记、聊天记录这类增量增长的内容——只需发送新增部分,Hindsight 会跳过未变化的 chunk,仅对新内容触发 LLM 抽取。注意 append 模式必须提供document_id。 - metadata 的存储规则:值一律按字符串存储并随召回返回;值为
null的键会被丢弃而非存储,若需要键存活请传空字符串。 - operation_id 保障重试安全:异步 retain 响应丢失或超时后盲目重试可能造成重复抽取与重复计费。自行提供
operation_id(任意 UUID)后,相同 ID 的重提会返回原操作而不产生新工作;复用已属于其他操作的 ID 会返回 HTTP 409。 - 成本优化:异步 retain 搭配 provider 批量 API(
HINDSIGHT_API_RETAIN_BATCH_ENABLED=true,支持 OpenAI、Groq、Gemini)可将事实抽取的 LLM 成本降低约 50%,代价是最高 24 小时的批处理窗口——对本就后台运行的 retain 通常无感知。该优惠要求请求中async=true。
retain 的响应结构
同步 retain 的响应包含:success(是否无错完成)、bank_id(接收内容的记忆库)、items_count(处理条目数)、async(是否异步执行)、usage(LLM 调用的 token 用量:input_tokens/output_tokens/total_tokens,仅同步操作返回)。异步 retain(含文件上传)则返回operation_ids,可通过 operations 端点轮询进度。
深入:文件摄入与批量摄入
- 文件上传:
retain_files支持 PDF、DOCX、PPTX、XLSX、图片(OCR,取决于配置的解析器)、音频(转录)及 TXT/MD/CSV/JSON/YAML 等文本格式。单次最多 10 个文件、总量不超过 100 MB;每个文件成为独立文档,支持逐文件指定context、document_id、tags(files_metadata)。文件处理始终异步,响应中的operation_ids与文件一一对应。上传的文件默认存储在 PostgreSQL,生产环境可切换 S3/GCS/Azure(HINDSIGHT_API_FILE_STORAGE_TYPE)。 - 异步批量:大批量场景使用
retain_async=True(Node 中async: true),调用立即返回,处理由 worker 服务后台执行;异步操作不返回usage指标。 - 批量提交:CLI 逐个执行 retain 调用实现批量;而 Python/Node 的
retain_batch是真正的单请求多条目提交,可减少网络开销并让 Hindsight 在相关内容间优化抽取。
Recall:用多策略检索搜索记忆
Recall 接收自然语言查询,返回排序后的事实与观察。其底层执行四种检索策略的并行融合:语义相似度(semantic embedding)、关键词 BM25(keyword)、知识图谱遍历(graph traversal)、时间(temporal)——四路结果经RRF(Reciprocal Rank Fusion)融合,再由 cross-encoder 重排序器(reranker)按原始查询对候选重新打分,最终输出单一排序列表。注意响应包含的是结构化事实而非原始文档。
Python 用法
# 基础检索 results = client.recall( bank_id="my-bank", query="What does Alice do at Google?" ) for result in results.results: print(f"- {result.text}") # 带选项检索 results = client.recall( bank_id="my-bank", query="What happened last spring?", budget="high", # 更彻底的图遍历 max_tokens=8192, # 返回更多上下文 types=["world"] # 只检索世界事实 ) # 附带来源 chunk 以获取更多上下文 results = client.recall( bank_id="my-bank", query="Tell me about Alice", include_chunks=True, max_chunk_tokens=500 ) # 查看 chunk 细节(chunks 挂在响应层级,按记忆 ID 索引) for result in results.results: print(f"Memory: {result.text}") if results.chunks and result.id in results.chunks: chunk = results.chunks[result.id] print(f" Source: {chunk.text[:100]}...")Node.js 用法
// 基础检索 const results = await client.recall('my-bank', 'What does Alice do at Google?'); for (const result of results.results) { console.log(`- ${result.text}`); } // 带选项检索 const filteredResults = await client.recall('my-bank', 'What happened last spring?', { budget: 'high', maxTokens: 8192, types: ['world'] }); // 附带实体信息 const entityResults = await client.recall('my-bank', 'Tell me about Alice', { includeEntities: true, maxEntityTokens: 500 }); // 查看实体详情 for (const [entityId, entity] of Object.entries(entityResults.entities || {})) { console.log(`Entity: ${entity.canonical_name}`); console.log(`Observations: ${entity.observations}`); }CLI 用法
# 基础检索 hindsight memory recall my-bank "What does Alice do at Google?" # 带选项检索 hindsight memory recall my-bank "What happened last spring?" \ --budget high \ --max-tokens 8192 \ --fact-type world,experience # 详细输出 hindsight memory recall my-bank "Tell me about Alice" -vrecall 的完整参数清单
对照 hindsight_client.py 的recall()签名,主要参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
query | — | 自然语言查询(唯一必填)。同时驱动语义嵌入、BM25 分词、图遍历种子与时间表达解析;原始查询文本还会传给 cross-encoder 重排序器。超过 500 token 的查询会被拒绝 |
types | 全部 | 事实类别过滤:world(客观事实)、experience(事件与对话)、observation(由多条记忆合并出的、有证据支撑的信念)。每种类型独立跑完整的四策略管线,收窄types可同时降低结果集与查询成本 |
budget | "mid" | 检索深度与广度:low适合快速简单查找,mid适合日常均衡查询,high适合寻找间接关联或穷尽覆盖 |
max_tokens | 4096 | 返回事实可占用的最大 token 数。只统计每条事实的text字段;重排后按相关度顺序填充直至预算耗尽,超出剩余预算的长事实会被跳过(而非截断),以保证更相关的短记忆也能返回。设计哲学是"给 Agent 按 token 思考,而非按条数思考" |
query_timestamp | 服务器当前时间 | 查询发生时刻(ISO 8601),作为解析查询中相对时间表达与近因性打分的锚点。对回放历史对话、构建时间锚定检索的 Agent 至关重要 |
temporal_window | null | 显式{start, end}时间窗,直接用于时间检索分支。只排名、不过滤:窗口内的记忆被提升,窗口外的仍正常返回;比较的是记忆自身的事件时间而非入库时间。边界包含,无时区按 UTC 处理 |
include_chunks | False | 附带每条事实来源的原始文本 chunk。chunk 在max_tokens过滤前获取,因此max_tokens=0时可以"只拿 chunk 不要事实";chunk 有独立预算(默认8192),最后一个 chunk 会被截断而非丢弃并带truncated标记 |
include_entities | 服务端默认开启 | 附带实体规范名;设null可跳过实体 JOIN 查询减小响应体积 |
include_source_facts | False | 当types含observation时,为每条观察附带其来源事实(顶层source_facts字典按事实 ID 索引)。预算(默认4096)按结果顺序消耗,耗尽时是低排名结果失去来源事实 |
prefer_observations | False | 同时召回observation与原始事实时,若某观察由某条原始事实合并而来,则丢弃该原始事实让观察取而代之(释放的槽位由次优结果回填)。适合"全都要但不想看到重复内容"的场景 |
tags/tags_match | null /"any" | 标签作用域过滤(见下文表格) |
tag_groups | null | 复合布尔标签过滤(见下文) |
trace | False | 附带详细调试轨迹:查询嵌入、入口点、各策略检索结果、RRF 融合候选、重排结果、检测到的时间约束与各阶段耗时。不影响检索逻辑 |
min_scores | null | 各阶段分数下限(semantic/keyword在检索阶段按各自分支的 SQL 内剪枝;reranker/final在重排后作用于每个结果) |
tags 与 tags_match:作用域过滤的六种组合
tags默认null、tags_match默认any。tags_match的取值决定过滤逻辑:
| 模式 | 未打标签记忆 | 匹配条件 |
|---|---|---|
any(默认) | 包含 | 命中至少一个指定标签 |
any_strict | 排除 | 命中至少一个指定标签 |
all | 包含 | 命中全部指定标签 |
all_strict | 排除 | 命中全部指定标签 |
exact | 排除 | 标签集合完全等于指定集合 |
空过滤行为值得注意:tags省略/null/[]且tags_match非exact时,等同于无过滤(全部记忆可召回);仅当tags_match="exact"且无标签时,才表示"只召回未打标签的全局记忆"——这是读取observation_scopes: "shared"合并出的全局观察的标准方式。
tag_groups:复合布尔过滤
tag_groups是递归布尔表达式的列表,列表内各分组顶层 AND,每个分组可以是叶子节点{tags, match}或复合节点{and: [...]}、{or: [...]}、{not: ...}。叶子节点的match与tags_match取值一致,默认any_strict。它还支持resolve: "fuzzy"做三元组相似度模糊匹配(如过滤typsecript仍能命中标签typescript,相似度阈值 0.45;库内标签超过 5000 个或模糊展开超过 32 个候选作用域时返回 422)。REST/MCP 请求模型中tag_groups与tags互斥,同时提供会被拒绝。
recall 响应详解
results是按相关度排序的事实列表,每个结果包含:
id:事实唯一 ID,可用于与source_facts交叉引用或应用层去重text:记忆库中存储的抽取事实文本type:world/experience/observation之一context:retain 时设置的场景标签(未设置则为 null)metadata:retain 时附加的键值对(未设置则为 null)tags:该事实的可见性作用域标签entities:关联实体的规范名列表(默认开启)occurred_start/occurred_end:事件起止时间(LLM 抽取),无时间信息则为 nullmentioned_at:事实入库时间document_id/chunk_id:所属文档与来源 chunksource_fact_ids:observation 类型结果的来源事实 ID 列表scores:各阶段分数——final(最终排名分数,results按它降序)、reranker(cross-encoder 归一化相关度 0-1,passthrough 重排模式下为 null)、semantic(向量余弦相似度 0-1,非语义分支召回则为 null)、keyword(BM25 分数 ≥0 无上界,非关键词分支召回则为 null)
关键提醒:scores是相对信号,反映单次查询内的排序,而非跨查询可比的绝对置信度——一次查询的0.8与另一次查询的0.8不可比。对多数 Agent,正确做法是按序消费记忆,让max_tokens决定装多少条,而不是按分数过滤。
响应中还可能包含:source_facts(observation 来源事实字典)、source_facts_truncated(预算截断标记)、chunks(chunk 字典,每项含id/text/chunk_index/truncated)、entities(实体状态字典,含entity_id/canonical_name/observations)、trace(仅trace: true时返回)。
Reflect:基于记忆的倾向感知推理
Reflect 运行一个代理式循环(agentic loop):Agent 自主使用多个检索工具搜索记忆库,应用记忆库的 disposition 特质塑造推理风格,最后产出一份扎根于检索结果的综合回答。与 recall 返回原始事实不同,reflect 返回的是 LLM 撰写的综合回答。
What happens:记忆与观察被召回,记忆库倾向(disposition)被应用,LLM 依据证据链推理生成回答。
Python 用法
# 基础 reflect response = client.reflect( bank_id="my-bank", query="Should we adopt TypeScript for our backend?", include_facts=True, ) print(response.text) print("\nBased on:", len(response.based_on.memories if response.based_on else []), "facts") # 带选项 reflect response = client.reflect( bank_id="my-bank", query="What are Alice's strengths for the team lead role?", budget="high", # 更彻底的推理 include_facts=True, ) # 查看影响回答的事实 for fact in (response.based_on.memories if response.based_on else []): print(f"- {fact.text}")Node.js 用法
// 基础 reflect const response = await client.reflect('my-bank', 'Should we adopt TypeScript for our backend?'); console.log(response.text); console.log('\nBased on:', (response.based_on || []).length, 'facts'); // 带选项 reflect const detailedResponse = await client.reflect('my-bank', "What are Alice's strengths for the team lead role?", { budget: 'high' }); // 查看影响回答的事实 for (const fact of detailedResponse.based_on || []) { console.log(`- ${fact.text}`); }CLI 用法
# 基础 reflect hindsight memory reflect my-bank "Should we adopt TypeScript for our backend?" # 更高推理预算 hindsight memory reflect my-bank "Analyze our tech stack" --budget highreflect 参数详解
对照 hindsight_client.py 的reflect()签名:
| 参数 | 默认值 | 说明 |
|---|---|---|
query | — | 要反思的问题或提示(唯一必填)。如有影响答案的情境上下文,直接并入 query 而非独立字段 |
budget | "low" | 控制回答前探索记忆库的彻底程度。low浅层快速搜索;mid在问题需要时检查多个来源;high跨所有知识层深度探索,可能用多种查询变体寻找间接关联 |
context | null | 附加情境上下文(Node 示例中体现为 options.context) |
max_tokens | 服务端默认4096 | 限制最终生成回答的长度,不影响代理循环期间的检索量 |
response_schema | null | 可选 JSON Schema(对象、非空properties,支持嵌套)。提供后响应会额外包含structured_output字段——Agent 先推理出答案,再做一次抽取把答案按 schema 提取为 JSON。structured_output是text的忠实投影,两者并存互不替代 |
tags/tags_match | null /"any" | 作用于 Agent 可检索的原始事实、观察、mental models 的可见性作用域;同样的tags/tags_match还决定注入 reflect 提示词的 tagged directives |
tag_groups | null | 复合标签过滤,作用于与tags相同的数据源与指令选择;与tags互斥 |
include_facts | False | 响应附带based_on对象,列出 Agent 实际用于构造回答的记忆、mental models 与 directives。只有代理循环中真实检索到的来源才能出现——引用经过校验,防止幻觉式引用 |
include_tool_calls | False | 响应附带trace对象,记录代理循环中每次工具调用与 LLM 调用的完整执行日志(输入、输出、耗时)。设output: false可只保留工具输入以减小载荷 |
apply_all_directives | False | 忽略标签作用域,应用全部活动 directives(默认与记忆一样按标签作用域) |
fact_types | null | 限定参与推理的事实类型(world/experience/observation) |
exclude_mental_models/exclude_mental_model_ids | False / null | 排除全部或指定 mental models |
response_schema:结构化输出示例
Python 端可用 Pydantic 定义结构:
from pydantic import BaseModel # 用 Pydantic 定义响应结构 class HiringRecommendation(BaseModel): recommendation: str confidence: str # "low", "medium", "high" key_factors: list[str] risks: list[str] = [] response = client.reflect( bank_id="hiring-team", query="Should we hire Alice for the ML team lead position?", response_schema=HiringRecommendation.model_json_schema(), ) # 将结构化输出解析回 Pydantic 模型 result = HiringRecommendation.model_validate(response.structured_output) print(f"Recommendation: {result.recommendation}") print(f"Confidence: {result.confidence}") print(f"Key factors: {result.key_factors}")Node.js 端直接传 JSON Schema:
// 直接定义 JSON schema const responseSchema = { type: 'object', properties: { recommendation: { type: 'string' }, confidence: { type: 'string', enum: ['low', 'medium', 'high'] }, key_factors: { type: 'array', items: { type: 'string' } }, risks: { type: 'array', items: { type: 'string' } }, }, required: ['recommendation', 'confidence', 'key_factors'], }; const structuredResponse = await client.reflect('my-bank', 'What do you know about Alice and her career?', { responseSchema: responseSchema, }); // 结构化输出(若返回) if (structuredResponse.structuredOutput) { console.log('Recommendation:', structuredResponse.structuredOutput.recommendation || 'N/A'); console.log('Key factors:', structuredResponse.structuredOutput.key_factors || []); }CLI 端使用--schema传入 JSON schema 文件:
# 先创建 JSON schema 文件 schema.json: cat > schema.json << 'EOF' { "type": "object", "properties": { "recommendation": {"type": "string"}, "confidence": {"type": "string", "enum": ["low", "medium", "high"]}, "key_factors": {"type": "array", "items": {"type": "string"}} }, "required": ["recommendation", "confidence", "key_factors"] } EOF # 然后使用 --schema 标志: hindsight memory reflect hiring-team \ "Should we hire Alice for the ML team lead position?" \ --schema schema.json # 清理临时 schema 文件 rm -f schema.jsonreflect 的标签作用域语义
reflect 的标签过滤比 recall 多一层维度——它同时作用于三类数据源与 directives 选择:
| Reflect 配置 | 原始事实与观察 | Mental models | 生效的 directives |
|---|---|---|---|
省略tags/tags_match/tag_groups | 全部(含未打标签) | 全部(含未打标签) | 仅未打标签/全局 directives |
tags: [],默认tags_match: "any" | 全部 | 全部 | 仅全局 directives |
无标签,tags_match: "exact" | 仅全局数据 | 仅全局 models | 仅全局 directives |
非空tags,any/all | 匹配数据 + 全局数据 | 匹配 models + 全局 models | 匹配 directives + 全局 directives |
非空tags,any_strict/all_strict | 仅匹配数据 | 仅匹配 models | 匹配 directives + 全局 directives |
非空tags,exact | 仅精确匹配数据 | 仅精确匹配 models | 精确匹配 directives + 全局 directives |
非空tag_groups | 匹配复合表达式 | 匹配复合表达式 | 匹配 directives + 全局 directives |
注意第一行的刻意不对称:无作用域的 reflect 能搜索全部记忆,但不会加载带标签的 directives。想让某条 directive 作用于所有 reflect 调用,就把它留空标签;想作用域化,就给它打标签并在 reflect 请求中传入匹配的作用域。
reflect 响应结构
text:综合回答(格式良好的 Markdown 字符串),是 reflect 的主输出。提供response_schema时仍返回,structured_output由其派生而非替代。structured_output:按response_schema解析出的结构化结果(仅当请求提供 schema 时存在,否则 null)。structured_output_error:结构化视图无法产出的原因(provider 错误、超时、输出无法解析)。此时 reflect 本身仍成功(返回 200 与 markdowntext)。该字段的存在代表"可重试",是结构化输出异常时应告警的信号。based_on:构造回答所用的来源(仅include_facts=True)。含三部分:memories(被检索并引用的记忆事实,各含id/text/type/context/occurred_start/occurred_end)、mental_models(使用的 mental models,各含id/text/context)、directives(推理中强制执行的指令,各含id/name/content)。usage:代理循环全部 LLM 调用的 token 用量(input_tokens/output_tokens/total_tokens),用于成本追踪。trace:代理循环完整执行日志(仅include_tool_calls=True)。含tool_calls(每次工具调用的tool名——lookup/recall/learn/expand、input、output(若output: true)、duration_ms、iteration)与llm_calls(每次 LLM 调用的scope(如"agent_1"、"final")与duration_ms)。
Reflect 何时失败:宁可失败也不胡编
Reflect 的回答建立在它收集到的证据之上,因此无法收集证据时它不会回答——直接返回 500,而不是基于空数据给出自信的回答。失败场景包括:
- 检索工具抛错:数据库、embedder 或 reranker 不可用;批内一次失败即整体失败,因为围绕未返回的工具写出的回答与"记忆库确实没有相关内容"无法区分。
- 模型未产出回答:
done调用返回空,或最终综合阶段无输出。 - 模型/传输层无法驱动工具调用:reflect 完全由结构化工具调用驱动,静默丢弃工具定义的传输层会响亮地失败,促使你切换到支持工具调用的模型。
- provider 持续失败:非上下文溢出类的 LLM 错误在循环内重试一次后放弃。
而成功的检索返回空结果不算失败——记忆库确实没有相关内容时,reflect 会在回答中如实说明。另外两类情况刻意不视为失败:上下文窗口溢出时会基于已有证据综合(这是预算问题而非依赖损坏);模型用错工具(缺参、调用不存在的工具)会被作为错误回传让它自行修正,而不是抛出异常。
三大方法对比总结
| Feature | Retain | Recall | Reflect |
|---|---|---|---|
| Purpose(目的) | Store information 存储信息 | Find information 查找信息 | Reason about information 推理信息 |
| Input(输入) | Raw text/documents 原始文本/文档 | Search query 搜索查询 | Question/prompt 问题/提示 |
| Output(输出) | Memory IDs 记忆 ID | Ranked facts + observations 排序事实 + 观察 | Reasoned response 推理回答 |
| Uses LLM(是否用 LLM) | Yes (extraction) 是(抽取) | No 否 | Yes (generation) 是(生成) |
| Uses observations(是否用观察) | No 否 | Yes 是 | Yes 是 |
| Disposition(是否应用倾向) | No 否 | No 否 | Yes 是 |
从源码结构看,这三个方法在客户端实现上是三种截然不同的管线:retain走批处理提交与文件上传通道(retain_batch/file_retain),recall聚合了types/budget/max_tokens/tags/min_scores/temporal_window等十余个检索控制参数,reflect则把response_schema/include_facts/include_tool_calls等代理循环控制参数打包进一次调用。CLI 端(memory.rs)通过hindsight memory retain|recall|reflect三个子命令暴露相同能力,其parse_budget、parse_tags_match、parse_temporal_window等辅助函数与 API 层的枚举定义一一对应。
下一步学习路径
- Retain— 存储记忆的高级选项(timestamp、update_mode、实体解析、观察作用域、异步摄入与成本优化)
- Recall— 调优检索质量与性能(标签过滤、分数下限、时间窗、复合过滤)
- Reflect— 配置倾向(disposition)与结构化输出
- Memory Banks— 管理记忆库及其倾向
关联的完整 API 文档位于 skills/hindsight-docs/references/developer/api,Python 客户端完整实现见 hindsight_client.py,命名空间封装见 client_wrapper.py。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考