充分利用GPT-5.4进行视觉与文档理解
技术背景
传统文档理解流水线通常需要拼接 OCR、版面检测与自定义解析器,面对密集扫描件、手写表单、工程图和图表报告时容易在降采样阶段丢失小字段、注释与低对比度文字,且跨区域组合推理能力弱。GPT-5.4 将视觉感知与多步推理统一到单次模型调用中,但默认配置并非对所有文档都最优:图像分辨率、输出详细程度、推理预算与是否启用工具会显著影响准确率、延迟与成本。因此需要一套可复用的配置决策框架,帮助开发者在普通问答、逐字转录、区域定位与多轮视觉检查等不同任务间做出权衡。
核心原理
核心数据流是:客户端通过 client.responses.create 提交 input 消息列表,content 中包含 input_text 与 input_image 块;input_image.detail 控制视觉编码分辨率,auto 由模型选择,original 保留小视觉信号;text.verbosity 控制输出是压缩还是贴近原文;reasoning.effort 为多步视觉推理分配更多计算;tools 中的 code_interpreter 允许模型在回答前缩放、裁剪、旋转或检查子区域。输出为响应对象,response.output_text 提供最终文本,配合 text.format 的 json_object 或 json_schema 可强制结构化输出,再用 json.loads 解析。关键设计决策是:感知问题调 detail,保真问题调 verbosity,组合推理问题调 effort,证据分散问题加工具。
架构分析
整体架构分为四层。输入层负责将本地图像编码为 data URL 或上传为 file 对象,统一 detail 策略。推理层由 Responses API 承载,按任务选择 verbosity、reasoning.effort 与 tools 组合。约束层通过 json_schema 与 strict 模式限定字段、枚举与坐标范围,边界框统一采用左上角为原点的 0..999 归一化坐标,便于裁剪、绘制与下游消费。后处理层在本地将归一化坐标还原为像素坐标,用 PIL 绘制标注或裁剪目标区域,必要时对裁剪图重跑聚焦提示。该架构将通用沙箱能力与窄范围本地裁剪解耦,使受限环境也能获得多轮检查的大部分收益。
完整实战教程
GPT-5.4 是面向真实世界多模态工作负载的一次重大进步。
过去,密集扫描件、手写表单、工程图和图表密集型报告等文档往往会让视觉系统不堪重负,或需要将 OCR、版面检测和自定义解析器拼接在一起;如今,借助 GPT-5.4,这些文档通常可以在单次模型推理中完成解读与推理。
然而,模型配置是解锁 SOTA 结果的关键。围绕图像细节、详细程度、推理强度和工具使用的小小选择,都可能显著影响性能。
本 notebook 聚焦于文档工作负载中杠杆率最高的几项调整:图像细节、详细程度、推理强度和工具使用。目标是展示每一项何时重要、如何改变输出,以及如何选择既稳健又实用的配置。
本笔记本中的所有示例都通过 client.responses.create(...) 使用 Responses API。我们所说的“设置”是指传入该调用的请求参数。
输入结构
input:一个类似消息的对象列表(通常是一个{ "role": "user", "content": [...] })content:一个带类型的块列表,通常为:{ "type": "input_text", "text": "..." }{ "type": "input_image", "image_url": "...", "detail": "auto" | "original" }
本笔记本中使用的参数
- 图像细节(
input_image.detail):控制视觉处理所使用的图像分辨率。对于大多数页面使用"auto";当文本很小、为手写体或扫描质量较低时,使用"original"。 - 详细程度(
text={"verbosity": ...}):影响文本输出是更压缩还是更贴近原文。较高的详细程度有助于忠实转录。 - 推理强度(
reasoning={"effort": ...}):在图像已经可读的情况下,为多步视觉推理(图表、表格、示意图)分配更多计算资源。 - 工具使用(
tools=[...]+instructions=...):可选地让模型在回答前使用 Code Interpreter 等工具进行缩放/裁剪/检查;当单次回答已足够时,可省略工具。
一个最小请求如下所示:
response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "Extract the total amount due."},
{
"type": "input_image",
"image_url": "data:image/png;base64,...",
"detail": "auto",
},
],
}
],
)
输出结构
- 模型返回一个包含一个或多个输出项的响应对象。
- 在本笔记本中,我们主要使用
response.output_text作为获取最终文本的便捷方式。 - 对于结构化输出,你仍然会收到文本——你只需通过
text={"format": ...}要求模型将其格式化为 JSON,然后使用json.loads(response.output_text)进行解析。
快速决策指南
将此作为起点。一个很好的经验法则是从简单开始,然后根据失败模式调整设置。
| 如果你的任务看起来像这样 | 从这个设置开始 | 为什么 |
|---|---|---|
| 普通文档问答或提取 | detail="auto" | 对于可读页面,这是摩擦最小的默认设置 |
| 密集扫描件、截图、手写或微小标签 | detail="original" | 保留经常丢失的小视觉信号 |
| 逐字转录或 Markdown 转换 | text={"verbosity": "high"} | 鼓励模型保留更多布局,减少改写 |
| 区域定位 | 在固定的 0..999 网格中要求 [x_min, y_min, x_max, y_max] | 易于裁剪、绘制、调试并输入下游系统 |
| 跨多个区域的图表、表格、表单或绘图问答 | 将推理努力提高到 'high' 或 'xhigh' | 改善多步视觉推理 |
| 多轮视觉检查 | 添加 Code Interpreter | 当人类在回答前需要放大、裁剪、旋转或检查多个子区域时效果最佳 |
设置
在运行此 notebook 之前,请确保你已在环境中设置了 OPENAI_API_KEY。如果你还没有 API 密钥,可以在 platform.openai.com 创建一个。
export OPENAI_API_KEY="your_api_key_here"
如果需要,安装 notebook 依赖:
pip install --upgrade openai pillow
import base64
import json
import mimetypes
import tempfile
from pathlib import Path
from IPython.display import Markdown, display
from PIL import Image, ImageDraw
from openai import OpenAI
client = OpenAI()
### Helper function
def image_to_data_url(image_path: str | Path) -> str:
path = Path(image_path)
if not path.exists():
raise FileNotFoundError(f"Image not found: {path}")
mime_type = mimetypes.guess_type(path.name)[0] or "image/png"
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
return f"data:{mime_type};base64,{encoded}"
1. 为密集页面和手写内容增加图像细节
detail 参数控制模型处理图像时使用的分辨率。大多数应用应从 detail="auto" 开始,让模型选择合适的分辨率。然而,当页面包含手写内容、小标签、密集表格、低对比度扫描件或带有细小文字的截图时,切换到 detail="original" 可以显著改善结果。如果模型大体正确但总是遗漏小字段或注释,增加图像细节通常是第一个值得尝试的调整。
此示例有意包含了小的电子邮件和电话字段,而不仅仅是较大的手写姓名。当图像被降采样时,这些细节往往是最先退化的。

handwriting_prompt = """
Read the handwritten earthquake insurance application and return JSON with these keys:
- applicant_name
- applicant_email
- applicant_home_phone
- applicant_cell_phone
- co_applicant_name
- co_applicant_email
- co_applicant_home_phone
- co_applicant_work_phone
- effective_date
- expiration_date
- dwelling_coverage_limit_usd
- square_footage
- year_of_construction
"""
handwritten_form_path = "assets/images/3732016e8a04fcad.png"
handwriting_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": handwriting_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(handwritten_form_path),
"detail": "original",
},
],
}
],
text={"format": {"type": "json_object"}
},
)
handwriting_result = json.loads(handwriting_response.output_text)
display(Markdown(f"### Extracted insurance form\n\n```json\n{json.dumps(handwriting_result, indent=2, ensure_ascii=False)}\n```"))
2. 提高详细程度以实现忠实转录
当被要求转录文档时,多模态模型倾向于压缩布局。它们保留含义,但可能会简化空白、换行和类似表格的布局。这种行为对于问答通常是有益的,但对于 OCR 风格的任务则不然。
提高详细程度 - text={"verbosity": "high"} 会促使模型进行更字面的呈现和精确的转录。将其用于 OCR 风格的工作负载以及完整性和格式保真度至关重要的定向提取。
下面的示例是 Ticket To The Arts 面板,要求对所有四个列表进行完整转录,同时保持图像细节固定。

section_prompt = "Transcribe everything in the Ticket To The Arts section. Do not summarize or paraphrase. Do not add any additional text."
newspaper_path = "../../examples/multimodal/assets/images/d78ee8da5e1ec611.png"
section_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": section_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(newspaper_path),
"detail": "original",
},
],
}
],
text={"verbosity": "high"},
)
display(Markdown(f"### Transcription preview\n\n```text\n{section_response.output_text}\n```"))
3. 当图像可读但答案需要组合推理时,提高推理投入
一旦图像可读,下一个瓶颈往往不是感知,而是推理。这在那些答案依赖于组合图像多个部分的信息、而非读取单个字段的文档中尤为明显。图表、表格、技术示意图以及密集的视觉布局通常都属于这一类。
在这些情况下,提高推理投入 reasoning={"effort": "high"} 比提高图像细节更有帮助。模型已经看到了内容。它需要的是更强的能力来连接标签、比较区域、遵循结构,并正确计算出最终答案。
以下是一些不同类型的任务或图像示例,在这些场景中更高的推理能力会有所帮助。
示例:平面图推理
下面的平面图是一个很好的例子,说明任务不仅仅是转录。为了正确回答,模型必须读取房间标签、解读空间关系,并利用可见的尺寸来计算数值。

floorplan_prompt = """
Inspect this apartment floorplan and return JSON with these keys:
- total_named_rooms_excluding_hallways_and_closets
- largest_room
- room_immediately_east_of_kitchen
- room_immediately_south_of_study
- bedroom_1_total_area_ft2
- bedroom_2_total_area_ft2
Rules:
- Use the room labels and dimension annotations that are visible on the drawing.
- Return integers for numeric fields.
- Return JSON only.
"""
floorplan_path = "../../examples/multimodal/assets/images/d1830872ca6b66e2.png"
floorplan_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": floorplan_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(floorplan_path),
"detail": "original",
},
],
}
],
reasoning={"effort": "high"},
text={"format": {"type": "json_object"}},
)
floorplan_result = json.loads(floorplan_response.output_text)
display(Markdown(f"### Floorplan reasoning result\n\n```json\n{json.dumps(floorplan_result, indent=2, ensure_ascii=False)}\n```"))
示例:图表理解
同样的模式也出现在图表理解中。如果任务只是读取标题或识别一个绘制值,默认设置可能就足够了。但如果答案取决于比较多个系列、追踪相邻区间内的变化,或估计随时间变化的趋势,那么推理就成了限制因素。

chart_prompt = """
Inspect this line chart and return a JSON with these keys:
- largest_qoq_increase: {"channel": ..., "from_quarter": ..., "to_quarter": ..., "approx_delta_millions": ...}
- largest_qoq_drop: {"channel": ..., "from_quarter": ..., "to_quarter": ..., "approx_delta_millions": ...}
- fastest_growing_channel_overall
Rules:
- Use approximate values only when exact values are not printed.
- Base the answer on the visible lines and quarter labels.
- Return JSON only.
"""
chart_path = "assets/images/ee8d98d1d3d5208f.png"
chart_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": chart_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(chart_path),
"detail": "original",
},
],
}
],
reasoning={"effort": "high"},
text={"format": {"type": "json_object",}},
)
chart_result = json.loads(chart_response.output_text)
display(Markdown(f"### Chart reasoning result\n\n```json\n{json.dumps(chart_result, indent=2, ensure_ascii=False)}\n```"))
示例:在密集括号表上进行长程视觉推理
密集的锦标赛括号表是推理的强候选对象,因为模型必须在拥挤的布局中沿着路径追踪,保持左右区域区分,并在不丢失结构线索的情况下识别最终结果。

bracket_prompt = """
Inspect this tournament bracket image and return JSON with these keys:
- left_bracket_title
- right_bracket_title
- mens_champion_team
- womens_champion_team
- mens_runner_up_team
- womens_runner_up_team
Rules:
- Use the visible central championship score boxes.
- Team labels may include seeds; if you include a seed, keep it attached to the same string.
- Return JSON only.
"""
bracket_path = "../../examples/multimodal/assets/images/4846d30d77a50526.png"
bracket_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": bracket_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(bracket_path),
"detail": "original",
},
],
}
],
reasoning={"effort": "high"},
text={"format": {"type": "json_object"}},
)
bracket_result = json.loads(bracket_response.output_text)
display(Markdown(f"### Bracket reasoning result\n\n```json\n{json.dumps(bracket_result, indent=2, ensure_ascii=False)}\n```"))
4. 使用代码解释器进行多轮检查和边界框定位
有些文档任务用人类的方式解决更容易:检查整个页面,放大或裁剪某个区域,再检查另一个区域,然后将证据整合成最终答案。
代码解释器在以下视觉任务中特别有用:
- 页面密集,证据分散在多个区域
- 模型需要缩放、裁剪、旋转或运行中间检查
- 定性准确性比最低延迟更重要
对于定位任务(包括边界框),需要提供对代码解释器的访问权限,以及严格的坐标约定,如 [x_min, y_min, x_max, y_max],并采用固定的坐标空间,例如 0..999,原点位于左上角。
在实践中,这种组合(代码解释器工具使用 + 明确的框格式)通常比单次视觉调用更可靠、更可重复。

bbox_prompt = """
Find the license plate number for Vehicle 1 and Vehicle 2 in this police report form.
It is written after License # and is a 7-digit number.
Do not guess or infer the license plate number.
Return JSON with this schema:
{
"b": [
{"label": "vehicle_1_license_plate", "bbox": [x_min, y_min, x_max, y_max]},
{"label": "vehicle_2_license_plate", "bbox": [x_min, y_min, x_max, y_max]}
]
}
Use discrete normalized coordinates between 0 and 999.
Return JSON only.
"""
police_form_path = "../../examples/multimodal/assets/images/56c31250eaddab57.png"
with Path(police_form_path).open("rb") as asset_file:
uploaded_file = client.files.create(file=asset_file, purpose="user_data")
bbox_schema = {
"type": "object",
"properties": {
"b": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string",
"enum": ["vehicle_1_license_plate", "vehicle_2_license_plate"],
},
"bbox": {
"type": "array",
"items": {"type": "integer", "minimum": 0, "maximum": 999},
"minItems": 4,
"maxItems": 4,
},
},
"required": ["label", "bbox"],
"additionalProperties": False,
},
"minItems": 2,
"maxItems": 2,
}
},
"required": ["b"],
"additionalProperties": False,
}
bbox_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": bbox_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(police_form_path),
"detail": "original",
},
],
}
],
reasoning={"effort": "high"},
text={
"format": {
"type": "json_schema",
"name": "plate_bboxes",
"schema": bbox_schema,
"strict": True,
}
},
instructions=(
"You are an expert document analyst. Use Code Interpreter before answering. "
"Inspect the uploaded file, crop or zoom if needed, then answer in JSON."
),
tools=[
{
"type": "code_interpreter",
"container": {
"type": "auto",
"memory_limit": "4g",
"file_ids": [uploaded_file.id],
},
}
],
)
bbox_results = json.loads(bbox_response.output_text)["b"]
display(Markdown(f"### Detected plate regions\n\n```json\n{json.dumps(bbox_results, indent=2, ensure_ascii=False)}\n```"))
annotated = Image.open(police_form_path).convert("RGB")
draw = ImageDraw.Draw(annotated)
width, height = annotated.size
palette = ["red", "dodgerblue", "limegreen", "orange", "magenta", "cyan"]
for index, item in enumerate(bbox_results):
color = palette[index % len(palette)]
x_min, y_min, x_max, y_max = item["bbox"]
pixel_bbox = [
round(x_min * (width - 1) / 999),
round(y_min * (height - 1) / 999),
round(x_max * (width - 1) / 999),
round(y_max * (height - 1) / 999),
]
draw.rectangle(pixel_bbox, outline=color, width=4)
draw.text((pixel_bbox[0] + 4, max(0, pixel_bbox[1] - 16)), item["label"], fill=color)
display(annotated)

5. 如果无法使用代码解释器,请构建一个窄范围的裁剪并重跑流水线
在受限环境中,你可能不希望授予模型一个通用的 Python 沙箱。一个实用的替代方案是两阶段工作流:
- 定位你关心的字段或区域
- 在本地裁剪该区域
- 对裁剪后的图像重新运行一个更小、更聚焦的提示
这通常能在保持控制面较小的同时,恢复多轮检查的大部分价值。
target_region = next(
item for item in bbox_results if item["label"] == "vehicle_2_license_plate"
)
source_image = Image.open(police_form_path).convert("RGB")
width, height = source_image.size
x_min, y_min, x_max, y_max = target_region["bbox"]
pixel_bbox = [
round(x_min * (width - 1) / 999),
round(y_min * (height - 1) / 999),
round(x_max * (width - 1) / 999),
round(y_max * (height - 1) / 999),
]
padding = 0.18
pad_x = round((pixel_bbox[2] - pixel_bbox[0]) * padding)
pad_y = round((pixel_bbox[3] - pixel_bbox[1]) * padding)
crop = source_image.crop(
(
max(0, pixel_bbox[0] - pad_x),
max(0, pixel_bbox[1] - pad_y),
min(width, pixel_bbox[2] + pad_x),
min(height, pixel_bbox[3] + pad_y),
)
)
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
crop_path = Path(tmp.name)
tmp.close()
crop.save(crop_path)
display(crop)
crop_prompt = """
Read the license number value in this cropped police-form region.
It is next to the word License #.
Return JSON with one key:
- license_number
Return JSON only.
"""
crop_schema = {
"type": "object",
"properties": {
"license_number": {"type": "string"},
},
"required": ["license_number"],
"additionalProperties": False,
}
crop_response = client.responses.create(
model="gpt-5.4",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": crop_prompt},
{
"type": "input_image",
"image_url": image_to_data_url(crop_path),
"detail": "original",
},
],
}
],
text={
"format": {
"type": "json_schema",
"name": "license_number_extraction",
"schema": crop_schema,
"strict": True,
}
},
)
crop_result = json.loads(crop_response.output_text)
display(Markdown(f"### Crop-and-rerun result\n\n```json\n{json.dumps(crop_result, indent=2, ensure_ascii=False)}\n```"))

结论
总结一下,从简单开始:当任务简单且页面清晰时,使用原生视觉,设置 detail="auto" 且不使用工具。
当文本很小、手写、对比度低或扫描质量差时,提高图像细节(detail="original")。
当你需要忠实转录而非压缩摘要时,提高详细程度。
当图像可读但答案需要组合多个区域时,提高推理投入。
使用代码解释器进行多轮检查(缩放/裁剪/旋转),尤其是在密集页面上。
对于边界框,要求严格的约定:[x_min, y_min, x_max, y_max] 在固定的 0..999 坐标空间中(左上角为原点),并强制结构化 JSON 输出。
如果代码解释器不可用,使用裁剪并重跑:定位、本地裁剪,然后运行聚焦的提取提示。
在受限环境中,暴露轻量级视觉工具(裁剪/缩放/旋转/OCR 区域回退)以实现更精细的控制。
常见问题
常见错误包括:未设置 OPENAI_API_KEY 导致认证失败;图像路径错误触发 FileNotFoundError;MIME 类型推断失败导致 data URL 无效;detail 使用 auto 时小字段丢失;verbosity 过低导致布局被压缩;reasoning.effort 不足导致跨区域组合题答错;json.loads 解析失败通常因未启用 json_object 或模型返回了额外文本;边界框坐标越界或原点约定不一致导致标注错位;code_interpreter 容器内存不足或 file_ids 未正确关联。排查时先验证密钥与路径,再单独测试图像可读性,逐步提升 detail、verbosity 与 effort,最后检查 schema 与坐标换算。生产环境建议缓存图像编码、限制容器内存、对关键字段做二次校验、记录每次请求的参数组合以便回归。
企业应用扩展
可进一步扩展的方向包括:将裁剪重跑流水线封装为可配置的视觉工具集,暴露裁剪、缩放、旋转与区域 OCR 回退;对高价值文档建立参数组合的 A/B 评测集,量化 detail、verbosity、effort 对准确率与成本的影响;把 json_schema 与业务校验规则结合,实现字段级置信度与人工复核队列;在多页文档中引入页面级路由,先低分辨率定位再高分辨率精读;结合缓存与批处理降低重复调用成本;将边界框结果接入可视化标注与下游结构化数据库,形成端到端的文档智能流水线。
总结
本文面向有编程基础的开发者,系统讲解 GPT-5.4 在文档理解场景下的工程化配置方法。围绕图像细节、详细程度、推理强度与工具使用四个高杠杆参数,结合手写表单、报纸转录、平面图、图表、锦标赛括号表与边界框定位等真实案例,给出可执行的 Responses API 调用代码、结构化输出 schema、坐标约定与裁剪重跑流水线。文章强调从简单配置起步,依据失败模式逐步调整,并给出代码解释器不可用时的替代方案与生产环境优化建议。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:S_CANDIDATE
- 难度:中级
- 验证等级:V1
- 原文作者:annikab-oai, kathylau-oai, nsingaraju-oai
- 原文日期:2026-03-06
- 来源许可:MIT
- 来源版本:
3e1571675b56be76a875e1c0109ef2054dc3e542 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员