更好的性能,通过响应 API 使用推理模型

概述

通过利用响应 API 和 OpenAI 最新的推理模型,您可以在应用程序中实现更高的智能、更低的成本和更高效的 token 使用。该 API 还支持访问推理摘要、托管工具使用等功能,并旨在适应即将推出的增强功能,以获得更大的灵活性和性能。

我们最近发布了两个最先进的推理模型 o3 和 o4-mini,它们在结合推理能力和代理工具使用方面表现出色。许多人不知道的是,您可以通过充分利用我们(相对)较新的响应 API 来提高它们的性能。本指南展示了如何充分利用这些模型,并探讨了推理和函数调用在后台的工作原理。通过让模型访问先前的推理项,我们可以确保它以最高的智能和最低的成本运行。

我们通过单独的 指南API 参考 介绍了响应 API。主要收获是:响应 API 类似于完成 API,但具有改进和新增的功能。我们还推出了响应的加密内容,使其对于无法以有状态方式使用 API 的用户更加有用!

推理模型如何工作

在深入探讨响应 API 如何提供帮助之前,让我们快速回顾一下 推理模型 的工作原理。像 o3 和 o4-mini 这样的模型会一步一步地分解问题,产生一个编码其推理的内部思维链。为了安全起见,这些推理 token 仅以摘要形式向用户公开。

在多步对话中,推理 token 在每个回合后都会被丢弃,而每个回合的输入和输出 token 会被馈送到下一个回合。

reasoning-context 图表来自我们的 文档

让我们检查一下返回的响应对象:

from openai import OpenAI
import os
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
response = client.responses.create(
    model="o4-mini",
    input="tell me a joke",
)
import json

print(json.dumps(response.model_dump(), indent=2))
{
  "id": "resp_6820f382ee1c8191bc096bee70894d040ac5ba57aafcbac7",
  "created_at": 1746989954.0,
  "error": null,
  "incomplete_details": null,
  "instructions": null,
  "metadata": {},
  "model": "o4-mini-2025-04-16",
  "object": "response",
  "output": [
    {
      "id": "rs_6820f383d7c08191846711c5df8233bc0ac5ba57aafcbac7",
      "summary": [],
      "type": "reasoning",
      "status": null
    },
    {
      "id": "msg_6820f3854688819187769ff582b170a60ac5ba57aafcbac7",
      "content": [
        {
          "annotations": [],
          "text": "Why don\u2019t scientists trust atoms?  \nBecause they make up everything!",
          "type": "output_text"
        }
      ],
      "role": "assistant",
      "status": "completed",
      "type": "message"
    }
  ],
  "parallel_tool_calls": true,
  "temperature": 1.0,
  "tool_choice": "auto",
  "tools": [],
  "top_p": 1.0,
  "max_output_tokens": null,
  "previous_response_id": null,
  "reasoning": {
    "effort": "medium",
    "generate_summary": null,
    "summary": null
  },
  "status": "completed",
  "text": {
    "format": {
      "type": "text"
    }
  },
  "truncation": "disabled",
  "usage": {
    "input_tokens": 10,
    "input_tokens_details": {
      "cached_tokens": 0
    },
    "output_tokens": 148,
    "output_tokens_details": {
      "reasoning_tokens": 128
    },
    "total_tokens": 158
  },
  "user": null,
  "service_tier": "default",
  "store": true
}

从响应对象的 JSON 转储中,您可以看到,除了 output_text 之外,模型还生成了一个推理项。此项代表模型的内部推理 token,并以 ID 的形式公开——例如,此处为 rs_6820f383d7c08191846711c5df8233bc0ac5ba57aafcbac7。由于响应 API 是有状态的,这些推理 token 会持续存在:只需在后续消息中包含它们的 ID,即可让未来的响应访问相同的推理项。如果您使用 previous_response_id 进行多轮对话,模型将自动访问所有先前的推理项。

您还可以看到模型生成了多少推理 token。例如,对于 10 个输入 token,响应包含 148 个输出 token——其中 128 个是最终助手消息中未显示的推理 token。

等等——图表不是显示前一轮的推理被丢弃了吗?那么为什么还要在后面的回合中传递它呢?

好问题!在典型的多轮对话中,您不需要包含推理项或 token——模型经过训练,无需它们即可产生最佳输出。但是,当涉及工具使用时,情况会发生变化。如果一轮包含函数调用(可能需要额外的 API 往返),您确实需要包含推理项——无论是通过 previous_response_id 还是通过将推理项显式添加到 input。让我们通过一个快速的函数调用示例来看看它是如何工作的。

import requests

def get_weather(latitude, longitude):
    response = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m")
    data = response.json()
    return data['current']['temperature_2m']


tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get current temperature for provided coordinates in celsius.",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {"type": "number"},
            "longitude": {"type": "number"}
        },
        "required": ["latitude", "longitude"],
        "additionalProperties": False
    },
    "strict": True
}]

context = [{"role": "user", "content": "What's the weather like in Paris today?"}]

response = client.responses.create(
    model="o4-mini",
    input=context,
    tools=tools,
)


response.output
[ResponseReasoningItem(id='rs_68210c71a95c81919cc44afadb9d220400c77cc15fd2f785', summary=[], type='reasoning', status=None),
 ResponseFunctionToolCall(arguments='{"latitude":48.8566,"longitude":2.3522}', call_id='call_9ylqPOZUyFEwhxvBwgpNDqPT', name='get_weather', type='function_call', id='fc_68210c78357c8191977197499d5de6ca00c77cc15fd2f785', status='completed')]

经过一些推理后,o4-mini 模型确定它需要更多信息,并调用一个函数来获取它。我们可以调用该函数并返回其输出。至关重要的是,为了最大化模型的智能,我们应该通过将所有输出添加回下一个回合的上下文中来包含推理项。

context += response.output # 将响应(包括推理项)添加到上下文中
tool_call = response.output[1]
args = json.loads(tool_call.arguments)


# 调用函数
result = get_weather(args["latitude"], args["longitude"]) 

context.append({                               
    "type": "function_call_output",
    "call_id": tool_call.call_id,
    "output": str(result)
})

# 我们再次使用添加的函数调用输出来调用 API。请注意,虽然这是另一个 API 调用,但我们将其视为对话中的一个回合。
response_2 = client.responses.create(
    model="o4-mini",
    input=context,
    tools=tools,
)

print(response_2.output_text)
The current temperature in Paris is 16.3°C. If you’d like more details—like humidity, wind speed, or a brief description of the sky—just let me know!

虽然这个玩具示例可能无法清楚地显示好处——因为模型很可能无论是否有推理项都能很好地工作——但我们自己的测试发现并非如此。在像 SWE-bench 这样的更严格的基准测试中,包含推理项在相同提示和设置下带来了大约 3% 的改进

缓存

如上所示,推理模型会生成推理 token 和完成 token,API 对它们的处理方式不同。这种区别会影响缓存的工作方式,并影响性能和延迟。下图说明了这些概念:

reasoning-context

在回合 2 中,回合 1 的任何推理项都会被忽略和删除,因为模型不会重用先前回合的推理项。因此,图中第四次 API 调用无法实现完全缓存命中,因为提示中缺少这些推理项。但是,包含它们是无害的——API 将简单地丢弃任何与当前回合无关的推理项。请记住,缓存仅影响长度超过 1024 个 token 的提示。在我们的测试中,从完成 API 切换到响应 API 将缓存利用率从 40% 提高到 80%。更高的缓存利用率可降低成本(例如,o4-mini 的缓存输入 token 比未缓存的便宜 75%)并提高延迟。

加密推理项

一些组织——例如那些有 零数据保留 (ZDR) 要求的组织——由于合规性或数据保留策略,无法以有状态方式使用响应 API。为了支持这些情况,OpenAI 提供了 加密推理项,允许您在保持工作流程无状态的同时,仍然受益于推理项。

要使用加密推理项:

  • ["reasoning.encrypted_content"] 添加到 API 调用中的 include 字段。
  • API 将返回推理 token 的加密版本,您可以像常规推理项一样将其传递回将来的请求中。

对于 ZDR 组织,OpenAI 会自动强制执行 store=false。当请求包含 encrypted_content 时,它会在内存中解密(绝不会写入磁盘),用于生成下一个响应,然后安全地丢弃。任何新的推理 token 会立即被加密并返回给您,确保中间状态永远不会被持久化。

这是一个显示其工作原理的简单代码更新:

context = [{"role": "user", "content": "What's the weather like in Paris today?"}]

response = client.responses.create(
    model="o3",
    input=context,
    tools=tools,
    store=False, #store=false,就像 ZDR 的强制执行方式一样
    include=["reasoning.encrypted_content"] # 加密的思维链在响应中传递回来
)
# 查看加密的推理项
print(response.output[0]) 
ResponseReasoningItem(id='rs_6821243503d481919e1b385c2a154d5103d2cbc5a14f3696', summary=[], type='reasoning', status=None, encrypted_content='gAAAAABoISQ24OyVRYbkYfukdJoqdzWT-3uiErKInHDC-lgAaXeky44N77j7aibc2elHISjAvX7OmUwMU1r7NgaiHSVWL5BtWgXVBp4BMFkWZpXpZY7ff5pdPFnW3VieuF2cSo8Ay7tJ4aThGUnXkNM5QJqk6_u5jwd-W9cTHjucw9ATGfGqD2qHrXyj6NEW9RmpWHV2SK41d5TpUYdN0xSuIUP98HBVZ2VGgD4MIocUm6Lx0xhRl9KUx19f7w4Sn7SCpKUQ0zwXze8UsQOVvv1HQxk_yDosbIg1SylEj38H-DNLil6yUFlWI4vGWcPn1bALXphTR2EwYVR52nD1rCFEORUd7prS99i18MUMSAhghIVv9OrpbjmfxJh8bSQaHu1ZDTMWcfC58H3i8KnogmI7V_h2TKAiLTgSQIkYRHnV3hz1XwaUqYAIhBvP6c5UxX-j_tpYpB_XCpD886L0XyJxCmfr9cwitipOhHr8zfLVwMI4ULu-P3ftw7QckIVzf71HFLNixrQlkdgTn-zM6aQl5BZcJgwwn3ylJ5ji4DQTS1H3AiTrFsEt4kyiBcE2d7tYA_m3G8L-e4-TuTDdJZtLaz-q8J12onFaKknGSyU6px8Ki4IPqnWIJw8SaFMJ5fSUYJO__myhp7lbbQwuOZHIQuvKutM-QUuR0cHus_HtfWtZZksqvVCVNBYViBxD2_KvKJvR-nN62zZ8sNiydIclt1yJfIMkiRErfRTzv92hQaUtdqz80UiW7FBcN2Lnzt8awXCz1pnGyWy_hNQe8C7W35zRxJDwFdb-f3VpanJT0tNmU5bfEWSXcIVmiMZL1clwzVNryf9Gk482LaWPwhVYrhv2MkhKMPKdeAZWVhZbgm0eTT8a4DgbwcYRGhoXMGxrXWzOdvAY536DkrI_0xsJk8-Szb5Y2EH0xPxN4-CdB_fMPP60TPEQTOP1Qc64cJcQ9p2JE5Jfz59bubF_QGajC9-FtHkD6Q5pT-6CbhuD6xrFJMgxQPcggSDaWL_4260fZCdf6nzMlwPRD3wrfsxs6rFyd8pLC-2SOh9Iv297xAjes8xcnyqvMKSuCkjARr11gJCe0EXnx87NWt2rfW8ODUU0qFYbjFx8Rj9WJtnvQBNyqp7t5LLLf12S8pyyeKTv0ePqC3xDuWdFKmELDUZjarkkCyMHoO12EbXa6YCpY_MpA01c2vV5plrcouVPSwRK0ahbPs0mQnQnDAkfi2XVS0Bzgk2GpNONGf7KWkzD7uTgDtg9UbWI0v_-f-iiBM2kKDz_dIb1opZfaxZEloyiQ2MnWQj2MRefL7WM_0c3IyTAccICN-diGn2f1im82uL9maELcbYn')

使用 include=["reasoning.encrypted_content"] 设置后,我们现在在返回的推理项中看到了 encrypted_content 字段。此加密内容代表模型的推理状态,完全在客户端持久化,OpenAI 不保留任何数据。然后,您可以像以前一样将此内容传递回。

context += response.output # 将响应(包括加密的思维链)添加到上下文中
tool_call = response.output[1]
args = json.loads(tool_call.arguments)



result = 20 # 模拟函数调用的结果

context.append({                               
    "type": "function_call_output",
    "call_id": tool_call.call_id,
    "output": str(result)
})

response_2 = client.responses.create(
    model="o3",
    input=context,
    tools=tools,
    store=False,
    include=["reasoning.encrypted_content"]
)

print(response_2.output_text)
It’s currently about 20 °C in Paris.

通过对 include 字段进行简单的更改,我们现在可以传递回加密的推理项,并利用它来提高模型的智能、成本和延迟性能。

现在,您应该已具备充分利用我们最新推理模型的知识!

推理摘要

响应 API 中的另一个有用功能是它支持推理摘要。虽然我们不公开原始思维链 token,但用户可以访问其 摘要

# 使用推理摘要进行硬调用 o3

response = client.responses.create(
    model="o3",
    input="What are the main differences between photosynthesis and cellular respiration?",
    reasoning={"summary": "auto"},


)

# 从响应对象中提取第一个推理摘要文本
first_reasoning_item = response.output[0]  # 应为 ResponseReasoningItem
first_summary_text = first_reasoning_item.summary[0].text if first_reasoning_item.summary else None
print("First reasoning summary text:\n", first_summary_text)
First reasoning summary text:
 **分析生物过程**

我认为用户正在寻找对某些过程之间差异的清晰解释。我应该创建一个并排比较,列出关键要素,如公式、能量流、位置、反应物、产物、涉及的生物体、电子载体以及过程是合成代谢还是分解代谢。这种结构化的方法将有助于提供全面的答案。涵盖所有方面以确保用户清楚地理解区别至关重要。

推理摘要文本可以让您了解模型的思维过程。例如,在具有多个函数调用的对话中,用户可以看到调用了哪些函数以及每次调用的推理——而无需等待最终的助手消息。这为您的应用程序的用户体验增加了透明度和交互性。

结论

通过利用 OpenAI 响应 API 和最新的推理模型,您可以在应用程序中实现更高的智能、改进的透明度和更高的效率。无论您是利用推理摘要、用于合规性的加密推理项,还是针对成本和延迟进行优化,这些工具都能使您构建更强大、更具交互性的 AI 体验。

祝您构建愉快!