Hindsight 文档文件上传:从 Markitdown 标准提取到 Iris 增强提取的记忆导入实战
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
Hindsight 是面向 Agent 的"会学习的记忆"系统。本文基于 Hindsight Cloud 的文件上传能力,系统讲解如何把 PDF、Word、PPT、Excel、图片与纯文本直接导入任意 memory bank:包括标准提取(Markitdown)与增强提取(Iris)两种方式的选择、支持的文件类型、完整工作流程,并结合仓库源码(解析器实现、HTTP 接口、配置定义)深入说明底层机制,帮助你为 Agent 建立可持续检索、可回忆(recall)、可反思(reflect)的结构化文档记忆。
背景:为什么 Agent 需要"文档记忆"
Agent 的记忆通常来自对话过程或显式的 retain 操作,但当资料以文件形态存在——报告、会议纪要、幻灯片、数据表、截图——它们必须被转化为与既有记忆一致的结构化数据,才能被 recall 与 reflect 查询命中。Hindsight 的做法是:上传文件 → 提取文本 → 作为结构化记忆存入 bank,之后 Agent 可以像检索普通记忆一样检索文档内容。
两种提取方式:Standard(Markitdown)与 Enhanced(Iris)
上传文件时,你需要在两种处理路径中选择:
- 标准提取(Standard extraction):基于微软开源的 Markitdown 库,把文档转为 Markdown 文本。免费,适合文本密集的文件(PDF、Word、纯文本)。
- 增强提取(Enhanced extraction / Iris):基于 AI 的云端处理,理解文档的结构与语义,适合复杂排版、扫描件与图片;按 token 计费。
无论哪种方式,提取结果都会作为结构化记忆存入 bank。两者的差异在源码层面非常清晰:hindsight-api-slim/hindsight_api/engine/parsers/目录下注册了多种解析器(markitdown.py、iris.py、llama_parse 等),并通过 FileParserRegistry 统一管理,支持按名称指定、按扩展名自动探测以及有序回退链(fallback chain)。
工作原理:上传到记忆的完整流程
- 在 Hindsight Cloud 中打开任意 memory bank;
- 点击上传按钮并选择文件;
- 选择 Standard 或 Enhanced(Iris)提取方式;
- 在 Document Operations 面板中通过状态指示器跟踪进度。
处理完成后,提取内容成为 bank 记忆的一部分,Agent 可以像对待任何其他记忆一样对文档内容执行 recall 与 reflect。
底层 API:files/retain 上传接口
从源码看,该能力对应POST /v1/default/banks/{bank_id}/files/retain接口(见 test_file_retain.py 的端到端测试)。上传使用 multipart/form-data:files字段携带文件字节,request字段携带 JSON 配置(如{"document_tags": ["test"], "async": true})。该接口始终异步处理——即使不传async也走异步队列,因为文件转换可能耗时较长。
请求模型 FileRetainRequest 支持请求级与文件级两级参数:
- 请求级
parser:默认解析器或有序回退链,例如'markitdown'或['iris', 'markitdown'];未设置时回退到服务端默认值。 - 文件级
files_metadata[].parser:单文件覆盖,优先级高于请求级配置;files_metadata[].strategy可覆盖该文件在 bank 配置中定义的 retain 策略;document_id、context、metadata、tags、timestamp用于标注该文件生成的记忆单元。 - 请求级
operation_id:可选的客户端自供 UUID,用于幂等去重——以相同 operation_id 重试不会创建重复任务,复用属于其他操作的 id 会返回 HTTP 409。
解析器注册与自动探测
FileParserRegistry 是解析器的统一入口:
register(parser):注册解析器实例,按parser.name()索引;get_parser(name, filename, content_type):显式指定名称时直接返回对应解析器(未注册则抛ValueError);未指定时遍历注册表,调用各解析器的supports()做扩展名/MIME 探测;convert_with_fallback(parsers, file_data, filename):按顺序尝试解析器链,当前解析器抛出UnsupportedFileTypeError、返回空内容或任意异常都会触发回退到下一个,直到链耗尽——这正是"标准/增强"混合方案与高可用性的底层实现;list_parsers():列出全部已注册解析器。
FileParser 是解析器抽象基类,定义convert()与name()两个抽象方法,supports()默认返回 True;注释明确说明:委托给远程服务的解析器(如 Iris)应保持 supports() 为 True,而在 convert() 内抛出UnsupportedFileTypeError,因为远程 API 才知道它实际支持哪些类型。
支持的文档类型
- PDF——报告、白皮书、研究论文
- Word 文档(.docx)——会议纪要、规格说明、提案
- PowerPoint 演示文稿(.pptx)——幻灯片、培训材料
- Excel 电子表格(.xlsx)——数据表、财务报告
- 图片(.png、.jpg)——截图、图表(建议使用增强提取)
- 纯文本(.txt、.md)——日志、笔记、文档
Markitdown 解析器在 supports() 中声明的扩展名集更广,包括.doc/.ppt/.xls等旧版 Office 格式、.html/.htm、.csv,以及带转写能力的音频.mp3/.wav。
深入 Markitdown 解析器:标准提取的工程细节
MarkitdownParser 的实现体现了几个值得注意的工程决策:
懒加载与启动开销:构造函数只通过importlib.util.find_spec("markitdown")检查包是否安装,不实际导入。注释说明这是有意为之:MemoryEngine.initialize()会在启动时急切构造解析器,而导入 markitdown(连带 bs4 → lxml)会让每次服务启动多花约 400ms,即使从未转换过文件。真正的 import 被推迟到首次convert()调用。
线程池执行:markitdown 是同步库,convert()通过loop.run_in_executor(None, self._convert_sync, ...)放入线程池,避免阻塞事件循环(markitdown.py)。
临时文件与清理:markitdown 需要文件路径而非字节流,因此先把字节写入tempfile.NamedTemporaryFile(保留原扩展名),解析完成后在finally中删除临时文件。
UTF-8 显式提示:针对.json/.jsonl/.ipynb/.txt/.text/.md/.markdown/.csv/.html/.htm等文本类扩展名,如果字节能干净地解码为 UTF-8,则通过StreamInfo(charset="utf-8")显式传入字符集提示。原因是 markitdown 只采样首个 chunk 做字符集检测,一个带长 ASCII 前缀的 UTF-8 文件会被误判为 ASCII,进而导致 JSON/ipynb 转换器在解码首个多字节字符时崩溃。
可选 OCR:markitdown 支持通过 OpenAI 兼容的 vision 端点做图片 OCR。_validate_ocr_config()在启动期(而非首次转换时)校验三项配置:模型、API Key、Base URL,任何缺失都会让服务拒绝启动,避免"启动正常、上传图片才失败"的陷阱。启用 OCR 时用 OpenAI SDK 构造llm_client、llm_model、llm_prompt传给MarkItDown;未启用时对.jpg/.jpeg/.png图片直接抛错提示启用 OCR。
深入 Iris 解析器:AI 增强提取的调用链
IrisParser 调用 Vectorize Iris 云端提取服务,完整流程分为四步:
- 申请预签名上传地址:
POST https://api.vectorize.io/v1/org/{org_id}/files,携带{"name": filename, "contentType": content_type},响应中拿到fileId与uploadUrl; - 上传文件字节:向预签名 URL 直接
PUT(不带鉴权头),Content-Type 保持原文件的 MIME 类型; - 启动提取任务:
POST .../org/{org_id}/extraction,携带{"fileId": file_id},返回extractionId; - 轮询直到就绪:
GET .../org/{org_id}/extraction/{extraction_id},默认每 2 秒轮询一次(poll_interval=2.0),总超时 300 秒(timeout=300.0);ready为 true 后检查data.success,成功则返回data.text。
鉴权:需要HINDSIGHT_API_FILE_PARSER_IRIS_TOKEN与HINDSIGHT_API_FILE_PARSER_IRIS_ORG_ID两个环境变量(分别对应 Vectorize API token 与组织 ID),以Authorization: Bearer <token>传入。
错误语义:_raise_for_status()把 4xx 响应统一转为UnsupportedFileTypeError(文件被云端拒绝——支持哪些类型由 Iris API 决定),其他 HTTP 错误转为RuntimeError。这正是上面提到的"远程解析器在 convert() 内抛 UnsupportedFileTypeError"模式的实际落地:Iris 不支持的格式会通过 fallback 链自动回退到 markitdown。
配置与参数一览
以下环境变量在 config.py 中定义,控制文件解析行为:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
HINDSIGHT_API_FILE_PARSER | markitdown | 默认解析器/有序回退链,逗号分隔,如iris,markitdown |
HINDSIGHT_API_FILE_PARSER_ALLOWLIST | 空 | 允许客户端请求的解析器白名单(空 = 全部已注册解析器) |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_ENABLED | false | 是否启用 Markitdown 图片 OCR |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_API_KEY | 空 | OCR 端点的 API Key |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_BASE_URL | 空 | OpenAI 兼容的 OCR/vision 端点地址 |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_MODEL | 空 | OCR/vision 模型名 |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_PROMPT | 见源码 | OCR 转写提示词,默认要求"只转写可见文本、不描述不推断" |
HINDSIGHT_API_FILE_PARSER_MARKITDOWN_OCR_DEFAULT_HEADERS | null | 附加到 OpenAI 客户端的默认请求头(JSON) |
HINDSIGHT_API_FILE_PARSER_IRIS_TOKEN | 空 | Vectorize API token(Iris) |
HINDSIGHT_API_FILE_PARSER_IRIS_ORG_ID | 空 | Vectorize 组织 ID(Iris) |
HINDSIGHT_API_FILE_PARSER_LLAMA_PARSE_API_KEY | 空 | LlamaCloud API Key(llama_parse 解析器) |
默认的 OCR 提示词(DEFAULT_FILE_PARSER_MARKITDOWN_OCR_PROMPT)明确要求:仅转写图片中可见文本,不做描述、总结、翻译或补全,保留原始语言、措辞、数字、标点、大小写与阅读顺序,清晰版面重建为 Markdown 标题/列表/键值/表格,无法辨认处标记[unclear]——这也是增强提取输出质量的重要一环。
此外还有两个批量限制:HINDSIGHT_API_FILE_CONVERSION_MAX_BATCH_SIZE_MB(单次请求所有文件合计的最大 MB 数)与HINDSIGHT_API_FILE_CONVERSION_MAX_BATCH_SIZE(单次请求最大文件数)。token 与 api_key 类配置(file_parser_markitdown_ocr_api_key、file_parser_iris_token等)位于配置模型的敏感字段清单中,避免被日志暴露。
解析器链与"标准+增强"混合策略
HINDSIGHT_API_FILE_PARSER的默认值是markitdown,但注释给出了推荐形态:iris,markitdown——优先用 Iris 的 AI 提取,失败或不支持的格式自动回退到免费的 markitdown。这种有序回退链的语义在 convert_with_fallback() 中实现:逐个尝试,UnsupportedFileTypeError、空内容或任何异常都触发下一个,全部失败才抛出最终错误。对"扫描件/复杂排版优先 Iris、普通文本省钱用 markitdown"的实际场景,可以直接在请求级或文件级parser字段指定。
相关能力:Bank 级 API Key 与 MCP 支持
本次更新之前的两个相关能力值得一并了解:
- Bank 级 API Key(3 月 3 日):可将 API Key 限制到特定 memory bank,适合多租户场景——每个客户/Agent 只能访问自己的记忆;未授权访问返回 403。
- MCP 支持(2 月 13 日):为 Claude、Cursor、VS Code 等 AI 客户端提供 Model Context Protocol 集成,支持单 bank 模式(专用 Agent 记忆)与多 bank 模式(跨 bank 操作),开箱即用约 30 个工具。
文件上传与两者协同:上传后的文档记忆,既可以通过 bank 级 Key 做租户隔离,也可以通过 MCP 工具被 Claude/Cursor 等客户端直接 recall/reflect。
验证方式:从测试用例看端到端行为
仓库中的 test_file_retain.py 给出了可复现的端到端验证路径:先用最小 PDF 字节(一个含 "Test Document" 文本流的最小合法 PDF)或纯文本构造文件,创建 bank 后调用POST /v1/default/banks/{bank_id}/files/retain(multipart 携带files与requestJSON),再通过操作状态查询确认转换完成。test_markitdown_parser.py则针对 MarkitdownParser 做单元级验证。想要本地跑通,可按hindsight-api-slim/pyproject.toml安装依赖(其中声明了markitdown与httpx),配置对应环境变量后启动服务即可。
开始使用
在 Hindsight Cloud 中上传你的第一个文档,然后对它发起一条 recall 查询验证效果。文件上传现已可用:打开任意 memory bank,点击上传按钮选择文件,选择 Standard 或 Enhanced(Iris)提取,即可在 Document Operations 面板跟踪处理进度。处理完成后,文档内容将与既有记忆一起参与 recall 与 reflect,成为 Agent 长期记忆的一部分。
【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考