ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从 LiteParse 到 VLM:LlamaIndex 两遍式 OCR 的 Key 交给 TaoToken

从 LiteParse 到 VLM:LlamaIndex 两遍式 OCR 的 Key 交给 TaoToken 1. 链路拆解LiteParse 粗读与 VLM 精读在 LlamaIndex 中的边界LlamaIndex 的 just-in-time Agentic OCR 把文档处理拆成 LiteParse 粗读与 VLM 精读两遍在配置 VLMPredictor 之前先到 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_intro 获取 Key并把 Base URL 设为 https://taotoken.net/api。很多文档问答项目一开始会把 PDF、DOCX、图片全部送进视觉模型结果索引构建慢成本随页数上涨而检索本身并不需要每一页都做到像素级 OCR。两遍式思路更接近工程上的分层第一遍用 LiteParse 这类低成本解析器把全部文件粗读成可检索文本第二遍只把检索命中的相关页面交给 VLM 做 OCR补回表格、印章、公式、手写或扫描质量差的区域。从链路拆解视角看关键不是“选哪个模型”这么简单而是把粗读结果、页码定位、检索命中、精读回写、缓存与失败重试串起来并且让 VLM 客户端稳定指向 TaoToken 的兼容接口。本文按可复现目标来写先给出两段式调用链图再拆第一遍和第二遍的配置片段最后把 VLMPredictor、Claude Code、Codex、CC Switch 的配置边界讲清楚。你能拿到的不是概念图而是一条可以落到本地脚本和工具配置里的路径。两段式调用链可以先用文本图表示文件集PDF/扫描件/图片 - 第一遍LiteParse 粗读全量页面 - 产出doc_id、page_no、raw_text、source_path、hash - 建索引文本索引/向量索引/关键词索引 - 用户查询 - 检索返回命中的 page_id 列表 - 去重与排序按页码、分数、文档聚合 - 第二遍仅对命中页面调用 VLM OCR - 产出refined_text、table_json、layout_notes - 回写合并粗文本 精读文本 - 生成回答或二次检索这个链路里LiteParse 负责“广撒网”VLM 负责“精修”。如果第一遍没有保存页码第二遍就不知道要渲染哪一页如果第二遍没有缓存每次查询都会重复 OCR 同一页如果 VLM 的 Base URL 和 Key 配置错第一遍再便宜也跑不到最终答案。因此先把 TaoToken 的 Key 和 Base URL 准备好再写 LlamaIndex 侧配置是最省排障时间的顺序。2. 第一遍 LiteParse 粗读全量文件如何变成可检索的页码索引第一遍的目标不是“完美还原文档”而是“让检索能工作”。LiteParse 适合处理文本型 PDF、可提取文字的 DOCX、部分版面规整的扫描件。它输出得快成本低适合全量跑一遍。你需要强制保留几个字段否则第二遍会缺定位信息doc_id文档唯一标识建议用文件内容哈希避免同名文件覆盖。page_no页码统一从 1 开始后续渲染和回写都用它。raw_text粗读文本用于全文检索和向量化。source_path原始文件路径第二遍渲染页面时要用。page_hash页面图像或页面文本的哈希用于缓存 VLM OCR 结果。parser_name例如liteparse方便排查不同解析器差异。一个简化后的粗读入库片段可以这样写。实际导入名请按你安装的 LiteParse SDK 调整重点看字段结构import hashlib from pathlib import Path from typing import Iterable def file_hash(path: Path) - str: h hashlib.sha256() with path.open(rb) as f: for chunk in iter(lambda: f.read(1024 * 1024), b): h.update(chunk) return h.hexdigest() def page_hash(doc_id: str, page_no: int, text: str) - str: raw f{doc_id}:{page_no}:{text[:2000]}.encode(utf-8) return hashlib.sha256(raw).hexdigest() def build_coarse_pages(paths: Iterable[str]): pages [] for p in paths: path Path(p) doc_id file_hash(path) # 这里替换为你的 LiteParse 解析调用。 # 目标是拿到 per-page 文本而不是只拿整篇文本。 parsed_pages liteparse_to_pages(str(path)) for item in parsed_pages: page_no int(item[page_no]) raw_text item.get(text, ) pages.append({ doc_id: doc_id, page_no: page_no, raw_text: raw_text, source_path: str(path), page_hash: page_hash(doc_id, page_no, raw_text), parser_name: liteparse, }) return pages入库后检索层可以同时支持关键词和向量。对于两遍式 OCR关键词检索很重要因为合同编号、发票号、条款号、表格标题往往靠精确匹配命中。向量检索适合语义召回。你可以把raw_text切成 page 级 chunk也可以再做 512 到 1024 token 的细分但建议保留page_no元数据。命中后第二遍只需要拿到一个去重后的页码集合。这里有一个容易忽略的点第一遍粗读不要过早丢弃低质量页面。扫描页可能文字很少但检索分数低并不代表它不重要。更稳妥的做法是把低文本密度页面也保留在检索阶段用“文档级召回 页面级排序”处理。例如先按文档召回再把该文档内文本密度低、包含表格关键词、包含签章位置的页面加入候选。这样第二遍 VLM 才不会漏掉关键页。粗读完成后建议做一次质量统计每页字符数、是否包含乱码、是否包含表格符号、是否几乎为空。它可以作为第二遍的触发条件。比如def should_refine(page: dict, retrieved_score: float) - bool: text page.get(raw_text, ) if retrieved_score 0.65: return False if len(text.strip()) 80: return True if any(k in text for k in [表, 金额, 合计, 签字, 盖章, 附件]): return True if in text or □ in text: return True return False这段逻辑不是固定规则但它体现了两遍式的成本控制思想VLM 不是默认全开而是被检索和页面质量触发。此时第一遍的 LiteParse 已经完成全量覆盖第二遍只做“just-in-time”的精读补充。3. 第二遍 VLM 精读只对命中页面调用 OCR 的路由与提示词第二遍的入口不是文件而是page_id列表。你要做四件事去重、排序、限流、渲染。去重按doc_id page_no排序按检索分数和页码顺序限流用最大页数上限渲染把页面转成图像或可被视觉模型读取的输入。不要直接把整份 PDF 再次传给 VLM否则又回到全量 OCR 的老路。页面渲染时要注意分辨率。太低会丢失小字和表格线太高会增加图像 token 和超时概率。实践上先按 150 到 200 DPI 渲染再根据失败重试调整。如果页面主要是文字可以适当降低如果包含密集表格或印章可以局部裁剪。裁剪区域最好来自 LiteParse 或其他版面分析结果中的 bbox如果没有 bbox就按整页处理。VLM OCR 的提示词要明确输出格式否则后续合并很麻烦。推荐让模型输出 JSON字段固定{ page_no: 12, full_text: 本页完整转写文本, tables: [ { title: 费用明细, rows: [[项目, 金额], [服务费, 1000]] } ], layout_notes: [右上角有盖章, 底部有手写签名], uncertain_parts: [第三行第二列数字可能为 8] }对应的 Python 调用可以这样组织。这里不绑定具体视觉模型模型 ID 从 TaoToken 控制台可见的视觉模型里选import base64 import json from pathlib import Path from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) VLM_OCR_PROMPT 你是文档 OCR 引擎。请转写当前页面保留表格结构。 只输出 JSON不要输出 Markdown 代码块。 字段page_no, full_text, tables, layout_notes, uncertain_parts。 无法确认的内容放入 uncertain_parts不要编造。 def image_to_data_url(image_path: str) - str: suffix Path(image_path).suffix.lower().replace(., ) mime image/jpeg if suffix in {jpg, jpeg} else image/png data base64.b64encode(Path(image_path).read_bytes()).decode(utf-8) return fdata:{mime};base64,{data} def vlm_ocr_page(image_path: str, page_no: int, model_id: str) - dict: resp client.chat.completions.create( modelmodel_id, messages[ { role: user, content: [ {type: text, text: VLM_OCR_PROMPT}, { type: image_url, image_url: {url: image_to_data_url(image_path)}, }, {type: text, text: f当前页码{page_no}}, ], } ], temperature0.1, max_tokens2048, ) content resp.choices[0].message.content.strip() return json.loads(content)这段代码的重点是base_url用https://taotoken.net/apiKey 用YOUR_API_KEY模型 ID 用你在 TaoToken 模型列表里确认支持图像输入的模型。不要凭记忆填一个模型名也不要把 Anthropic 的环境变量套到 OpenAI 兼容客户端上。先跑通一页再批量跑。第二遍完成后把refined_text回写到页面记录并保留uncertain_parts。最终回答可以优先使用精读文本粗读文本作为兜底。对于表格可以把tables转成 Markdown 或 JSON 再进入索引。对于layout_notes可以单独存为元数据不参与正文拼接。这样既提升精度又不破坏原有检索结构。4. 把 VLMPredictor 接到 TaoTokenKey、Base URL 与模型名的最小配置在 LlamaIndex 里视觉模型通常落在 MultiModal LLM 或 VLMPredictor 这一层。不管你用哪种封装核心参数都是三个API Key、Base URL、模型 ID。配置前先到 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_key_setup 完成登录并创建 Key。不要在其他地方找来源不明的 Key也不要把 Key 写进公开仓库。推荐用环境变量保存 Key代码里只读环境变量export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_VISION_MODELYOUR_VISION_MODEL_ID如果 LlamaIndex 版本提供 OpenAI 兼容的多模态客户端可以这样初始化。不同版本的导入路径可能不同按你本地版本调整import os from llama_index.multi_modal_llms.openai import OpenAIMultiModal vlm OpenAIMultiModal( modelos.environ[TAOTOKEN_VISION_MODEL], api_keyos.environ[TAOTOKEN_API_KEY], api_baseos.environ[TAOTOKEN_BASE_URL], max_new_tokens2048, temperature0.1, )如果你使用的是自定义VLMPredictor思路相同把它的底层客户端指向 OpenAI 兼容接口并传入base_url。伪配置如下from llama_index.core.multi_modal_llms import MultiModalLLM class TaoTokenVLMPredictor(MultiModalLLM): def __init__(self, model: str, api_key: str, api_base: str): super().__init__() self.model model self.api_key api_key self.api_base api_base def complete(self, prompt: str, image_documents, **kwargs): # 在这里调用 OpenAI 兼容的 chat.completions 或 responses 接口。 # 请求地址的 base 使用 self.api_base。 # 图片转成 image_url 或等价的多模态消息结构。 ...注意Base URL 统一写https://taotoken.net/api不要手动附加 UTM也不要在代码里写带查询参数的地址。UTM 只用于官网和 deep link 的访问统计。模型 ID 必须从 TaoToken 控制台或模型对话页面确认。先拿一张包含表格的发票页测试确认返回 JSON 能解析再接入 LlamaIndex 的文档处理流。这里再给一个验证客户端是否配置正确的片段from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) models client.models.list() for m in models.data[:20]: print(m.id)如果这个片段能列出模型说明 Key 和 Base URL 基本正确。接下来再排查视觉模型是否支持图片输入。不要把“文本模型可用”等同于“视觉 OCR 可用”两者要分开验证。5. 两段式调用链的可复现编排从文件集到答案合并把前面的片段串起来就是一个可复现的 just-in-time Agentic OCR 流程。下面给出编排骨架重点不是库的具体名字而是每一步的输入输出边界from typing import List, Dict MAX_VLM_PAGES 8 def run_two_pass_ocr(paths: List[str], query: str) - Dict: # 第一遍全量粗读 coarse_pages build_coarse_pages(paths) # 建索引可以分别建关键词索引和向量索引 index build_page_index(coarse_pages) # 检索返回页面级命中 hits retrieve_pages(index, query, top_k30) # 去重、排序、限流 candidates dedup_by_page(hits) candidates sorted(candidates, keylambda x: x[score], reverseTrue) refined_pages [] for item in candidates[:MAX_VLM_PAGES]: page item[page] if not should_refine(page, item[score]): continue image_path render_page_to_image( source_pathpage[source_path], page_nopage[page_no], dpi180, ) cache_key f{page[doc_id]}:{page[page_no]}:{page[page_hash]} cached load_ocr_cache(cache_key) if cached: refined_pages.append(cached) continue refined vlm_ocr_page( image_pathimage_path, page_nopage[page_no], model_idYOUR_VISION_MODEL_ID, ) refined[cache_key] cache_key save_ocr_cache(cache_key, refined) refined_pages.append(refined) merged_pages merge_coarse_and_refined(coarse_pages, refined_pages) answer answer_with_pages(merged_pages, query) return { answer: answer, coarse_page_count: len(coarse_pages), refined_page_count: len(refined_pages), refined_pages: refined_pages, }这段代码里有几个工程点值得单独强调MAX_VLM_PAGES必须设上限。没有上限一次查询可能触发几十页 OCR延迟不可控。should_refine要把检索分数和页面质量结合起来。不是所有命中页都值得精读。cache_key要包含文档哈希、页码、页面哈希。文档更新后旧缓存自动失效。merge_coarse_and_refined要以页码为主键合并精读文本优先粗读文本兜底。answer_with_pages要保留引用页码方便回溯是哪一页提供了答案。如果你要把这条链路做成 Agent 工具也建议保持“检索在前、OCR 在后”的顺序。不要让 Agent 直接对全量文件做 OCR更不要让 Agent 绕过检索随意选择页面。工具调用的参数应该是doc_id、page_no、query返回结构化 OCR 结果。这样既可审计也能缓存。6. 排障清单401、404、图片不支持、页码错位与超时配置两遍式 OCR 时最容易卡在接口和页面定位上。下面按具体报错排查。报错一401 Unauthorized 或 Incorrect API key provided。优先检查YOUR_API_KEY是否已经替换环境变量是否在当前终端生效。Claude Code、Codex、Python 脚本可能读取不同环境变量不要假设终端里设置了就一定能被 IDE 或后台进程继承。还要检查 Base URL 是否误写成带 UTM 的官网地址。接口 Base URL 是https://taotoken.net/api不是官网首页。报错二404 Not Found 或 model not found。通常是模型 ID 写错或者把 Anthropic 风格的模型名直接用在 OpenAI 兼容客户端里。去 TaoToken 控制台或模型对话页面确认可用模型 ID再填入配置。LlamaIndex 封装如果默认拼接了/v1也要确认它和 Base URL 的组合是否符合平台文档。不要盲目在代码里硬编码完整端点。报错三Unsupported content type image_url 或 invalid image。这说明当前模型或请求格式不支持图片输入。先确认模型是否具备视觉能力再检查图片是否转成了data:image/png;base64,...或data:image/jpeg;base64,...。如果图片过大可以降低分辨率或裁剪区域。不要把 PDF 二进制直接塞进 image_url。报错四页码错位。LiteParse 输出的页码可能从 1 开始渲染库可能从 0 开始VLM 返回的页码又可能按当前图片重新编号。统一策略是所有内部记录使用从 1 开始的page_no渲染时再转换。合并时以内部page_no为准不信任模型返回的页码。报错五Read timed out 或 rate limit。第二遍 OCR 要加超时、重试和退避。建议单页超时 60 到 120 秒失败重试 2 次并在重试时降低 DPI。批量处理时加并发上限不要一次开几十个请求。对于限流按指数退避并记录失败页下一轮只补失败页。报错六返回内容不是 JSON。在提示词里明确“只输出 JSON”并在代码里做容错解析。如果模型返回了 Markdown 代码块可以先剥离首尾标记再json.loads。仍然失败就记录原始响应不要静默丢弃。排障时建议单独维护一个failed_pages.jsonl记录doc_id、page_no、错误类型、原始响应摘要和重试次数。下一次运行时先补失败页再跑新查询。这样两遍式链路才是可运维的。7. Claude Code、Codex、CC Switch 的配套配置不能混TaoToken 的 Key 可以服务多种工具但不同工具的配置格式不同。最常见的错误是把 Claude Code 的ANTHROPIC_*环境变量套到 Codex 的config.toml里或者反过来把 OpenAI 风格配置塞进 Claude Code。下面分开写。Claude Code 使用settings.json和ANTHROPIC_*系列变量。示例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_CLAUDE_MODEL_ID } }如果你的 Claude Code 版本使用ANTHROPIC_AUTH_TOKEN作为认证头也可以按官方文档替换对应字段。关键是 Base URL 指向https://taotoken.net/apiKey 使用YOUR_API_KEY模型 ID 使用 TaoToken 控制台可见的 Claude 兼容模型。Codex 使用config.toml不要写ANTHROPIC_*。示例model YOUR_CODEX_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat同时设置环境变量export TAOTOKEN_API_KEYYOUR_API_KEYCodex 的env_key指向的是环境变量名不是 Key 本身。不要把YOUR_API_KEY直接写进config.toml的env_key字段否则工具会去读取一个名为YOUR_API_KEY的环境变量而不是使用真实值。CC Switch 可以理解成三件套供应商配置、API Key、默认模型映射。你在 CC Switch 里新增一个供应商时填供应商标识taotokenBase URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY模型映射把 Claude Code 和 Codex 分别映射到 TaoToken 控制台可见的模型 ID不要在一个工具里配置完成后直接把同一份配置文件复制给另一个工具。Claude Code、Codex、CC Switch 的字段名和读取路径不同。先分别跑通最小请求再统一管理。8. 成本与精度平衡第二遍 VLM 应该开多大两遍式 OCR 的核心收益来自“把 VLM 从全量变成按需”。成本可以粗略理解为第一遍粗读覆盖所有页面成本低且可预测第二遍精读只覆盖命中页面成本取决于命中页数和每页图像 token。影响第二遍成本的主要因素是每次查询允许精读的最大页数。页面渲染 DPI 和图片尺寸。是否缓存已 OCR 页面。是否把表格、印章、手写区域单独裁剪。是否对低价值命中页做了过滤。精度方面VLM 对表格、印章、手写、复杂版面的还原通常优于纯文本解析器但也会受图像质量、提示词和模型能力影响。建议先设置一个保守上限例如每次查询最多精读 5 到 8 页并且只对检索分数高或粗读质量差的页面触发。跑一段时间后看哪些查询最终答案确实引用了精读页再调整上限。缓存是最直接的省钱手段。同一页在不同查询中可能被反复命中如果没有缓存每次都要重新渲染和调用 VLM。缓存键建议使用doc_id:page_no:page_hash。当文档更新时page_hash变化旧缓存自然不再命中。对于表格页可以额外缓存table_json避免每次重新解析。另一个技巧是分级精读。第一级只让 VLM 转写文本第二级才要求表格 JSON 和版面说明。如果某页只是普通段落第一级就够。如果某页包含金额、合计、签字位置再升到第二级。这样可以在保持答案质量的同时减少每页输出 token。最后不要把“成本低”理解成“完全不调用 VLM”。两遍式的意义是让 VLM 在正确的时间、正确的页面上出现。第一遍保证召回第二遍保证精度。链路里每一层都有明确职责才不会在成本和精度之间反复摇摆。9. 文末 CTA从模型对话到 API Key 的落地顺序如果你还没开始配置建议按下面顺序落地。先到 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_final 登录并创建 Key然后把本文的 Base URL 填成https://taotoken.net/api。先用模型对话验证视觉模型能读取一张测试图片再去创建 API Key最后接 Claude Code 文档完成命令行工具配置。模型对话https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_chat先用对话页验证图片输入和 OCR 提示词确认返回 JSON 可解析。Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_coding_plan如果你要把两段式 OCR 接到日常开发工作流可以先了解 Coding Plan 的配置方式。API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_api_keys创建独立 Key填入本文所有YOUR_API_KEY占位符不要把 Key 提交到仓库。Claude Code 文档https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentllamaindex_ocr_claude_code_doc按文档完成settings.json和ANTHROPIC_*配置再回到两遍式 OCR 链路中做端到端测试。把 LiteParse 粗读、检索命中、VLM 精读、缓存回写这四步串起来你就得到了一个可复现的 just-in-time Agentic OCR 工作流。Key 交给 TaoTokenBase URL 固定为https://taotoken.net/api剩下的就是按页面、按查询、按成本上限去精读真正重要的内容。
返回列表