1. 金额跑到公司名里,检索当然查不到
先说清楚这篇要解决什么:PDF 里的错位表格,被 OCR 解析后直接入库,结果金额字段串到了公司名里,用户搜公司名搜出一堆金额,搜金额又什么都查不到。适合谁看?正在做票据、合同、对账单入库的开发者,尤其是那种「OCR 模型明明识别对了,但入库后数据就是乱的」场景。
面试官圈出那行的时候,问题其实不在 OCR 模型本身。模型输出的文本大概率是对的,错的是解析后的列坐标、合并单元格处理,以及金额字段往哪一列映射。我试过把 OCR 原始输出和入库后的记录并排打印,一眼就能看出错位发生在哪一步:PDF 表格里「公司名」和「金额」两列挨得近,OCR 按行返回文本时把两列拼成了一串,入库代码又按固定分隔符切分,切出来的第一段被当成公司名,金额就跟着进去了。
所以这条排障路线不是重做 OCR 模型,而是拿 Codex 当对照工具,把 OCR 输出和入库代码放在一起看,定位到底是坐标解析错了、合并单元格没处理,还是字段映射写反了。TaoToken 在这里只做一件事:给你一个 Key 和 Base URL,让 Codex 能跑起来帮你读代码、比对数据。OCR 解析仍然在你本地执行,数据不出本地。
2. 用 TaoToken 拿到 Key 和 Base URL
打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建 Key,这一步是后面所有操作的前提。注册完进控制台,在 API Keys 页面新建一个 Key,复制出来先存好,后面配置 Codex 要用。
TaoToken 的 API 地址是 https://taotoken.net/api ,这个地址填到 Codex 的 Base URL 里就行。注意这里只提供 Key 和 Base URL 两样东西,OCR 解析、PDF 读取、入库逻辑全都在你本地跑,TaoToken 不碰你的数据。
如果你后面要长期用 Codex 做编码和 Agent 任务,可以看下 Coding Plan 页面,按用量选套餐比单次调用划算。只是这次排障的话,先用按量计费的 Key 就够了。
配置 Codex 的时候,环境变量这样写:
export OPENAI_API_KEY="你的TaoToken Key" export OPENAI_BASE_URL="https://taotoken.net/api"如果你用的是 Codex CLI,配置文件里对应改成:
# ~/.codex/config.toml model = "gpt-5-codex" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "OPENAI_API_KEY"保存后重启终端,让环境变量生效。这一步做完,Codex 就能通过 TaoToken 的 Base URL 发请求了。
3. 把 OCR 输出和入库代码喂给 Codex 对照
排障的核心动作是「对照」。你需要准备两份东西:一份是 OCR 解析后的原始输出,一份是入库代码。OCR 输出最好是带坐标的,比如每行文本加上 bbox 信息,这样 Codex 能看出列的位置关系。
假设你的 OCR 输出长这样,每行是文本 + 坐标:
[ {"text": "杭州某某科技有限公司 128,000.00", "bbox": [72, 210, 480, 232]}, {"text": "上海另一家公司 56,800.00", "bbox": [72, 240, 460, 262]} ]入库代码可能是这样按空格切分的:
def parse_row(line): parts = line["text"].split(" ") company = parts[0] amount = parts[1] if len(parts) > 1 else "" return {"company": company, "amount": amount}问题就出在这:split(" ")把「杭州某某科技有限公司」和「128,000.00」当成两段,但如果 OCR 输出里公司名中间有空格,或者金额和公司名之间没有空格,切分就会错位。更隐蔽的情况是合并单元格,OCR 把跨列的公司名和金额读成一行,坐标上金额其实在另一列,但文本被拼到了一起。
把这两份内容一起丢给 Codex,提示词可以这样写:
下面是一段 OCR 解析后的表格输出(带 bbox 坐标)和对应的入库代码。 请帮我定位:为什么金额字段会跑到公司名字段里? 重点检查: 1. 列坐标是否被正确使用,还是只按文本切分 2. 合并单元格是否导致跨列文本被拼成一行 3. 金额字段的映射逻辑是否写反 OCR 输出: [粘贴你的 JSON] 入库代码: [粘贴你的 parse_row 函数]Codex 会逐行分析,指出split(" ")这种按分隔符切分的做法在表格场景下不可靠,应该改用 bbox 的 x 坐标来判断列归属。比如金额的 bbox 左边界如果大于某个阈值,就归到金额列,而不是靠文本里的空格。
4. 跑通一次请求,验证定位结果
改完代码后,别急着全量入库。先拿一条错位的数据跑一次,看输出对不对。你可以写个小脚本,把 OCR 输出和修正后的解析结果并排打印:
import json def parse_row_by_bbox(line, amount_x_threshold=400): bbox = line["bbox"] text = line["text"] # 如果文本左边界超过阈值,整行归金额列 if bbox[0] >= amount_x_threshold: return {"company": "", "amount": text.strip()} # 否则按坐标切分:金额通常在右侧 # 这里简化处理,实际按你的列定义调整 return {"company": text.strip(), "amount": ""} with open("ocr_output.json", encoding="utf-8") as f: rows = json.load(f) for row in rows: print(parse_row_by_bbox(row))跑出来如果金额和公司名各归各位,说明定位方向对了。这时候再用 Codex 帮你把整个入库函数重写一遍,加上列坐标判断和合并单元格处理。验证请求是否走通,可以看 Codex 的返回里有没有正常读到你的代码和 JSON,如果报 401 就是 Key 没配对,报 404 就是 Base URL 写错了。
实测下来,最容易踩的坑是 bbox 坐标系不统一。有的 OCR 输出用左上角为原点,有的用左下角,x 和 y 的含义也不一样。Codex 能帮你看出代码里有没有做坐标归一化,但具体阈值还得你根据实际 PDF 调。
5. 本篇常见错排查
报错一:401 Unauthorized
Key 没填对,或者环境变量没生效。检查echo $OPENAI_API_KEY有没有输出,Codex 配置里的env_key名字和实际环境变量名是否一致。
报错二:404 Not Found
Base URL 写成了https://taotoken.net/api/带斜杠,或者写成了别的路径。正确写法是https://taotoken.net/api,不带尾部斜杠。
报错三:Codex 读不到本地文件
Codex CLI 默认只能读当前工作目录下的文件。如果你把 OCR 输出放在别的目录,要么cd过去,要么在提示词里给绝对路径。别把文件路径写成相对路径还指望它自己找。
报错四:金额还是串到公司名
大概率是 bbox 阈值设错了。打印几条数据的 bbox 看看,金额列的 x 坐标范围是多少,公司名列的 x 坐标范围是多少,取中间值当阈值。如果两列有重叠,说明 PDF 本身列就没对齐,得先修 PDF 解析而不是改入库代码。
报错五:合并单元格导致整行文本错乱
OCR 对合并单元格的处理通常是「把跨列文本拼成一行」。这时候不能只靠 bbox 切分,得结合表格结构识别。Codex 可以帮你写一个基于坐标聚类的列检测逻辑,但前提是你把合并单元格的样本数据给它看。
6. 定位完错位,再调入库字段
排障的终点不是「Codex 说哪里错了」,而是你根据定位结果把入库字段改对。具体来说,如果确认是列坐标没用上,就把parse_row改成按 bbox 的 x 坐标判断列归属;如果是合并单元格,就在解析前先做列检测,把跨列文本拆开;如果是金额字段映射写反了,就把company和amount的赋值对调。
改完之后,拿一批历史数据回灌验证,看检索能不能按公司名和金额分别查到。这时候如果还要继续用 Codex 帮你写测试用例或者批量校验脚本,Key 和 Base URL 还是那套:打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建 Key,Base URL 填 https://taotoken.net/api 。长期做编码和 Agent 任务的话,Coding Plan 页面有按量套餐可以选。
最后留一个实用技巧:把 OCR 输出、入库代码、修正后的解析函数放在同一个目录里,Codex 一次就能读全,不用来回贴。排障效率高很多。