# OpenAI API 教程：用 Python 和 JavaScript 调用 Responses API

> Responses API 是 OpenAI 文档推荐新项目使用的接口。你发送模型 id 和输入，也可以附上 instructions，返回的是一组输出项，外加一个 `output_text` 快捷属性。这篇教程会创建密钥，安装官方 Python 和 JavaScript SDK，发出第一次调用，把回复做成流式输出，再让对话跨回合延续下去。

Mindket 编辑部 · 发布于 2026-11-05 · 更新于 2026-11-05 · https://mindket.com/cn/learn/openai-api/openai-api-quickstart

## 要点

- 把密钥放在环境变量里，别让访客能打开的页面碰到它。
- 请求里一旦有工具或推理，就按输出项的类型逐个处理。
- 每个串联的回合都要重新发送行为规则。
- 每段对话只选一个地方保存历史，并且一直用它。
- 迁移旧的聊天代码时小步推进，先迁纯文本。

## 开始之前

你需要一个 [OpenAI 平台](https://platform.openai.com/)账号和一个 API 密钥，密钥在控制台的 [API keys 页面](https://platform.openai.com/api-keys)创建。每个官方 SDK 都会从环境变量 `OPENAI_API_KEY` 读取密钥，所以代码里不用出现它。macOS 或 Linux 上运行 `export OPENAI_API_KEY="your_api_key_here"`，PowerShell 里运行 `setx OPENAI_API_KEY "your_api_key_here"`。

你为自己创建的密钥，带的是你本人的权限。部署好的应用、定时任务或 CI，[API 密钥指南](https://developers.openai.com/api/docs/guides/api-keys)建议改用项目服务账号。服务账号是一个非人类身份，自己持有密钥，这样工作负载就不会因为某位开发者离开团队而失效。服务账号的密钥只在创建那一刻完整显示一次，所以要直接存进密钥管理器。

密钥上的权限范围可以收窄它能做的事，但永远不会给出它的主人本来没有的权限。轮换密钥有固定的顺序：先签发新密钥，更新工作负载，确认能用，再吊销旧密钥。密钥换了，服务账号的身份不变。不同的项目可以把不同应用或环境的资源、成员和用量隔离开。如果工作负载运行在 OpenAI 支持的身份提供方上，可以用工作负载身份联合，完全不必保存长期有效的密钥。

Python SDK 需要 Python 3.10 或更高版本。JavaScript SDK 支持 Node.js 22 和 24 LTS，也支持 Deno、Bun 和 Cloudflare Workers。Node.js 20 已在 2026 年 4 月底结束生命周期，不再受支持。浏览器默认被禁止，打开 `dangerouslyAllowBrowser` 选项才能用，而那样一来，任何打开页面的人都能读到你的密钥。

## 发出第一次调用，再看看返回了什么

快速入门里的第一个请求只有三步：创建客户端，用模型和输入调用 `responses.create`，打印 `output_text`。下面是[开发者快速入门](https://developers.openai.com/api/docs/quickstart)里的 Python 版本。JavaScript 版本的调用写法是 `await client.responses.create({ model, input })`，文件存成 `example.mjs`，最后用 `console.log(response.output_text)` 输出。

返回的是一个带自己 id 的 response 对象，不是字符串。它的 `output` 属性是一组输出项。文本生成指南针对这个提示词给出的示例里，列表只有一项：id 以 `msg_` 开头，类型是 `message`，角色是 `assistant`。里面有一个 `content` 列表，装着一个 `output_text` 部分，也就是那个独角兽故事，旁边是一个空的 `annotations` 列表，工具提供引用时，引用就放在这里。

迁移指南打印了同一个请求在 `gpt-5.5` 上的结果，那里的列表以一个 id 以 `rs_` 开头的 `reasoning` 项开头，消息排在第二。`output_text` 是 SDK 提供的辅助属性，把所有文本部分拼成一个字符串，正好把这个差别藏了起来。写个小脚本时它很方便。代码一旦加了工具或要保存历史，就该逐项遍历这个列表，因为文本生成指南明确提醒，回复不一定在 `output[0].content[0].text`。

````python
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="Write a one-sentence bedtime story about a unicorn.",
)

print(response.output_text)
````

1. 在要运行代码的终端里导出密钥。
2. 安装 SDK：Python 用 `pip install openai`，JavaScript 用 `npm install openai`。
3. 把代码存成 `example.py`，JavaScript 版本存成 `example.mjs`。
4. 运行 `python example.py` 或 `node example.mjs`，调用返回后会打印出一个短故事。
5. 把打印 `output_text` 换成打印 `response.output`，看看那个字符串背后有哪些输出项类型。

| 输出项类型 | 什么时候出现 | 你的代码要做什么 |
| --- | --- | --- |
| `message` | 模型给用户写了文字 | 读取其中的文本部分，或者直接用 `output_text` |
| `reasoning` | 推理模型在回答前先做了推理 | 保留它，自己回放历史时要原样传回 |
| `function_call` | 模型想运行你的某个函数 | 运行函数，再回一个带相同 `call_id` 的 `function_call_output` |
| `web_search_call` 或 `file_search_call` | 内置工具已在 OpenAI 那边运行过 | 不用运行任何东西，读消息里的引用即可 |

## 用 instructions 引导模型，再把回复做成流式

`instructions` 是顶层参数，用来规定模型怎样表现，比如语气、目标、好回答的例子。它的优先级高于 `input` 里的提示词。传入它，大致相当于在输入列表最前面放一条 `developer` 角色的消息。developer 消息承载应用的规则，排在 `user` 消息之前，user 消息则是用户打出来的内容。模型自己的回合用 `assistant` 角色。

文本生成指南拿代码来比喻这两种角色。developer 消息像函数定义，装着规则和业务逻辑。user 消息像传给这个函数的参数。

它只对当前这一次请求生效。用 `previous_response_id` 串起来的回合，看不到你之前发过的 instructions，所以串联调用的代码每次都要重新发送。提示词本身应该写在代码里，这样它就有类型化的输入、代码审查和测试。OpenAI 正在淘汰一些旧教程存在平台上的可复用提示词对象，下面的时间线列出了日期。

默认情况下，API 要把整段回答写完，才通过一个 HTTP 响应发回来，回答一长，用户看到的就是这段等待。流式输出只多一个开关就解决了等待。把 `stream` 设为 true，SDK 交给你的就是带类型的服务器推送事件，也就是服务器单向持续写入的 HTTP 流。下面的示例把[文本生成指南](https://developers.openai.com/api/docs/guides/text)里的 instructions 示例和[流式输出指南](https://developers.openai.com/api/docs/guides/streaming-responses)里的事件循环拼在了一起。

文档的示例还设置了 `reasoning: { effort: "low" }`。推理强度告诉推理模型回答前要想多少，强度越低，速度越快，token 越少。可用的取值因模型而异，推理指南写明 GPT-6.1 Sol 和 GPT-6 Luna 的默认值是 `medium`。Python 里传 `stream=True`，再用 `for event in stream` 遍历，检查的 `event.type` 名称完全相同。

````javascript
import OpenAI from "openai";
const client = new OpenAI();

const stream = await client.responses.create({
  model: "gpt-6-astra",
  reasoning: { effort: "low" },
  instructions: "Talk like a pirate.",
  input: "Are semicolons optional in JavaScript?",
  stream: true,
});

for await (const event of stream) {
  if (event.type === "response.output_text.delta") {
    process.stdout.write(event.delta);
  } else if (event.type === "response.completed") {
    console.log("\nResponse completed.");
  } else if (event.type === "error") {
    console.error(event.message);
  }
}
````

- `response.created` 只出现一次，在回复开始时。
- `response.output_text.delta` 会出现很多次，每次的 `delta` 里是下一段文字。
- `response.completed` 只出现一次，在生成结束时。
- `error` 可能在流的中途出现，所以循环里要给它单独一个分支。

**旧接口和它们的日期**

1. **2026年6月3日** 弱化提示词对象的创建。可复用提示词对象开始弃用，文本生成指南要求把提示词放在代码里。
2. **2026年8月26日** Assistants API 停用。它已经无法使用，迁移指南把它的用户引向 Responses API。
3. **2026年11月30日** 提示词接口关闭。文本生成指南给出的 v1/prompts 停止服务日期。

来源：[OpenAI Docs: Text generation](https://developers.openai.com/api/docs/guides/text) (2026-11-05 查阅)；[OpenAI Docs: Migrate to the Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) (2026-11-05 查阅)

> 一旦开始读取流，Python SDK 就不会再自动重试，因为重放可能把应用已经显示过的文字再输出一遍。流式输出也让内容审核更难，因为不完整的回答比完整的更难判断。如果你要求返回审核分数，它们要等完整输出生成后才会到达。

## 让对话跨回合延续

先问“tell me a joke”，再在第二个请求里问“explain why this is funny”，第二个请求对第一个一无所知。每次调用都是独立的。要让模型拿到上下文，你有三种办法。

手动回放最直白。在应用里维护一个输出项列表，每次回复后把模型的 `output` 追加进去，再加上下一条用户消息，把整个列表作为 `input` 发出去。没有任何东西替你保存，也没有任何东西瞒着你。

最轻的是 `previous_response_id`：传入上一次响应的 id，只带新的用户输入，前面的回合由 OpenAI 补上。这能成立，是因为响应默认会被存储，存储的响应对象保留 30 天。下面的代码就是对话状态指南里的示例。

串联省掉的是重复发送文字，省不掉费用：链上所有先前的输入 token，在下一个回合仍按输入计费。如果你学过 [Claude API 课程](https://mindket.com/cn/learn/claude-api/claude-api-quickstart)，这里的变化很大，因为 Anthropic 的 Messages API 是无状态的，历史总要由你的代码重新发送。

如果一段对话要跨会话保留，或者在不同设备之间接续，就用 `client.conversations.create()` 创建一个 conversation，并在每次请求里把它的 id 作为 `conversation` 传入。conversation 用自己的持久 id，把消息、工具调用和工具输出都存成输出项。

三种办法都会撞上同一个上限。上下文窗口是单次请求能用的最多 token 数，输入、输出和推理 token 都算在里面。长对话一个回合一个回合地逼近它，超出上限的输出可能被截断。对这种情况，指南指向压缩功能，在 `/responses` 上通过 `context_management` 和 `compact_threshold` 设置。发送之前，还可以用它提供的分词工具数一数一段文字有多少 token。

````python
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="tell me a joke",
)
print(response.output_text)

second_response = client.responses.create(
    model="gpt-6-astra",
    previous_response_id=response.id,
    input=[{"role": "user", "content": "explain why this is funny."}],
)
print(second_response.output_text)
````

| 方式 | 历史存在哪里 | 每个回合发送什么 | 保留多久 |
| --- | --- | --- | --- |
| 手动回放 | 你的应用 | 之前的所有输出项，加上新消息 | 你保留多久就多久，`store` 可以设为 false |
| `previous_response_id` | OpenAI，按响应逐个保存 | 上一次响应的 id 和新输入 | 响应对象默认保留 30 天 |
| Conversations API | 一个有自己 id 的 conversation 对象 | conversation id 和新输入 | 其中的输出项不受 30 天期限限制 |

> 在 `store` 设为 false 的情况下手动回放？要保留 `output` 里的每一项，包括加密的推理项。Python 里就是在下一条用户消息之前执行 `history += response.output`。

## Responses 和 Chat Completions 是什么关系

Chat Completions 是更早的接口，路径是 `/v1/chat/completions`，现在仍受支持。迁移指南建议所有新项目都用 Responses，而有些模型直接替你做了选择：GPT-6 Astra 和 GPT-6.1 Sol 只能通过 Responses 调用工具。从 GPT-5.4 起，只要推理强度不是 `none`，Chat Completions 也不再支持工具调用。

指南把迁移看成三处改动：请求发到 `/v1/responses`，从带类型的 `output` 列表读取输出，再决定应用怎样在回合之间保存状态。只有文本的消息列表可以原样迁移，同一个数组在一个接口里叫 `messages`，在另一个接口里叫 `input`。

其余部分的结构都变了，下表就是对照图。Chat Completions 把文本、工具调用和元数据黏在一个消息对象里，Responses 则把它们拆成各自独立的输出项。[迁移指南](https://developers.openai.com/api/docs/guides/migrate-to-responses)给下表每一行都配了左右对照的示例。

存储方式也不同。Responses 默认会被存储，Chat Completions 只对新账号默认存储，两个接口都可以用 `store: false` 关闭存储。迁移旧代码时，有两个缺口最容易绊人。Responses 没有 `n` 参数，想要多个候选回答，就多请求几次。Assistants API 已经停用，还在调用它的代码只能在 Responses 上重写。

| Chat Completions | Responses API |
| --- | --- |
| `messages` 数组 | `input`，一个字符串或一组输出项 |
| system 或 developer 消息 | 顶层的 `instructions` |
| `choices[0].message.content` | `output_text`，或者 `output` 里的 `message` 项 |
| 工具调用放在助手消息里 | 独立的 `function_call` 项，靠 `call_id` 配对 |
| 用 `response_format` 指定 JSON schema | `text.format` |
| 带 `delta` 字段的流式片段 | 带类型的事件，比如 `response.output_text.delta` |
| 用 `n` 一次生成多个结果 | 不支持，需要分开请求 |
| 函数默认是非严格模式 | 省略 `strict` 时会先尝试严格模式 |

## Atty Eleti 和 OpenAI Developers 谈 Responses API

Atty Eleti 在 OpenAI 工作，是这个接口的设计者之一。2025-03-11 Responses API 发布当天，他[在 X 上介绍了它](https://x.com/athyuttamre/status/1899541471532867821)，称它是 OpenAI API 新的基础原语，凝结了两年的经验，也是构建智能体的基础。这条帖子是一串讲设计经过的帖子的开头。你第一次调用拿回的那个带类型的 item 列表，就是这个原语在工作，所以加工具之前，先认熟这些类型。

OpenAI Developers 在 2025-08-26 [发帖](https://x.com/OpenAIDevs/status/1960409187122602172)说，Assistants API 的 beta 会在一年后停用，并附上迁移到 Responses 的指南。同一条帖子还说，Responses API 的 token 用量已经超过了 Chat Completions。时间线里的停用日期就是这一次。还在调用 Assistants 的代码现在得重写，而 Chat Completions 的代码可以一个流程一个流程地迁。

> 它是我们推荐的 OpenAI API 接入方式，现在如此，将来也是。
>
> OpenAI Developers, OpenAI 面向开发者的官方账号，引文为译文, [X](https://x.com/OpenAIDevs/status/1960409187122602172) (2026-11-05 查阅)

[![Atty Eleti（@athyuttamre）2025 年 3 月 11 日在 X 上发帖：推出 Responses API，OpenAI API 新的基础原语。它凝结了两年设计 OpenAI API 的经验，也是我们构建智能体下一阶段的基础。下面讲讲我们是怎样设计它的。](https://mindket.com/blog/shots/x-athyuttamre-responses-api.webp?v=18e0ff1d7e48)](https://x.com/athyuttamre/status/1899541471532867821)

Atty Eleti 在 X 上，2025 年 3 月 11 日

[![OpenAI Developers（@OpenAIDevs）2025 年 8 月 26 日在 X 上发帖：我们正在逐步停用 Assistants API beta，它将在一年后，也就是 2026 年 8 月 26 日停用。我们准备了迁移到 Responses API 的指南。帖子还说，Responses API 的 token 用量已经超过 Chat Completions。](https://mindket.com/blog/shots/x-openaidevs-assistants-sunset.webp?v=baaf4f0ad42d)](https://x.com/OpenAIDevs/status/1960409187122602172)

OpenAI Developers 在 X 上，2025 年 8 月 26 日

[Build Hour: Responses API](https://www.youtube.com/watch?v=hNr5EebepYs)

OpenAI 自己的 YouTube 频道上，Build Hour 系列讲 Responses API 的一期。

> X 官方嵌入卡片的截图，拍摄于 2026-11-05，时间为 UTC。点击截图可到 X 上打开原帖。X 用 Show more 截断了 OpenAI Developers 的帖子。

## 常见错误

- 出于习惯，从 Responses 对象里读取 `choices[0].message.content`。
- 把 `output[0]` 当成回答，而它前面其实还有一个推理项。
- 手动回放历史时，丢掉了推理项或工具项。
- 串联的回合没带上 instructions，然后纳闷语气为什么变了。
- 既传了 `previous_response_id`，又回放了完整对话，结果模型把同样的回合读了两遍。
- 以为 `previous_response_id` 能让先前的回合免费，其实它们每次都按输入计费。
- 把带类型的 Responses 事件交给旧的流处理代码，而它还在等 `delta` 片段。
- 用 `dangerouslyAllowBrowser` 把密钥放进公开网页。

## 自己试一试

- 问一个普通问题，打印 `response.output` 里每一项的类型。`gpt-6-astra` 返回了哪些类型？
- 把 instructions 改成放在 `input` 最前面的一条 `developer` 消息，对比两次回复。
- 用 `previous_response_id` 串起三个回合，第三个回合不带 `instructions`。语气还保持得住吗？
- 用 `store=False` 发一次调用，再试着从它的 id 串联第二个回合。会发生什么？
- 同一个问题，先把 `reasoning` 强度设为 `low`，再设为 `high`，记下第一个流式词出现要等多久。
- 照着上面的对照表，迁移你自己代码里的一个 Chat Completions 调用，再把两边的输出并排比较。

## 接下来

[第 2 课](https://mindket.com/cn/learn/openai-api/openai-function-calling)会给模型加上工具，包括你用 JSON Schema 描述的函数，以及由 OpenAI 替你运行的网页搜索和文件搜索。它就从第一张表里的 `function_call` 项讲起，还有把它和你的结果配对起来的 `call_id`。

本课里的每一个限额和每一笔费用都按 token 计算。文字在送到模型之前，会先被切分成 token，弄清这一点，串联的对话开始超出预算时，你就知道钱花在了哪里。

## 自测一下

1. 你用 previous_response_id 串起第二个回合，第一个回合发过的 instructions 会怎样？

   A. 它们会自动延续
   B. 它们不在上下文里，所以要重新发送
   C. 它们会变成一条 developer 消息
   D. 它们会让请求返回 400 错误

   答案：B。 instructions 只对当前请求生效，所以串联的回合要重新发送。

2. 请求里有推理或工具时，哪种读取回复的方式靠得住？

   A. 遍历 response.output，逐项检查类型
   B. 读取 output[0].content[0].text
   C. 读取 choices[0].message.content
   D. 读取第一个流式事件

   答案：A。 输出列表可能以推理项或工具调用开头，所以代码要按每一项的类型分别处理。

3. 哪个流式事件带着下一段文字？

   A. response.created
   B. response.completed
   C. error
   D. response.output_text.delta

   答案：D。 每个 response.output_text.delta 事件的 delta 字段里，都是下一段文字。

4. 你把一个使用 JSON schema 的 Chat Completions 请求迁到 Responses，schema 该放在哪里？

   A. response_format
   B. instructions
   C. text.format
   D. tools

   答案：C。 在 Responses API 里，结构化输出从 response_format 移到了 text.format。

## 常见问题

### OpenAI API 免费吗？

只有第一次测试是免费的。快速入门把你的第一个请求称为免费测试，之后会把你带到[账单页面](https://platform.openai.com/settings/organization/billing/overview)去充值额度，供正式使用。从那以后，用量按 token 计量，记在你的组织账上。价格经常变动，所以这一课不写具体数字。

### 刚开始该用哪个 OpenAI 模型？

2026 年 11 月 5 日的[模型页面](https://developers.openai.com/api/docs/models)建议：复杂推理和编程用 GPT-6 Astra，兼顾能力和成本用 GPT-6.1 Sol，大批量任务用 GPT-6 Luna。三者标注的上下文窗口都是 1,050,000 个 token，最多输出 128,000 个 token。上线生产时，文本生成指南要求固定一个带日期的快照，比如 `gpt-5.5-2026-04-23`，这样不同版本发布之间行为不会变。

### SDK 会自己重试哪些错误？

连接错误，以及 408、409、429 和 500 以上的状态码，默认各重试两次，采用较短的指数退避。请求超时时间是 10 分钟。Python 用 `max_retries`，JavaScript 用 `maxRetries` 来调整，失败的调用要记录 `_request_id`，这是 [Python SDK](https://github.com/openai/openai-python) README 给的建议。

### 现在就得重写我的 Chat Completions 代码吗？

不必一次全改。指南建议一次迁移一个用户流程，先从纯文本生成开始，比较延迟、token 用量和错误之后，再把更多流量切过去。需要在最新模型上调用函数的流程，要最先迁移。

## 资料来源

- [OpenAI Docs: Developer quickstart](https://developers.openai.com/api/docs/quickstart) (2026-11-05 查阅)
- [OpenAI Docs: Text generation](https://developers.openai.com/api/docs/guides/text) (2026-11-05 查阅)
- [OpenAI Docs: Streaming API responses](https://developers.openai.com/api/docs/guides/streaming-responses) (2026-11-05 查阅)
- [OpenAI Docs: Conversation state](https://developers.openai.com/api/docs/guides/conversation-state) (2026-11-05 查阅)
- [OpenAI Docs: Migrate to the Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses) (2026-11-05 查阅)
- [OpenAI Docs: Reasoning models](https://developers.openai.com/api/docs/guides/reasoning) (2026-11-05 查阅)
- [OpenAI Docs: SDKs and CLI](https://developers.openai.com/api/docs/libraries) (2026-11-05 查阅)
- [OpenAI Docs: API keys and service accounts](https://developers.openai.com/api/docs/guides/api-keys) (2026-11-05 查阅)
- [OpenAI Docs: Models](https://developers.openai.com/api/docs/models) (2026-11-05 查阅)
- [OpenAI Docs: GPT-6 Astra](https://developers.openai.com/api/docs/models/gpt-6-astra) (2026-11-05 查阅)
- [openai/openai-python on GitHub](https://github.com/openai/openai-python) (2026-11-05 查阅)
- [openai/openai-node on GitHub](https://github.com/openai/openai-node) (2026-11-05 查阅)
- [X: Atty Eleti introduces the Responses API](https://x.com/athyuttamre/status/1899541471532867821) (2026-11-05 查阅)
- [X: OpenAI Developers on winding down the Assistants API beta](https://x.com/OpenAIDevs/status/1960409187122602172) (2026-11-05 查阅)
- [YouTube (OpenAI): Build Hour: Responses API](https://www.youtube.com/watch?v=hNr5EebepYs) (2026-11-05 查阅)

[浏览 AI 智能体](https://mindket.com/cn/agents)
