首页
/ Vibe-Trading 通用文档阅读器 read_document 技能全解析:多格式文本提取、OCR 引擎切换与安全边界

Vibe-Trading 通用文档阅读器 read_document 技能全解析:多格式文本提取、OCR 引擎切换与安全边界

2026-09-09 18:28:05作者:毕习沙Eudora

Vibe-Trading 的 doc-reader 技能为 Agent 提供了一套统一的文档读取入口:无论面对 PDF、Word、Excel、PowerPoint、图片(OCR)、CSV/TSV、配置文件还是源代码文件,都只需调用同一个 read_document 工具,返回结构一致的 JSON 信封。本文基于 doc-reader 技能文档 展开,并结合 doc_reader_tool.py 的完整实现,深入讲解每种格式的底层提取逻辑、OCR 引擎的本地/云端切换机制、质量元数据字段以及路径安全校验,帮助你理解并安全地使用这一能力。

技能定位与核心设计

doc-reader 是一个 category: tool 类型的 Agent 技能,其核心设计理念是「按扩展名分派、统一返回信封」:调用方永远只关心一个工具和一个返回结构,具体格式差异由内部 handler 消化。

技能文档明确要求:始终直接调用 read_document 工具,不要从 bash 里手动运行 Python 脚本(见 SKILL.md 的 Usage 一节)。这是为了让 Agent 的每一次读取都经过路径校验、安全扫描、统一截断与进度上报,而不是绕过工具管线直读文件系统。

该工具的入口定义在 doc_reader_tool.pyread_document(file_path, pages="", min_text_per_page=50) 函数中,并通过 DocReaderTooldoc_reader_tool.py)注册为名为 read_document 的可重复调用工具(repeatable = True)。

支持的格式矩阵

技能文档给出了一张完整的格式支持表,实际源码中的分派逻辑完全一致(见 doc_reader_tool.py):

类别 扩展名 提取方式与说明
PDF .pdf 文本页毫秒级提取;扫描/图片页自动降级 OCR(默认阈值 50 字符/页)
Word .docx 段落文本 + 表格单元格,单元格以 | 拼接
Excel .xlsx.xls 全部 sheet,每 sheet 默认只读前 100 行作为预览
PowerPoint .pptx 逐页提取幻灯片文本框内容
图片 .png/.jpg/.jpeg/.gif/.bmp/.webp/.tiff 仅走 OCR 管线(无 OCR 引擎时返回空文本并附提示)
CSV / TSV .csv.tsv 按原始文本读取,带编码回退
纯文本 .txt/.md/.log/.rst 编码回退读取
配置类 .json/.yaml/.yml/.toml/.ini/.cfg/.env 原始文本读取(不解析、不美化)
标记语言 .html/.htm/.xml 原始文本,不做 HTML 剥离
源代码 .py/.js/.ts/.tsx/.go/.rs/.java/.cpp/.c/.sql/.sh/... 原样返回,禁止重新格式化或缩进
未知扩展名 其他任意后缀 尽力按 UTF-8/GBK 文本读取

从源码看,_TEXT_EXTS 集合(doc_reader_tool.py)还额外覆盖了 .pyi/.jsx/.kt/.swift/.h/.hpp/.cc/.rb/.php/.pl/.lua/.bash/.zsh/.ps1/.bat/.r/.m/.dockerfile/.makefile/.cmake/.conf 等扩展名;对于完全未知的扩展名,分派逻辑同样落入文本读取路径(doc_reader_tool.py),这是一种"尽力而为"的设计。

上传阶段的阻断清单

技能文档强调,以下类型在 /upload 阶段即被拒绝(Blocked):

  • 可执行文件.exe/.dll/.so/...
  • 压缩归档.zip/.tar/...

遇到归档文件时应提示用户先在本地解压再上传,而不是尝试直接解析二进制内容。

统一的 JSON 返回信封

所有格式共用同一个返回结构。技能文档给出了如下示例:

{
  "status": "ok",
  "file": "paper.pdf",
  "format": "pdf",
  "char_count": 52000,
  "truncated": true,
  "text": "..."
}

该信封由 _envelope() 函数构建(doc_reader_tool.py),其中 char_count 记录的是截断前的完整字符数,truncated 标记是否被截断,text 是实际返回的内容。当文本超过 15000 字符时,会以 ... (truncated, total N chars) 的形式裁剪(doc_reader_tool.py_MAX_CHARS = 15000)。测试 test_doc_reader.py 专门验证了信封键的完备性与 20000 字符文件的截断行为。

各格式的附加元数据字段

技能文档中的字段表,对应源码里的 **extra 参数(doc_reader_tool.py):

格式 附加键
pdf total_pagespages_readocr_pagesocr_engineocr_qualityskipped_pages
docx paragraphstables
excel sheets{name, rows, cols} 数组)
pptx slides
text encodingsize

例如读取 Excel 时,sheets 会返回每个 sheet 的名称、总行数、总列数(doc_reader_tool.py),同时 text 中附带每 sheet 的预览块:--- Sheet: {name} ({rows} rows × {cols} cols) ---

使用方法与参数详解

基本调用

技能文档给出的标准调用示例:

read_document(file_path="uploads/paper.pdf")
read_document(file_path="uploads/annual_report.pdf", pages="1-10")
read_document(file_path="uploads/contract.docx")
read_document(file_path="uploads/sales.xlsx")
read_document(file_path="uploads/deck.pptx")
read_document(file_path="uploads/chart.png")     # image → OCR
read_document(file_path="uploads/config.yaml")
read_document(file_path="uploads/notes.md")

pages 参数(仅 PDF 生效)

pages 支持 "1-10""5""1,3,5-8" 三种写法,其他格式会忽略该参数。底层解析器 _parse_pagesdoc_reader_tool.py)有几个值得注意的实现细节:

  • 容错 Unicode 破折号:会把 en-dash( U+2013)、em-dash( U+2014)、数学减号( U+2212)统一替换为 ASCII 连字符,兼容 Word/LLM 粘贴产生的特殊字符。对应测试见 test_doc_reader_unicode_page_dashes.py
  • 拒绝反向范围"5-1" 这类起始大于结束的范围会抛出 inverted page range 错误,与 alpha_bench 工具的 _parse_period 行为保持一致。对应测试见 test_doc_reader_inverted_pages.py
  • 自动越界裁剪:页码会被裁剪到 [1, total_pages] 区间内。
  • 去重排序:最终返回按升序去重后的零基索引列表。

min_text_per_page 参数(PDF OCR 触发阈值)

该参数控制「文本不足才触发 OCR」的临界值,默认 50 字符:

read_document("scanned_report.pdf", min_text_per_page=10)  # 更激进:更早触发 OCR
read_document("mixed_pdf.pdf", min_text_per_page=100)       # 更保守:尽量用原生文本

在源码中,每页提取文本后若 len(text) >= min_text_per_page 则直接采用原生文本,否则进入 OCR 分支([doc_reader_tool.py](https://gitcode.com/GitHub_Trending/vi/Vibe-Trading/blob/5f85c6d515aa5219e941e08d9df176387fbebd66/agent/src/tools/doc_reader_tool.py?utm_source=gitcode_repo_files#L149-L190)。OCR 分支下,页面会以 300/72 DPI(约 4 倍缩放的位图)渲染后交给 OCR 引擎。读取进度通过 emit_progress("reading_pdf", ...) 逐页上报,便于前端展示长文档的读取进度。

各格式底层处理逻辑

PDF:pypdfium2 + OCR 降级

PDF 提取依赖 pypdfium2doc_reader_tool.py),未安装时返回明确的错误提示。整体流程为:

  1. 打开文档、解析页码范围;
  2. 逐页调用 page.get_textpage().get_text_range() 提取原生文本;
  3. 文本量达标 → 直接输出(标记 --- Page N ---);
  4. 文本量不足 → 渲染位图交给 OCR;OCR 无可用引擎 → 该页计入 skipped_pages
  5. 全部页都是扫描页且无 OCR 引擎时,返回错误并附带安装提示(来自 get_ocr_install_hint)。

DOCX:段落 + 表格

_read_docxdoc_reader_tool.py)用 python-docx 依次输出非空段落,表格部分以 --- Table N --- 开头,每行单元格用 | 拼接、单元格内换行替换为空格。返回中带 paragraphstables 计数。

Excel:全部 sheet 预览

_read_exceldoc_reader_tool.py)通过 pandas 的 ExcelFile 遍历全部 sheet,统一以字符串类型(dtype=str)解析,每 sheet 取 head(100) 预览,避免解析精度丢失与预算超支。若用户需要完整数据(如交易日志),技能文档建议改用 analyze_trade_journal 工具(见 trade-journal 技能),而不是直接读取整个工作簿。

PPTX:逐页幻灯片文本

_read_pptxdoc_reader_tool.py)遍历每页幻灯片的 shape,仅提取含文本框(has_text_frame)的内容,逐行输出。

图片:纯 OCR 管线

_read_imagedoc_reader_tool.py)将图片转为 RGB numpy 数组后交给 OCR 引擎。无可用引擎时返回错误并附安装提示;OCR 返回空文本时,text 为空、note 字段提示"OCR returned no text (empty or unreadable image)"。技能文档中的工作流强调:若 OCR 返回空,应如实告知用户,不要编造内容SKILL.md 的 Chart / screenshot / scanned PDF 工作流)。

文本类文件:编码回退链

_read_textdoc_reader_tool.py)按如下顺序尝试解码:utf-8 → utf-8-sig → gbk → gb2312 → big5 → latin-1。两个实现细节值得注意:

  • UTF-16 BOM 优先:检测到 \xff\xfe / \xfe\xff 时先尝试 utf-16 解码(doc_reader_tool.py)。原因是 latin-1 永不抛异常,若把 UTF-16 留到最后会被静默解成带 NUL 字节的乱码(Notepad「Unicode」与 Excel「Unicode 文本」导出的正是 UTF-16)。测试 test_doc_reader.py 验证了 hello 动量 的 UTF-16 文件能正确解码且不含 \x00
  • GBK 中文场景:A 股券商导出的文本常为 GBK 编码,回退链保证了这类文件可读。测试 test_doc_reader.py 验证了 GBK 编码的中文内容能正确解码并报告 encoding: gbk

所有解码结果都带 encodingsize(原始字节数)元数据。

OCR 引擎架构:本地与云端的可插拔切换

OCR 是 doc-reader 最具可配置性的部分。技能文档指出内置两套引擎,不需要除引擎 SDK 之外的额外包:

引擎 类型 依赖 安装方式
rapid 本地(离线) rapidocr_onnxruntime pip install rapidocr_onnxruntime
llm-vision 云端 视觉能力 LLM 模型 + API key 无需额外安装,复用现有 LLM provider 配置

引擎选择机制

引擎选择由环境变量 VIBE_TRADING_OCR_ENGINE 控制(env_schema.py,默认 auto):

  • auto(默认):只使用本地引擎,永不使用云端。这是隐私红线——文档页面绝不离开本机。源码中 _select_first_localengine.py)会按名称排序实例化所有已注册引擎,跳过 is_cloud=True 的引擎,返回第一个 is_available() 的本地引擎;
  • rapid:强制 RapidOCR(本地 ONNX);
  • llm-vision:强制 LLM 视觉 OCR(云端,页面会发送到所配置的 LLM provider);
  • none:彻底禁用 OCR。

引擎实例化被设计为廉价操作(仅做属性赋值),耗时的初始化(如加载 ONNX 模型、建立 OpenAI 客户端)都推迟到 is_available()recognize() 里惰性执行,以保证 auto 模式的探测开销可控(engine.py 注释说明了这一约定)。

可插拔插件机制

OCR 引擎不止内置两款。engine.py 支持通过 pip 的 entry-points(分组名 vibe_trading.ocr_engines)发现第三方引擎(engine.py),插件与内置引擎重名时插件优先。所有引擎只需实现 OcrEngine 协议(engine.py):提供 nameis_cloudinstall_hint 属性以及 is_available()recognize(numpy_rgb)、可选 confidence() 方法。

RapidOCR:本地离线引擎

rapid_ocr.py 中的 RapidOcrEngine 基于 ONNX Runtime,离线可用、无需 API key。recognize 直接调用 RapidOCR()(image),拼接检测框的文本行;无结果时返回空字符串。它被注册为 name="rapid"is_cloud=False

llm-vision:任意 OpenAI 兼容视觉模型

llm-vision 引擎(llm_vision_ocr.py)能与任意 OpenAI 兼容的视觉模型配合(GPT-4o、Qwen-VL、Gemini、Claude、GLM-4V 等),且无需单独维护 provider 映射——它直接复用现有的 LANGCHAIN_PROVIDER / LANGCHAIN_MODEL_NAME / API key 配置,通过 provider_env_names()(覆盖 18+ provider 的动态环境变量解析)确定 base_urlapi_key

几个重要的实现参数:

  • 模型可单独覆盖,不改变 Agent 主模型:VIBE_TRADING_OCR_LLM_MODEL=qwen3.7-plus(对应配置项见 env_schema.py);
  • 系统提示词对提取保真度做了细致约束(llm_vision_ocr.py):表格优先 Markdown 管道表格、合并单元格用 HTML <table>;数学公式用 \(...\) / \[...\] LaTeX(避免 $ 与货币冲突);不可读内容标记为 [illegible]、部分可读用 [?];禁止幻觉、禁止翻译、禁止修正原文错别字、禁止输出注释;
  • 请求参数:超时 90 秒、max_tokens=8192temperature=0.0(提取任务要求最大确定性)、图片以 JPEG quality=85 压缩后 base64 传输(比 PNG 小 5~10 倍);
  • 重试策略:仅对瞬时错误(APITimeoutError/APIConnectionError/5xx)重试一次,4xx 客户端错误(401/403/404/422)立即抛出——「真实的 API 报错比启发式猜测是更清晰的反馈」(llm_vision_ocr.py)。

技能文档特别说明:如果你显式设置 VIBE_TRADING_OCR_ENGINE=llm-vision,模型选择会被信任,视觉能力的校验不做拦截。另外,旧环境变量 VIBE_TRADING_OCR_QWEN_MODEL 已向后兼容地映射到 VIBE_TRADING_OCR_LLM_MODELenv_schema.py),而旧的 qwen-vl 引擎名则被别名映射到 llm-visionengine.py,会输出弃用警告)。

PDF 响应中的 OCR 元数据

技能文档列的字段,源码中由 ocr_quality 对象承载(doc_reader_tool.py):

  • ocr_engine:实际使用的引擎名(如 "rapid""llm-vision")或 null
  • ocr_pages:经过 OCR 处理的页数;
  • skipped_pages:因无可用 OCR 引擎而跳过的页数;
  • ocr_quality:包含 quality_flag(取值 good / degraded / no_ocr_engine / no_ocr_needed)、ocr_pagestext_density(每页字符密度)。

quality_flag 的判定逻辑(doc_reader_tool.py):无 OCR 页且无跳过 → no_ocr_needed;OCR 尝试过但返回空 → degraded;有跳过页 → no_ocr_enginedegraded;OCR 成功且无跳过 → good。图像文件同样返回 ocr_quality(单页场景下 text_density 即总字符数)。

安全边界:路径校验与文件类型限制

doc-reader 的路径安全由 path_utils.pysafe_document_path 保障:所有文件路径必须解析后落在允许的文档导入根目录内,支持 ~ 展开,拒绝 UNC 共享路径,否则抛出 outside allowed document roots 错误并提示通过 VIBE_TRADING_ALLOWED_FILE_ROOTS 环境变量添加导入目录。安全回归测试 test_doc_reader_security.py 验证了三件事:

  • 读取 /etc/passwd 这类系统路径会被拒绝(返回 status: error);
  • 未配置根目录时,临时目录外的文档不可读;
  • 配置 VIBE_TRADING_ALLOWED_FILE_ROOTS 后,该目录下的文档正常读取。

此外,_err() 返回的错误信封统一为 {"status": "error", "error": "..."} 结构;_envelope 在组装完成后还会经过 with_security_warningstext 字段做告警扫描(doc_reader_tool.py),与安全扫描器(scanner.py)联动。文件不存在与目录路径也分别返回明确的错误信息(doc_reader_tool.py)。

实战工作流

技能文档给出了四类典型工作流,均以 read_document 为唯一入口:

论文 / 报告摘要

1. read_document(file_path="paper.pdf")  → full text
2. Extract abstract, methodology, conclusion → summarize

长 PDF 建议配合 pages 分片读取,避免 15000 字符截断丢失结论部分。

合同审查

1. read_document(file_path="contract.docx")  → paragraphs + tables
2. Flag key clauses (termination, liability, payment, IP)

DOCX 返回中的 paragraphs / tables 元数据可帮助判断合同结构化程度,表格以管道符拼接便于 Agent 快速定位条款。

表格快速预览

1. read_document(file_path="sales.xlsx")  → all sheet previews
2. If user wants trade journal analysis specifically, pivot to
   `analyze_trade_journal` tool instead (see trade-journal skill).

Excel 的 100 行预览用于判断数据规模与结构;涉及交易日志的深度分析应切换到专用工具。

图表 / 截图 / 扫描 PDF

1. read_document(file_path="scan.png")  → OCR text
2. If OCR returns empty, tell the user; don't fabricate.

扫描件依赖 OCR 引擎可用性:本地可 pip install rapidocr_onnxruntime,云端则设置 VIBE_TRADING_OCR_ENGINE=llm-vision 并配合视觉模型。ocr_quality.quality_flag == "no_ocr_engine" 是安装 OCR 引擎的明确信号(strategy-dev-manager 技能文档同样引用该字段作为引导依据)。

测试与验证依据

doc-reader 在 tests 目录 下有完整的测试矩阵,可作为行为契约:

小结

doc-reader 技能把 Vibe-Trading 中「文档 → 文本」这件事收敛成一个工具、一套信封、一个引擎开关:格式分派逻辑集中在 doc_reader_tool.py,OCR 的可插拔架构位于 ocr 包,隐私红线由 auto 模式默认守卫(本地优先、云端显式选择),路径安全由 VIBE_TRADING_ALLOWED_FILE_ROOTS 根目录约束兜底。对于 Agent 而言,理解 pages 分片、min_text_per_page 阈值、ocr_quality.quality_flag 语义以及编码回退链,就能在研报阅读、合同审查、行情数据表预览与扫描件转录等场景中精准、安全地完成文本提取。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395