Skip to content
LlamaParse 文档版本 跟随上游 源快照 89746575 更新于 2026-10-16

Beta

本示例演示如何把每一个表单页拿到手作为结构化 JSON —— 小节、带标签及其填写值与复选框状态的字段、可填写网格,每个字段都带边界框 —— 与 Parse 照常返回的 markdown 和 items 输出并列,并说明如何从结果里把这份 JSON 读回来。

在这些情况下用它:

  • 把填好的表单装载进你自己的系统,以表单上印着的字段标识为键(1a、Part III、第 13 格)。
  • 区分空白字段与已填字段,并直接读取复选框与签名状态,而不是从文本去推断。
  • 搭一个复核界面,高亮显示每个字段取值来自页面上的什么位置。

表单分析是在常规解析之外额外跑的一步。 Parse 会检出哪些页是表单,只在这些页上跑增强表单分析。

用起来很直接:解析时设上 forms: "enrich",然后从结果上读 forms —— 用 expand=["forms"] 内联拿到,或用 expand=["forms_content_metadata"] 作为可下载文件拿到。

每含一个表单页,就在档位价格之上多加 10 credit。未检出表单的页返回空的 forms 列表,不产生额外费用。

Beta

表单增强输出处于 beta 阶段,仍在积极改进中,输出的形状与字段用词都可能变化。

1. 准备

设置 API key,让各 SDK 自动读取:

终端窗口

export LLAMA_CLOUD_API_KEY="llx-..."
  • Python
  • TypeScript
  • Go
  • Java
  • CLI
  • cURL

安装 SDK:

终端窗口

pip install "llama-cloud>=2.15"
from llama_cloud import LlamaCloud

client = LlamaCloud()  # reads LLAMA_CLOUD_API_KEY from the environment

安装 SDK:

终端窗口

npm install @llamaindex/llama-cloud
import LlamaCloud from '@llamaindex/llama-cloud';

const client = new LlamaCloud(); // reads LLAMA_CLOUD_API_KEY from the environment

安装 SDK:

终端窗口

go get github.com/run-llama/llama-parse-go
import (
  "context"

  llamacloud "github.com/run-llama/llama-parse-go"
)

ctx := context.Background()
client := llamacloud.NewClient() // reads LLAMA_CLOUD_API_KEY from the environment

把 SDK 加进你的构建:

implementation("ai.llamaindex:llama-cloud:1.6.0")
import ai.llamaindex.llamacloud.client.LlamaCloudClient;
import ai.llamaindex.llamacloud.client.okhttp.LlamaCloudOkHttpClient;

// reads LLAMA_CLOUD_API_KEY from the environment
LlamaCloudClient client = LlamaCloudOkHttpClient.fromEnv();

安装 CLI:

终端窗口

go install github.com/run-llama/llama-parse-cli/cmd/llp@latest

llp 会从环境里读取 LLAMA_CLOUD_API_KEY(也可用 --api-key 传入)。

什么都不用装。下面的命令只用 curl 与 jq,并从环境里读 LLAMA_CLOUD_API_KEY。

2. 用 forms: "enrich" 解析

把 processing_options.forms 设为 "enrich",并在 expand 里加上 forms。这是请求侧唯一必需的改变。这一轮分析可以在 cost_effective、agentic 与 agentic_plus 档位上跑。

在 Python 与 TypeScript 里,SDK 会阻塞到任务结束;在 Go、Java、CLI 与 cURL 里,你要先创建任务、轮询到终态,再取结果。

下载一份填了测试数据的样例 Form W-2,存为 w2.pdf:

终端窗口

curl -fL -o w2.pdf https://cloud.llamaindex.ai/assets/demos/parse/form-w2.pdf
  • Python
  • TypeScript
  • Go
  • Java
  • CLI
  • cURL
# 1) Upload the file
file = client.files.create(file="w2.pdf", purpose="parse")

# 2) Parse with the form pass on, and ask for the structured JSON back
result = client.parsing.parse(
    file_id=file.id,
    tier="agentic",
    version="latest",
    processing_options={"forms": "enrich"},
    expand=["forms"],
)
import fs from 'fs';

// 1) Upload the file
const file = await client.files.create({
  file: fs.createReadStream('w2.pdf'),
  purpose: 'parse',
});

// 2) Parse with the form pass on, and ask for the structured JSON back
const result = await client.parsing.parse({
  file_id: file.id,
  tier: 'agentic',
  version: 'latest',
  processing_options: { forms: 'enrich' },
  expand: ['forms'],
});
// 1) Upload the file
f, err := os.Open("w2.pdf")
if err != nil {
  log.Fatal(err)
}
defer f.Close()
file, err := client.Files.New(ctx, llamacloud.FileNewParams{
  File:    f,
  Purpose: "parse",
})
if err != nil {
  log.Fatal(err)
}

// 2) Parse with the form pass on
job, err := client.Parsing.New(ctx, llamacloud.ParsingNewParams{
  FileID:  llamacloud.String(file.ID),
  Tier:    llamacloud.ParsingNewParamsTierAgentic,
  Version: llamacloud.ParsingNewParamsVersionLatest,
  ProcessingOptions: llamacloud.ParsingNewParamsProcessingOptions{
    Forms: "enrich",
  },
})
if err != nil {
  log.Fatal(err)
}

// expand is a GET parameter — poll until terminal, then fetch the forms JSON
getParams := llamacloud.ParsingGetParams{Expand: []string{"forms"}}
result, err := client.Parsing.Get(ctx, job.ID, getParams)
if err != nil {
  log.Fatal(err)
}
for result.Job.Status != "COMPLETED" && result.Job.Status != "FAILED" && result.Job.Status != "CANCELLED" {
  time.Sleep(2 * time.Second)
  result, err = client.Parsing.Get(ctx, job.ID, getParams)
  if err != nil {
    log.Fatal(err)
  }
}
if result.Job.Status != "COMPLETED" {
  log.Fatalf("parse ended as %s", result.Job.Status)
}
// 1) Upload the file
FileCreateResponse file = client.files().create(FileCreateParams.builder()
        .file(Paths.get("w2.pdf"))
        .purpose("parse")
        .build());

// 2) Parse with the form pass on
ParsingCreateResponse job = client.parsing().create(ParsingCreateParams.builder()
        .fileId(file.id())
        .tier(ParsingCreateParams.Tier.AGENTIC)
        .version(ParsingCreateParams.Version.LATEST)
        .processingOptions(ParsingCreateParams.ProcessingOptions.builder()
                .forms(ParsingCreateParams.ProcessingOptions.Forms.ENRICH)
                .build())
        .build());

// expand is a query parameter — poll until terminal, then fetch the forms JSON
ParsingGetParams getParams = ParsingGetParams.builder()
        .jobId(job.id())
        .addExpand("forms")
        .build();
ParsingGetResponse result = client.parsing().get(getParams);
while (!result.job().status().equals(ParsingGetResponse.Job.Status.COMPLETED)
        && !result.job().status().equals(ParsingGetResponse.Job.Status.FAILED)
        && !result.job().status().equals(ParsingGetResponse.Job.Status.CANCELLED)) {
    Thread.sleep(2000);
    result = client.parsing().get(getParams);
}
if (!result.job().status().equals(ParsingGetResponse.Job.Status.COMPLETED)) {
    throw new RuntimeException("parse ended as " + result.job().status());
}

终端窗口

# Upload the document
FILE_ID=$(llp files create \
  --file w2.pdf \
  --purpose parse | jq -r '.id')

# Start a parse job with the form pass on
JOB_ID=$(llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --processing-options.forms enrich | jq -r '.id')

# Poll until the job reaches a terminal status
while true; do
  STATUS=$(llp parsing get --job-id "$JOB_ID" | jq -er '.job.status') || break
  case "$STATUS" in COMPLETED|FAILED|CANCELLED) break ;; esac
  sleep 2
done

# Fetch the forms JSON
llp parsing get --job-id "$JOB_ID" --expand forms | jq -r 'if .job.status == "COMPLETED" then .forms.pages[]
  else "Parse job failed: \(.job.error_message // .)" end'

上传文件、开启 forms 启动任务、轮询结果端点直到 job.status 进入终态,然后用 expand=forms 取回那份结构化 JSON:

终端窗口

FILE_ID=$(curl -s -X POST 'https://api.cloud.llamaindex.ai/api/v1/beta/files' \
  -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" \
  -F 'purpose=parse' \
  -F 'file=@w2.pdf;type=application/pdf' | jq -r '.id')

JOB_ID=$(curl -s -X POST 'https://api.cloud.llamaindex.ai/api/v2/parse' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" \
  --data "{
    \"file_id\": \"$FILE_ID\",
    \"tier\": \"agentic\",
    \"version\": \"latest\",
    \"processing_options\": { \"forms\": \"enrich\" }
  }" | jq -r '.id')

# Poll until the job reaches a terminal status
while true; do
  STATUS=$(curl -sS --fail --retry 3 "https://api.cloud.llamaindex.ai/api/v2/parse/$JOB_ID" \
    -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" | jq -er '.job.status') || break
  case "$STATUS" in COMPLETED|FAILED|CANCELLED) break ;; esac
  sleep 2
done

# Fetch the forms JSON
curl -s "https://api.cloud.llamaindex.ai/api/v2/parse/$JOB_ID?expand=forms" \
  -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" \
  | jq -r 'if .job.status == "COMPLETED" then . else "Parse job failed: \(.job.error_message // .)" end'

在 Web 界面里,到解析设置的 Processing Options > Forms 下打开 Enriched forms output。(目前只对付费账户可见。)文档解析完成后,结果页会多出一个 Forms 标签,既展示结构化 JSON,也在页面上高亮每个字段。

在 `fast` 档上不可用

fast 档不跑表单分析。在 fast 上请求 forms: "enrich" 会返回校验错误。

3. 读 forms 结果

余下的步骤 —— 从结果里读出结构化 JSON 并遍历其字段 —— 用 Python 演示。forms 的形状在所有 SDK 与原始 JSON 里完全一致,只有字段访问语法不同。

result.forms.pages 每页一条记录。下面这段摘自一份填好的 W-2(已做删减):

{
  "pages": [
    {
      "page_number": 1,
      "page_width": 612,
      "page_height": 792,
      "success": true,
      "forms": [
        {
          "json": [
            {
              "type": "field", "field": "text", "id": "1",
              "label": "Wages, tips, other compensation", "value": "29,513",
              "bbox": [{ "x": 349.2, "y": 96.5, "w": 114.0, "h": 12.2 }]
            },
            {
              "type": "field", "field": "text", "id": "d",
              "label": "Control number", "isEmpty": true,
              "bbox": [{ "x": 38.1, "y": 167.8, "w": 291.8, "h": 12.3 }]
            },
            {
              "type": "field", "field": "multi_select", "id": "13",
              "valueItems": [
                { "type": "field", "field": "checkbox", "label": "Statutory employee", "value": true },
                { "type": "field", "field": "checkbox", "label": "Retirement plan", "value": false },
                { "type": "field", "field": "checkbox", "label": "Third-party sick pay", "value": false }
              ]
            },
            {
              "type": "section", "id": "15",
              "items": [
                { "type": "field", "field": "text", "label": "State", "value": "SC" },
                { "type": "field", "field": "text", "label": "Employer's state ID number", "value": "00-0000056" }
              ]
            }
          ],
          "list": {
            "type": "list",
            "ordered": false,
            "md": "- [1] Wages, tips, other compensation: 29,513\n- [d] Control number:\n- [13]\n  - [x] Statutory employee\n  - [ ] Retirement plan\n  - [ ] Third-party sick pay\n- [15]\n  - State: SC\n  - Employer's state ID number: 00-0000056",
            "items": [ ... ]
          }
        }
      ]
    }
  ]
}

每张表单都同时带着两份内容:json 是结构化 JSON,list 是打平后的项目符号列表,其 md 可以原样塞进提示词。

这份结构化 JSON 有四种节点类型:

type字段含义
sectionid, label, items表单上印着的一个分组(Part III、第 15 格)。items 按阅读顺序存放子节点。
fieldfield, id, label, value, isEmpty, valueItems, bbox一个待填项。field 可为 text、checkbox、single_select、multi_select 或 signature。
tableid, label, columns, rows, bbox一张可填写的网格。每个单元格是字符串、空白时为 null,或是 { "items": [...] } 并装着该单元格自有节点(例如一整列复选框)。html 把该网格渲染成 HTML 表格。
textvalue, bbox不属于任何字段、标题或表格的印刷文字,例如标题、说明或备注。

value 怎么读,取决于字段的种类:

  • text —— value 是原样保存的填入文本。印了但空着的字段是 isEmpty: true 且没有 value。
  • checkbox 与 signature —— value 是布尔值,分别表示是否勾选、是否签署。
  • single_select 与 multi_select —— 本身没有 value,改由 valueItems 列出各选项。一个选项通常是一个自带布尔 value 与印刷 label 的 checkbox 字段,但也可能是一个 text 字段,或者一层 section,把一个无标签的复选框与它的手写 text 字段归成一组(即"勾框并填写姓名"那种选项)—— 此时印刷出来的题注就是这个 section 的 label。

bbox 装着环绕该字段可填写区域的一个或多个框,与 items 输出使用同一套页面点坐标系。页面上的尺寸取自该页条目上的 page_width 与 page_height。换算到截图的像素尺寸,方法与细粒度边界框示例 里一致。

读 forms 结果之前一定先看 success。 表单分析失败的那页会返回 { "page_number": N, "success": false, "error": "..." },它的 markdown 与 items 也会被标为失败,并和任何其他失败页一样计入任务的页错误容忍度。若某页的表单分析与常规解析两轮都失败,并不会在容忍度上被重复计数。

4. 遍历结构化 JSON

由于小节可以嵌套、表格单元格也能装着表单字段(例如一整列复选框),你要同时下钻这两处,才能访问到每一个字段:

def fields(nodes):
    """Yield every field in a form's structured JSON, descending into sections and table cells."""
    for node in nodes:
        if node.type == "section":
            yield from fields(node.items)
        elif node.type == "table":
            for row in node.rows:
                for cell in row:
                    if cell is not None and not isinstance(cell, str):
                        yield from fields(cell.items)
        elif node.type == "field":
            yield node

for page in result.forms.pages:
    if not page.success:
        print(f"page {page.page_number} failed: {page.error}")
        continue
    for form in page.forms:
        for field in fields(form.json_):
            if field.field in ("single_select", "multi_select"):
                # An option is a checkbox, a text field, or a section grouping a
                # checkbox with its write-in; it is chosen when a checkbox in it is checked.
                chosen = [
                    opt.label
                    for opt in field.value_items or []
                    if any(f.field == "checkbox" and f.value for f in fields([opt]))
                ]
                print(f"[{field.id}] {field.label}: {chosen}")
            else:
                print(f"[{field.id}] {field.label}: {field.value!r}")

Python SDK 里有两处命名差异:API 响应中的驼峰字段以 snake_case 形式暴露(valueItems 变成 value_items、isEmpty 变成 is_empty),而 json 字段暴露为 json_,因为裸名会与 SDK 模型类上的一个方法冲突。原始响应与 TypeScript SDK 用的都是 API 的驼峰字段名。

5. 下载表单文件

长文档请改取预签名地址,而不是内联 JSON。设了 forms: "enrich" 时,expand=["forms_content_metadata"] 会在 result_content_metadata 下加一条 forms 记录,内含 size_bytes、一个 exists 标记与一个 presigned_url。若该任务从未跑过表单分析 —— 没设这个选项,或任务早于表单增强能力 —— 这条记录就不存在,所以取键时要先做判空:

result = client.parsing.get(job_id=result.job.id, expand=["forms_content_metadata"])

forms_file = (result.result_content_metadata or {}).get("forms")
if forms_file is None:
    raise RuntimeError('Forms file missing — was `forms: "enrich"` set?')

print(f"Forms file: {forms_file.size_bytes} bytes")
print(f"URL:        {forms_file.presigned_url}")

该文件的形状与内联结果相同,都是 { "pages": [...] }。

预签名 URL 是临时的。 请尽快下载,或再调一次 client.parsing.get(job_id=...) 生成新地址。

另请参阅

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。