space ocr
指南文章价格文档

space-ocr 与 LLM OCR 有何不同:可验证的结构化提取

space-ocr 与直接调用 LLM 做 OCR 的区别:字段会带着页面上的来源坐标和 verified 判定返回,需要复核的项则列在 review 里。

9 分钟阅读· 2026-08-31

你可以把一张收据或发票交给 GPT-4o、Gemini 或 Claude,让它读出总金额、供应商和明细行。多数情况下,返回的 JSON 看上去都挺像样。问题出在你想大批量地信任它的时候:模型返回的是一个字符串,而字符串没有地址。如果总金额返回的是 48,200,它到底读的是页面上哪几个像素?这个数字真的印在文档上,还是模型自己补了一个看起来合理的值?靠一次原始的 LLM 调用,你没法回答这些问题,除非自己再把整页重读一遍。

这道鸿沟,正是把通用 LLM 当作 OCR 工具与使用 space-ocr 之间的全部区别。space-ocr 并不反对 LLM。它目前在底层把一个 OCR 引擎(Google Cloud Vision)与负责结构化的 Gemini 搭配在一起——这是实现细节,而不是 API 契约的一部分。它多做的,是围绕模型搭建的那一层:它返回的值都会与 OCR 在页面上实际看到的内容做核对、给出判定,上传到表格之后再存成一行可查询的数据。这正是一次原始 LLM 调用留给你自己去做的两件事:每个值可验证的来源,以及无需自建数据库就能查询的结构化输出。

原始 LLM OCR 与 space-ocr 的对比

原始 LLM OCR(GPT-4o / Gemini / Claude)space-ocr
逐值定位一次 JSON 抽取调用返回的是文本,与独立 OCR 通道交叉核对过的来源坐标并不在契约里凡是能锚定的值都带一个框(xmin、ymin、xmax、ymax,位于 0–1000 网格上)和一个四点定向四边形;锚不上的值会以 nobox 出现在 review.flagged 里
逐值验证没有,你只能相信这个字符串判定在 cells[path].verified,原因在 review.reasons;核对本身放在 evidence 里——text_match、source,以及 match_ratio,也就是该值有多少字符能在页面检测到的符号中定位到
声明的规则校验逻辑得你自己写required、pattern、enum、min/max、near 与 not_near 随请求一起声明,由服务端判定,违规会落到 review.flagged
在上下文中核对某个值自己把文档重读一遍在应用里点一下某个单元格,它对应的精确区域就会在原图上高亮
输出形态取决于提示词的 JSON,每次运行都不一样固定的 schema:定义一次 fields,或让 autoFields 提议一份,data.values 就按这个形状返回
存储与查询得你自己搭POST /ocr/fields 只在响应里返回,不保存图像;上传到表格后,每一页落成一行,用 GET /view 查询(where、sort、select、limit、offset),不重新 OCR,也不计费
文种取决于模型和提示词日语、韩语、中文、英语等,自动识别,无需任何语言参数
接入自己的提示词、重试、解析、校验一整套流水线一次带 Bearer 密钥的 HTTPS 调用
✓ Verified

这里把“已验证”到底是什么意思说清楚,因为这一点很容易被夸大。语言模型并不输出坐标。它返回的是每个值的文本,外加词元(token)提示,然后引擎再把这段文本逐字符地与 Google Cloud Vision 在页面上检测到的符号做匹配。它把框落在这些真实存在的符号上,核对的结果则写进 cells[path].evidence:逐字符核对本身是 text_match,该值有多少比例的字符被定位到是 match_ratio,框是怎么找到的是 source。verified 是叠在上面的判定:只要这个单元格带上任何一条原因就是 false,核对跑过而没有任何原因就是 true,压根没有可核对的对象则是 null。匹配偏弱会立起 low_ratio 之类的原因,那条 path 随即出现在 review.flagged 里,这就是你转给人工的清单。values 是模型读出来的字符串,不是逐字节的副本,所以当你需要拿去和主数据做精确匹配时,请用 evidence.printed_text——OCR 在那组坐标上读到的字形。这并不是承诺模型永远不会出错:词元提示本身仍可能偏移,两个引擎也可能在同一处误读上达成一致。它的意思是:每个值都会与页面核对并给出结论,而不是凭信任照单全收。

POST /ocr/fields — 响应中的一个字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
  "status": "success",
  "data": {
    "values": {
      "vendor": "ACME Trading Co."
    },
    "cells": {
      "vendor": {
        "box": { "xmin": 120, "ymin": 84, "xmax": 512, "ymax": 118 },
        "quad": [
          { "x": 120, "y": 84 }, { "x": 512, "y": 84 },
          { "x": 512, "y": 118 }, { "x": 120, "y": 118 }
        ],
        "verified": true,
        "review": null,
        "evidence": {
          "text_match": true,
          "source": "vision_symbol_match",
          "match_ratio": 1.0,
          "ocr_confidence": 0.97
        }
      }
    },
    "review": {
      "unit": "field",
      "declared": 1,
      "returned": 1,
      "boxed": 1,
      "verified": 1,
      "flagged": []
    },
    "image": { "width": 1654, "height": 2339 }
  }
}

框返回在一个与图像尺寸无关的 0–1000 网格上,所以绘制时你要把它换算成像素:pixel_x = xmin / 1000 * image_width。宽和高请取自同一份响应里的 data.image——那是实际被读取的那一页(EXIF 旋转已经固化,大图也已经缩放过),而不是你发送的那个文件。那个四点四边形会贴合倾斜或旋转的扫描件,顶点按左上、右上、右下、左下排列。一旦有对不上的地方,同一条 path 就会以 { "path": "total", "reasons": ["text_mismatch"] } 的形式出现在 data.review.flagged 里,交给复核的人是一份可以逐条走完的清单,而不是一个还得自己定阈值的分数。对通用模型发起的 JSON 抽取调用不会附带这些,所以任何你想要的审计线索,都得自己手工拼出来。

从提取值到可查询的表格

一次原始的 LLM 调用到 JSON 就结束了。你还得把它持久化下来,而当你想问“本季度哪些发票超过 40,000?”时,你得先搭一个数据库和一层查询。POST /ocr/fields 同样到 JSON 为止——它只在响应里返回,不保留图像。不同之处在于,同一次提取可以走存储层:先按你的列建一张表(POST /create),再把页面传进去(POST /upload),每一页就成为一行,带着同样的 values、cells 和 review。之后查询就是一次 API 调用:GET /view,配合 where=total>=40000、sort=-invoice_date、select=vendor,total,再加上 limit 和 offset 做分页。它在服务端运行,不重新跑 OCR,也不计费。你还可以把这张表导出为 CSV(带 BOM 的 UTF-8,这样日语、韩语、中文文本和货币在 Excel 里都能正确打开,明细行数组也会展开成各自独立的行)。

什么时候原始 LLM 才是更合适的工具

当你只想做一次性的阅读、一段宽松的摘要,或是推理一份文档的含义时,通用 LLM 才是对的选择。“这份合同讲的是什么?”是一个交给模型的问题,而不是一个要带坐标的 OCR 问题。当你要大批量处理文档,并且需要每个值都可验证、结构一致、可查询时,就该用 space-ocr:应付账款自动化、费用对账、把名片导入 CRM,或是把积压的收据数字化。实话说,space-ocr 是一套以 LLM 为底、带有验证与存储层的 OCR,而不是要跟这些模型本身较劲。

计费按扫描量随用随付,可选月度套餐,每月还有一定额度的免费扫描,失败的扫描不计费。具体数字见价格页面。

做 OCR 时,space-ocr 会取代 GPT-4o 或 Gemini 吗?
不会。space-ocr 底层本身就用了 LLM,目前的组合是由 Google Cloud Vision 负责 OCR、Gemini 负责结构化——这是实现细节,不是 API 契约。区别在于围绕模型的那一层:每个值都会被匹配回页面上的符号并给出判定,上传到表格后,结果再作为一行可查询的数据存下来。它是一套以 LLM 为底的 OCR,而不是这些模型的替代品。
我怎么知道某个值不是模型编出来的?
看三个地方。cells[path].verified 是判定:带上任何一条原因就是 false,核对跑过且没有任何原因是 true,没有可核对的对象是 null。data.review.flagged 是值得复核的 path 清单,每一条都带 reasons。cells[path].evidence 里放的是核对本身(text_match、match_ratio、source),但这些键只出现在核对真的跑过的值上;键不在,意味着“无法判断”,而不是“没问题”。这是针对真实页面的核对,而不是承诺模型永远不会出错——两个引擎也可能在同一处误读上达成一致。
space-ocr 返回的坐标是什么格式?
一个由四个整数组成的框——xmin、ymin、xmax、ymax,位于归一化的 0–1000 网格上,另外还有一个四点定向四边形(quad),用于倾斜或旋转的页面,顶点按左上、右上、右下、左下排列。换算成像素时以同一份响应里的 data.image 为准,例如 pixel_x = xmin / 1000 * image_width。
不自己搭数据库,能查询提取出来的数据吗?
可以,前提是页面已经进到表格里。POST /ocr/fields 只返回 JSON,什么都不保存;先建一张表(POST /create),再把页面传上去(POST /upload),每一页就成为一行。之后 GET /view 会在服务端用 where、sort、select、limit 和 offset 来过滤它们(例如 where=total>=40000)。它不重新跑 OCR,也不计费,你还可以把这张表导出为 CSV。
我需要告诉 space-ocr 文档是什么语言吗?
不需要。语言会在日语、韩语、中文、英语等之间自动识别,没有任何语言参数需要设置。
它能读 PDF 吗?
网页应用会把 PDF 的每一页栅格化成图像,再对它做 OCR。API 本身接收的是栅格图像(JPEG、PNG、GIF、BMP、TIFF、WebP),每次调用传一张图,所以发送前请先把 PDF 页面转成图像。
我该怎么调用它?
向 POST /ocr/fields 发送一次 HTTPS 请求,带上 Bearer spocr_ 密钥,传入图像和一份 fields schema(也可以用 autoFields: true 让它提议一份)。响应把两件事分开:data.values 里只有你声明的 schema,data.cells[path] 里放着每个值的 box、quad、verified、review 和 evidence,data.review 则汇总了需要复核的内容。