OpenAI Responses API 常见用法

资料核验日期:2026-07-22。模型标识、SDK 类型、工具参数、数据保留和价格都会变化;复制示例前请以项目锁定的 SDK 版本与 Responses API Reference 为准。本文以 Python SDK 为主,默认把模型名放在环境变量中,避免把可变模型选择散落在业务代码里。

Responses API 是 OpenAI 面向新项目推荐的统一生成接口。一次请求可以接收文本或图像,返回文本、结构化结果或工具调用,并可通过 previous_response_id、手动 history 或 Conversations API 保持上下文。它不是“自动替你执行业务操作”的框架:模型提出 function call 后,仍必须由你的服务端验证、授权、执行并把结果回传。

client.responses 常用操作

client.responses 是 SDK 中操作 Responses API 的对象。parse 是在普通响应请求之上增加结构化输出解析的辅助方法;response 只是接收其返回值的变量名。

调用作用适用场景
client.responses.create(...)创建一次模型响应普通非流式调用;stream=True 时也从这里发起流式请求
client.responses.parse(...)创建响应并按 Pydantic 类型自动解析结构化输出程序需要可靠读取固定字段
client.responses.stream(...)使用 SDK 的流式辅助接口接收响应需要逐步处理流式事件
client.responses.retrieve(response_id)按 ID 取回已存储的响应后续查询已完成或后台任务的结果
client.responses.delete(response_id)删除已存储的响应不再需要保留该响应时
client.responses.cancel(response_id)取消后台响应仅适用于以 background=True 创建且仍在运行的响应
client.responses.compact(...)压缩长会话上下文长对话接近上下文窗口时
client.responses.connect(...)建立持久 Responses API WebSocket 连接需要通过 WebSocket 发送请求并持续接收事件
client.responses.input_items.list(...)列出响应的输入项检查某个已存储响应的输入
client.responses.input_tokens.count(...)估算输入 token 数发送请求前预估上下文长度或成本

Note

input_items 和 input_tokens 是 client.responses 下的子资源,不是 Responses 类本身的方法;上表写的是实际调用路径。不同 SDK 版本可能增减辅助方法,部署时仍应以项目锁定的版本为准。

先建立全局心智模型

一次 Responses 调用可以简化为:

model + instructions + input + 可选 tools / state
                    ↓
response: id, status, output[], output_text, usage
需求请求重点应读取的结果
普通问答、改写、总结input + 可选 instructionsresponse.output_text
多轮聊天previous_response_id、手动 history 或 conversation新 response 的 id 与文本
长回答的实时显示stream=Trueresponse.output_text.delta 等事件
给程序消费的固定字段responses.parse(..., text_format=...) 或 text.format已解析对象或 JSON
调用你的数据库、订单系统tools=[{"type": "function", ...}]response.output 中的 function_call
查询最新网页资料tools=[{"type": "web_search"}]文本及 url_citation 标注
查询已上传的私有资料tools=[{"type": "file_search", ...}]文本及 file_citation 标注
看图、OCR、截图理解input_image 内容块response.output_text

output_text 是“只想拿最终文本”时的便利属性。涉及工具、推理项或其他多模态输出时,应遍历 response.output,按每个 item 的 type 处理;不要假定 output[0] 永远是文本。

0. 安装与安全配置

在 PowerShell 7 中创建隔离环境。下面的环境变量只在当前终端会话有效;生产环境应使用部署平台的 secret manager 或受控环境变量,绝不把真实密钥放进前端、Markdown、Git 或日志。

python -m venv .venv  # 在当前目录创建隔离的 Python 虚拟环境。
.\.venv\Scripts\Activate.ps1  # 激活虚拟环境,使后续 pip 安装只影响本项目。
python -m pip install --upgrade openai pydantic  # 安装 OpenAI SDK;结构化输出示例还会使用 Pydantic。
 
$env:OPENAI_API_KEY = "<从安全的密钥管理工具读取>"  # 仅放入当前终端;不要把真实值写进代码或 Git。
$env:OPENAI_MODEL = "gpt-5.6"  # 集中指定本教程使用的模型,业务项目应改为自己的配置值。

所有 Python 示例默认共享下面的初始化。若项目规定了固定模型、区域、代理或重试策略,应在自己的 adapter 配置层统一注入,而不是逐个调用硬编码。

import os  # 用于读取操作系统环境变量。
 
from openai import OpenAI  # 导入 OpenAI Python SDK 的同步客户端。
 
client = OpenAI()  # 创建客户端;默认从 OPENAI_API_KEY 读取密钥。
MODEL = os.environ["OPENAI_MODEL"]  # 从集中环境变量读取模型名;缺失时立即报错而非静默选错模型。

1. 最小文本调用

instructions 放稳定的行为约束,input 放本轮任务。最小调用只需读取 output_text。

response = client.responses.create(  # 向 Responses API 发起一次非流式生成请求。
    model=MODEL,  # 指定由集中配置提供的模型。
    instructions="你是一位耐心的 Python 助教,用中文、分步骤解释概念。",  # 给本轮模型稳定的行为指导。
    input="用一个短例子解释列表推导式。",  # 传入本轮用户任务。
)
 
print(response.output_text)  # 读取 SDK 汇总好的最终文本。
print("response id:", response.id)  # 输出 response ID,便于排障、续接或审计。
print("token usage:", response.usage)  # 输出本次调用的 token 用量对象。

当需要显式表示某轮用户消息时,input 也可以是 item 列表:

response = client.responses.create(  # 发起一次使用 typed input item 的请求。
    model=MODEL,  # 指定本次调用的模型。
    instructions="回答不超过三条要点;不确定时明确说明。",  # 限制回答风格与不确定性表达。
    input=[  # 用列表显式描述输入 item,后续可扩展为多条消息或多模态内容。
        {  # 开始定义一条用户消息 item。
            "role": "user",  # 标识这条内容来自用户。
            "content": "比较列表和元组最重要的两个区别。",  # 放入用户的具体问题。
        }
    ],
    max_output_tokens=300,  # 限制本轮最多生成的输出 token 数。
)
 
print(response.output_text)  # 打印最终文本答案。

instructions 是当前请求顶层的系统/开发者指导。尤其在使用 previous_response_id 时,前一轮的顶层 instructions 不会自动继承,因此稳定规则应在每一轮重新发送。

2. 多轮对话:先选择状态策略

策略适合什么情况你需要保存什么
previous_response_id简单串行聊天,允许服务端保存 response最近一轮 response ID
手动 history要裁剪、审计或自行托管上下文;store=False 场景输入 item 与完整 response.output
conversation跨会话、跨设备或长期任务的持久会话Conversation ID

2.1 用 previous_response_id 续接

这是聊天原型最短的写法。store=True 在此显式写出,便于读者看出它依赖服务端保存的 response;当前 API 的默认保存行为及保留规则仍须按项目合规要求复核。

BASE_INSTRUCTIONS = "你是中文学习助手。每次先给结论,再给一个可验证的小例子。"  # 把每轮都要重发的稳定指导集中保存。
 
first = client.responses.create(  # 创建对话的第一轮 response。
    model=MODEL,  # 指定模型。
    instructions=BASE_INSTRUCTIONS,  # 发送第一轮的稳定行为指导。
    input="解释什么是闭包。",  # 发送第一轮用户问题。
    store=True,  # 显式允许服务端保存此 response,供下一轮引用。
)
 
follow_up = client.responses.create(  # 创建会引用上一轮上下文的第二轮 response。
    model=MODEL,  # 指定模型。
    instructions=BASE_INSTRUCTIONS,  # 必须重新发送稳定指导,因为它不会由 previous_response_id 自动继承。
    previous_response_id=first.id,  # 用第一轮 response ID 串联上下文。
    input="请把刚才的例子改成计数器。",  # 传入当前轮的新问题。
    store=True,  # 继续保存本轮 response,便于再续接下一轮。
)
 
print(follow_up.output_text)  # 输出带有前序语境的第二轮答案。

这会保留前序语境,但不意味着前序 token 不计费;链中既有输入仍会计入后续请求的 input tokens。需要截断、摘要或去除敏感内容时,优先使用手动 history。

2.2 手动维护 history(适合 store=False)

关键点是追加完整的 response.output,而不只是最后一段文本。这样 function call、推理 item 和 assistant message 都能以正确类型回放。

history = [{"role": "user", "content": "讲一个关于递归的笑话。"}]  # 从第一条用户消息开始维护调用方自己的 history。
 
first = client.responses.create(  # 用当前 history 发起第一轮请求。
    model=MODEL,  # 指定模型。
    input=history,  # 把调用方维护的完整上下文传给模型。
    store=False,  # 禁止保存 Response 对象,改由调用方自行保存所需上下文。
)
print(first.output_text)  # 显示第一轮生成的笑话。
 
history += first.output  # 追加完整 typed output,而非只追加文本,以保留可续接的上下文。
history.append({"role": "user", "content": "解释笑点。"})  # 再追加第二轮用户消息。
 
second = client.responses.create(  # 将手动维护后的 history 发送给模型。
    model=MODEL,  # 指定模型。
    input=history,  # 传入第一轮输入、第一轮输出和第二轮用户问题。
    store=False,  # 继续采用调用方维护状态的方式。
)
print(second.output_text)  # 显示模型根据前序内容生成的解释。

store=False 只控制 Response 对象的存储,不应被表述为“整条业务链路没有任何数据保留”。应用日志、反向代理、数据库、文件存储与第三方工具仍需分别设计和审计。

3. 流式文本:只在完成事件后视为成功

设置 stream=True 后,SDK 返回可迭代的语义事件。前端可以在 response.output_text.delta 到来时渲染,但服务端应当等到 response.completed 才把该轮标记为完整;流中 error、response.failed、连接中断或缺少终态都不能当作成功。

stream = client.responses.create(  # 发起返回语义事件流的 Responses 请求。
    model=MODEL,  # 指定模型。
    input="用通俗语言解释为什么 HTTP 请求需要超时。",  # 设置本轮待回答的问题。
    stream=True,  # 打开 SSE 流式模式;返回值会成为可迭代事件序列。
)
 
completed = False  # 先假定请求未完整结束,避免半截输出被误判为成功。
 
for event in stream:  # 逐个消费 SDK 解码后的流事件。
    # `response.output_text.delta` 表示:模型又生成了一小段新的文本。
    # `response.completed` 表示:整个 Response 已经完整完成。
    if event.type == "response.output_text.delta":  # 此事件携带一小段新增文本。
        print(event.delta, end="", flush=True)  # 立即显示增量文本,并强制刷新终端缓冲区。
    elif event.type == "response.completed":  # 只有此终态说明整轮 response 已完成。
        completed = True  # 记录已收到合法完成信号。
    elif event.type in {"response.failed", "error"}:  # 同时处理 response 失败和通用流错误。
        raise RuntimeError(f"响应流失败:{event}")  # 中止业务流程,避免继续使用不完整结果。
 
print()  # 在流式文本结束后补一个换行,保持终端输出整洁。
if not completed:  # 连接结束却没有终态时,仍必须判定为失败。
    raise RuntimeError("流在收到 response.completed 前结束,结果不可视为完整。")  # 明确拒绝半截结果。

函数参数也可能以增量事件到达。不要对半截 JSON 执行工具;应等待相应的 function-call 参数完成事件,并在下一节的正常工具循环中解析、校验、授权。

4. 结构化输出:让程序拿到对象,而不是猜 JSON

当你的程序需要固定字段(分类、表单、任务拆解)时,优先使用 Structured Outputs,而不是只在提示词里要求“请返回 JSON”。Pydantic helper 会把类型定义转成 schema,并把成功解析的结果放在输出内容的 parsed 字段。

from typing import Literal  # 为 priority 字段声明有限的字符串集合。
 
from pydantic import BaseModel, Field  # 用 Pydantic 定义并校验结构化输出类型。
 
 
class SupportTicket(BaseModel):  # 定义应用希望模型返回的工单对象。
    title: str = Field(description="不超过 40 字的标题")  # 标题字段及其对模型的说明。
    priority: Literal["low", "medium", "high"]  # 仅允许三种优先级,避免任意字符串。
    summary: str  # 保存可供后续人工阅读的简短摘要。
    needs_human: bool  # 标识此工单是否应转交人工处理。
 
 
response = client.responses.parse(  # 请求 SDK 按 Pydantic 类型发送 schema 并解析成功结果。
    model=MODEL,  # 指定模型。
    instructions="从用户消息中提取工单;信息不足时如实说明,不要编造订单号。",  # 约束抽取时的真实性边界。
    input="我的订单两周没到,客服邮件也没有回复。",  # 放入待抽取的原始用户文本。
    text_format=SupportTicket,  # 指定最终输出必须匹配的 Pydantic 类型。
)
 
ticket = None  # 先保留空值,用于检测最终是否取得了可解析对象。
for output_item in response.output:  # 遍历每个 typed output item,而不假设它们的位置。
    if output_item.type != "message":  # 只在 message item 中查找最终结构化文本。
        continue  # 跳过工具调用等其他类型的 item。
    for content in output_item.content:  # 遍历 message 内的每个内容块。
        if content.type == "refusal":  # 安全拒绝不保证遵循目标 schema。
            raise RuntimeError(f"模型拒绝:{content.refusal}")  # 将拒绝交给上层 UI 或业务策略处理。
        if content.type == "output_text" and content.parsed is not None:  # 仅接受 SDK 已成功解析的文本块。
            ticket = content.parsed  # 取出 Pydantic SupportTicket 对象。
 
if ticket is None:  # 没有 parsed 对象时,不能把原始文本误作结构化数据使用。
    raise RuntimeError("没有得到可解析的结构化结果。")  # 让调用方显式处理不完整或异常结果。
 
print(ticket.model_dump())  # 转为普通字典后打印,便于查看字段值。

若 schema 由多个语言或服务共享,可直接使用 text.format 发送 JSON Schema:

import json  # 用于把最终 JSON 文本解析为 Python 对象。
 
ticket_schema = {  # 定义可被多个服务或语言复用的 JSON Schema。
    "type": "object",  # 要求最终根节点是对象。
    "properties": {  # 列出对象允许的字段。
        "category": {"type": "string", "enum": ["billing", "shipping", "other"]},  # 限制分类只能取给定枚举值。
        "needs_human": {"type": "boolean"},  # 要求人工作转字段只能是布尔值。
    },
    "required": ["category", "needs_human"],  # 要求两个字段都必须出现。
    "additionalProperties": False,  # 拒绝 schema 中未定义的额外字段。
}
 
response = client.responses.create(  # 使用原始 JSON Schema 请求结构化输出。
    model=MODEL,  # 指定模型。
    instructions="输出与给定 JSON Schema 一致的 JSON。",  # 明确告诉模型最终内容应是 JSON。
    input="包裹显示已签收,但我没有收到。",  # 传入需要分类的用户描述。
    text={  # 配置 Responses API 最终文本的格式。
        "format": {  # 进入具体的格式约束对象。
            "type": "json_schema",  # 选择严格 JSON Schema 输出模式。
            "name": "ticket_routing",  # 给该 schema 一个便于审计和识别的名称。
            "strict": True,  # 要求模型严格遵循 schema 支持的约束。
            "schema": ticket_schema,  # 传入上面定义的 schema 内容。
        }
    },
)
 
data = json.loads(response.output_text)  # 将已完成的 JSON 文本转为 Python 字典。
print(data)  # 输出解析后的结构化结果。

Schema 合规不等于事实正确、业务逻辑正确或动作已获授权。仍要做领域校验,例如确认订单属于当前用户、金额范围合法,且写操作已获得明确确认。

5. Function calling:模型提议,应用执行

Function calling 的完整循环是:定义工具 → 模型返回一个或多个 function_call → 应用验证并执行 → 以同一个 call_id 回传 function_call_output → 再次请求模型。模型可能一次不调用、调用一个或调用多个工具,因此不要写成只处理第一项的 if。

import json  # 用于解析模型返回的函数参数,并序列化工具执行结果。
 
 
TOOLS = [  # 声明本轮允许模型选择的全部自定义工具。
    {  # 开始定义 get_order_status 工具的完整合同。
        "type": "function",  # 指明这是 JSON Schema 定义的函数工具。
        "name": "get_order_status",  # 提供应用侧可路由的稳定工具名。
        "description": "按订单号查询订单状态;只能用于读取当前用户有权访问的订单。",  # 说明何时能用及权限边界。
        "parameters": {  # 定义模型可提交给函数的参数 schema。
            "type": "object",  # 要求参数整体是一个对象。
            "properties": {  # 声明可接受的参数字段。
                "order_id": {  # 定义订单号字段。
                    "type": "string",  # 要求订单号必须是字符串。
                    "description": "订单号,例如 ORD-2026-001",  # 向模型说明字段格式。
                }
            },
            "required": ["order_id"],  # 要求调用时一定提供订单号。
            "additionalProperties": False,  # 拒绝 schema 之外的多余参数。
        },
        "strict": True,  # 要求函数参数尽可能严格匹配 schema。
    }
]
 
 
def get_order_status(order_id: str) -> dict:  # 模拟一个只读的订单查询函数。
    # 真实项目:先从登录会话取得 user_id,再做权限检查,然后查询数据库。
    return {"order_id": order_id, "status": "shipping", "eta": "2026-07-25"}  # 返回可安全回传给模型的查询结果。
 
 
def call_tool(name: str, args: dict) -> dict:  # 将模型提出的工具名路由到受控的应用函数。
    if name == "get_order_status":  # 只允许调用已登记的读取工具。
        order_id = args.get("order_id")  # 从模型提供的 JSON 参数中读取订单号。
        if not isinstance(order_id, str) or not order_id.startswith("ORD-"):  # 再次在服务端校验类型和业务格式。
            raise ValueError("订单号格式不合法。")  # 参数异常时拒绝执行,而非依赖模型自行修正。
        return get_order_status(order_id)  # 通过了本地校验后才访问订单数据。
    raise ValueError(f"不允许调用未知工具:{name}")  # 阻止模型请求未显式开放的能力。
 
 
def parse_tool_arguments(raw_arguments: str) -> dict:  # 把模型返回的 JSON 参数限制为对象形状。
    arguments = json.loads(raw_arguments)  # 先按 JSON 语法解析原始参数字符串。
    if not isinstance(arguments, dict):  # 防止数组、字符串等值绕过后续的字段校验逻辑。
        raise ValueError("工具参数必须是 JSON 对象。")  # 参数合同不成立时停止执行该工具。
    return arguments  # 仅把受控的字典参数交给应用侧路由器。
 
 
MAX_TOOL_ROUNDS = 4  # 为单个用户请求设置连续工具回合上限,避免失控循环和费用扩张。
 
 
input_items = [  # 初始化将持续追加的 Responses input item 列表。
    {"role": "user", "content": "帮我查询订单 ORD-2026-001 的状态。"}  # 放入第一轮用户请求。
]
 
for _ in range(MAX_TOOL_ROUNDS):  # 在受限回合内处理模型可能提出的多轮工具调用。
    response = client.responses.create(  # 带着当前上下文和工具定义请求模型决定下一步。
        model=MODEL,  # 指定模型。
        instructions="需要订单状态时调用工具;不要编造工具查询结果。",  # 约束模型应以工具结果为准。
        input=input_items,  # 传入用户消息、先前 output 和已有工具结果。
        tools=TOOLS,  # 向模型公开本轮可用的函数工具。
    )
 
    function_calls = [  # 从所有 typed output item 中筛出本轮请求执行的函数调用。
        item for item in response.output if item.type == "function_call"  # 保留零个、一个或多个 function_call item。
    ]
    if not function_calls:  # 没有工具调用时,模型已经给出了最终文本回答。
        print(response.output_text)  # 输出最终的自然语言回答。
        break  # 退出工具循环。
 
    # 回传所有本轮 output,保留 function call 及可能出现的其他 typed item。
    input_items += response.output  # 追加本轮完整 output,确保 call_id 和其他上下文不丢失。
 
    for call in function_calls:  # 逐个处理模型本轮提出的所有函数调用。
        args = parse_tool_arguments(call.arguments)  # 只接受 JSON 对象,再交给服务端业务校验。
        result = call_tool(call.name, args)  # 在应用侧完成路由、权限校验和真实工具执行。
        input_items.append(  # 把每个工具结果作为新的 input item 回传给模型。
            {  # 开始构造与当前 call_id 关联的函数输出 item。
                "type": "function_call_output",  # 明确标识这是某个函数调用的输出。
                "call_id": call.call_id,  # 用原调用 ID 将结果准确关联到模型请求。
                "output": json.dumps(result, ensure_ascii=False, allow_nan=False),  # 将受控结果编码为标准 JSON,保留中文且拒绝非有限数值。
            }
        )
else:  # 到达上限仍有待处理调用时,不继续发送请求。
    raise RuntimeError(f"工具调用超过 {MAX_TOOL_ROUNDS} 轮上限,已停止执行。")  # 把受控失败交给上层记录、告警或引导用户重试。

strict=True 约束参数形状,不替代服务器端身份认证、参数语义校验、幂等、审计或高风险动作的人工确认。示例刻意把连续工具回合限制为四次;生产环境还应按请求设置总截止时间,并按工具设置调用次数、速率/成本阈值和幂等保护。对于退款、删除、发送邮件等写操作,应让工具本身执行最小权限与确认门槛,不能因为模型调用了函数就立即产生副作用。

6. Web search:获取最新信息并显示引用

新项目使用 web_search,不要继续使用遗留的 web_search_preview。tool_choice="auto" 允许模型自行决定是否搜索;需求明确要求查询时,可用 "required"。网页回答中的 url_citation 必须在面向用户的 UI 中清晰、可点击地展示。

response = client.responses.create(  # 发起一轮必须使用网页检索工具的请求。
    model=MODEL,  # 指定模型。
    input="查找 OpenAI Responses API 的官方流式响应说明,给出两条要点和来源。",  # 明确检索目标和回答格式。
    tools=[  # 配置本轮可用的托管网页检索工具。
        {  # 开始定义网页检索工具的配置。
            "type": "web_search",  # 使用新项目应采用的网页检索工具类型。
            "search_context_size": "low",  # 为简单事实查询选择较小的检索上下文。
            "filters": {  # 限制允许搜索的站点范围。
                "allowed_domains": ["developers.openai.com"],  # 只允许官方开发者文档域名。
            },
        }
    ],
    tool_choice="required",  # 要求模型本轮实际执行网页检索,而非只凭既有知识回答。
    include=["web_search_call.action.sources"],  # 请求返回检索动作所用的完整来源列表。
)
 
print(response.output_text)  # 先显示模型汇总出的带内联引用的文本。
 
for output_item in response.output:  # 遍历所有输出 item,以便提取文本引用标注。
    if output_item.type != "message":  # 只在模型 message item 中查找最终文本。
        continue  # 跳过 web_search_call 等非文本 item。
    for content in output_item.content:  # 遍历 message 的内容块。
        if content.type != "output_text":  # 只处理输出文本块。
            continue  # 跳过拒绝或其他内容类型。
        for annotation in content.annotations:  # 逐个读取文本携带的标注。
            if annotation.type == "url_citation":  # 识别网页 URL 引用标注。
                print(f"- {annotation.title}: {annotation.url}")  # 输出可用于 UI 渲染的标题和链接。

域名过滤只写主机名,不写 https://;它适合缩小证据来源,并不替代你对结果质量的判断。默认允许实时联网;若业务只允许缓存/索引内容,可在工具对象中设定 external_web_access=False。需要读取所有被检索 URL 时可保留上例的 include,但要同时评估成本、延迟和日志中的外部内容暴露。

7. File search:让模型检索你的文档库

File search 是由 OpenAI 托管的内置工具:先上传文件并建立 vector store,等索引完成后,把 vector store ID 放到 file_search 工具中。模型决定调用后,平台会执行检索;应用不需要像 function calling 那样自行回传 tool output。

下面的初始化通常只做一次,应在业务数据库中保存 vector_store.id,避免每次提问重复上传和建库。

from time import monotonic, sleep  # 用单调时钟限制等待窗口,并在轮询间隔内短暂等待。
 
 
POLL_INTERVAL_SECONDS = 1.0  # 控制查询索引状态的频率,避免无间隔轮询。
INDEX_TIMEOUT_SECONDS = 120  # 为这次初始化等待设置明确的最长时长。
 
with open("employee_handbook.pdf", "rb") as file_content:  # 以二进制方式打开待入库的本地 PDF。
    uploaded_file = client.files.create(  # 先把本地文件上传到 OpenAI Files API。
        file=file_content,  # 传入已打开的二进制文件对象。
        purpose="assistants",  # 声明该文件用于可检索的知识库场景。
    )
 
vector_store = client.vector_stores.create(name="employee_handbook")  # 创建承载该知识库的 vector store。
client.vector_stores.files.create(  # 将刚上传的文件关联并提交到 vector store 索引。
    vector_store_id=vector_store.id,  # 指定目标知识库 ID。
    file_id=uploaded_file.id,  # 指定要加入知识库的上传文件 ID。
)
 
# 只有完成索引后才能稳定地用于检索。
indexing_deadline = monotonic() + INDEX_TIMEOUT_SECONDS  # 记录本次轮询的单调时钟截止点。
while monotonic() < indexing_deadline:  # 仅在受限等待窗口内查询文件索引状态。
    indexed_file = client.vector_stores.files.retrieve(  # 读取当前文件关联对象的最新状态。
        vector_store_id=vector_store.id,  # 指定要查询的 vector store。
        file_id=uploaded_file.id,  # 指定刚上传并关联的文件。
    )
    if indexed_file.status == "completed":  # 只有完成索引后才允许进入查询阶段。
        break  # 结束轮询。
    if indexed_file.status in {"failed", "cancelled"}:  # 显式识别不可恢复的索引终态。
        raise RuntimeError(f"文件索引失败:{indexed_file.status}")  # 不把失败文件当作可检索资料。
    sleep(POLL_INTERVAL_SECONDS)  # 未完成时按固定间隔等待,避免无间隔轮询。
else:  # 截止时仍未获得完成或失败终态。
    raise TimeoutError(f"文件索引在 {INDEX_TIMEOUT_SECONDS} 秒内未完成。")  # 由上层记录状态并决定是否受控重试。
 
print("保存这个 ID 以供后续提问复用:", vector_store.id)  # 输出需持久化保存的知识库 ID。

超时只代表本次等待窗口耗尽,不等同于索引服务永久失败;应记录 vector_store.id、文件 ID 与最后状态,再由受控的重试任务或人工流程决定后续动作。提问时只需引用现有的 vector store:

response = client.responses.create(  # 对已建好的知识库发起一次带 file_search 的问答请求。
    model=MODEL,  # 指定模型。
    instructions="只基于检索到的员工手册回答;未找到依据时明确说明。",  # 约束模型不以猜测替代文档证据。
    input="远程办公需要提前多久申请?",  # 传入用户关于手册的具体问题。
    tools=[  # 配置本轮可使用的托管文件检索工具。
        {  # 开始定义 file_search 工具的配置。
            "type": "file_search",  # 指定使用 vector store 的文件检索能力。
            "vector_store_ids": [vector_store.id],  # 将查询限定在前面创建的知识库中。
            "max_num_results": 5,  # 最多取回五条检索结果,平衡质量、成本和延迟。
        }
    ],
    include=["file_search_call.results"],  # 让响应额外包含原始检索结果,便于审计或调试。
)
 
print(response.output_text)  # 输出模型基于检索资料生成的答案。

最终文本会携带 file_citation 标注。向最终用户展示答案时也应把文件名/引用位置一并呈现;生产系统还应在上传前做租户隔离、文件授权、生命周期与删除策略。

8. 图像输入:URL、Data URL 或 file ID

图像可以使用公网 URL、Base64 Data URL 或 Files API 的 file_id。下例把本地文件转为 Data URL;detail="low" 适合快速、低成本的粗略理解,涉及小字、空间位置或复杂图表时要根据模型能力、成本和准确性选择 high、original 或自行预处理图片。

import base64  # 用于把本地图片字节编码成 Base64 文本。
import mimetypes  # 用于根据文件名推断图片 MIME 类型。
from pathlib import Path  # 用面向对象的路径 API 读取本地图片。
 
 
def as_data_url(path: str) -> str:  # 将本地图片转成 Responses API 可接收的 Data URL。
    image_path = Path(path)  # 把字符串路径转换为 Path 对象。
    mime_type = mimetypes.guess_type(image_path.name)[0] or "image/jpeg"  # 推断 MIME 类型;未知时采用常见 JPEG 默认值。
    encoded = base64.b64encode(image_path.read_bytes()).decode("ascii")  # 读取图片字节、Base64 编码并转为文本。
    return f"data:{mime_type};base64,{encoded}"  # 组装标准 data: URL 字符串。
 
 
response = client.responses.create(  # 发起一轮同时包含文字与图片的多模态请求。
    model=MODEL,  # 指定支持图像输入的模型。
    input=[  # 用用户消息 item 承载多个不同模态的内容块。
        {  # 开始定义包含文本与图片的一条用户消息。
            "role": "user",  # 指明这组内容来自用户。
            "content": [  # 以列表顺序发送文字指令和图片。
                {"type": "input_text", "text": "提取图片中的标题和三个主要要点。"},  # 告诉模型要对图片完成什么任务。
                {  # 开始定义图像内容块。
                    "type": "input_image",  # 指定此内容块是图像输入。
                    "image_url": as_data_url("report_screenshot.png"),  # 将本地截图转为 Data URL 后传入。
                    "detail": "low",  # 请求低细节视觉处理,以降低一般场景的成本和延迟。
                },
            ],
        }
    ],
)
 
print(response.output_text)  # 输出模型根据图像生成的文本结果。

图片也会消耗 input tokens。不要把验证码发给模型;医学影像、精确计数、复杂空间定位和低清晰度小字等任务存在已知局限,关键结论应加入专门的校验或人工复核。

9. 交付前检查:把“能跑”变成“可用”

  • API key 只在服务端或受控运行环境中读取,没有进入浏览器、仓库或日志。
  • 模型名从集中配置读取,并已用项目账户实际验证其可用性。
  • 只在 response.status 完成、或流收到 response.completed 后将一轮标记为成功。
  • 结构化输出同时处理 refusal、解析失败与领域语义校验。
  • function call 的参数、身份、权限、幂等和高风险确认由业务代码把关。
  • Web/file search 的引用在 UI 中可追溯;检索范围按租户和数据权限隔离。
  • 按 超时、错误、限流与重试设置超时、受限重试与限流,不把 SDK 默认行为和应用重试叠成无限重试。
  • 记录 response.id、模型、提示版本、耗时、用量和受控的错误分类;不要记录不必要的原始敏感内容。参见 用量、可观测性与供应商适配。

下一步

  • 需要系统掌握工具的选择、参数、权限与多轮循环时,继续学习 Tool Calling(含 Function Calling)。
  • 需要把上述代码变成可靠 SDK adapter 时,完成本库的 可靠客户端项目与自测,并将真实 API 调用与离线合同测试分开。
  • 需要长期知识库时,再阅读 RAG,不要只把 file search 当成全部检索系统设计。

参考资料