主题
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-cloudimport 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-goimport (
"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@latestllp 会从环境里读取 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 | 字段 | 含义 |
|---|---|---|
section | id, label, items | 表单上印着的一个分组(Part III、第 15 格)。items 按阅读顺序存放子节点。 |
field | field, id, label, value, isEmpty, valueItems, bbox | 一个待填项。field 可为 text、checkbox、single_select、multi_select 或 signature。 |
table | id, label, columns, rows, bbox | 一张可填写的网格。每个单元格是字符串、空白时为 null,或是 { "items": [...] } 并装着该单元格自有节点(例如一整列复选框)。html 把该网格渲染成 HTML 表格。 |
text | value, 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=...)生成新地址。
另请参阅
- 配置 Parse → 表单增强输出 —— 请求侧参考
- 取回结果 —— 全部
expand取值,含forms与forms_content_metadata - 细粒度边界框 —— 页面其余部分的词、行、单元格框