Skip to content
This page has been auto-translated and may contain errors.View in English

Chat Completions 和 Responses ​

打开课程的练习场,你会看到两个按钮,分别对应 OpenAI 的两种 API 形式:Chat Completions 和 Responses。你在别人的代码和供应商的文档中也会看到这两个名字,但通常没人会解释为什么有两种。

它们做的是同一件事。两者都是把指令和用户消息发给模型,然后获取回复。不同之处在于你把请求的各个部分放在哪里,以及回复文本最后出现在哪里。第二个差异特别容易踩坑,当你从某个地方复制代码片段时尤其如此。

这些练习场按钮调用的是 Express 服务器上的路由。Express 是一个小型 Node 程序,课程项目在它旁边运行,这样模型请求就发生在浏览器代码之外,你的 API 密钥永远不会暴露。在 Scrimba 中,这些服务器路由的输出会显示在 Runner 标签页而不是控制台标签页中。

下面两个例子都用同一种方式构建的客户端,使用的是你在提供商设置中保存的值:

js
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.AI_KEY,
  baseURL: process.env.AI_URL,
});

Chat Completions ​

Chat Completions 是第一个稳定的、被广泛采用的语言模型 API,其他提供商也实现了相同的结构,这就是为什么它出现在很多代码示例中。它把系统提示和用户输入放在一起的 messages 中:

js
const response = await client.chat.completions.create({
  model: process.env.AI_MODEL,
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    {
      role: "user",
      content: "Give me a short explanation of why open-source tools matter.",
    },
  ],
});

回复文本嵌套在第一个 choice 下:

js
response.choices[0].message.content
JunoChat Completions 所有内容都放在一个 messages 列表中:系统提示在前,然后是你的问题。回复出现在 response.choices[0].message.content。

这个路径前几次打的时候可能觉得有点复杂。仔细读一遍,因为你几乎会在所有网上找到的代码示例中看到它。

JunoChat Completions 一个数组承载整个请求,role 决定了每个条目的用途。回复位于 choices[0] 下,因为 API 可以在一次调用中返回多个替代完成方案,而你几乎总是想要第一个。

识别这个路径本身就很有用:它能让你一眼看出你找到的代码片段是 Chat Completions 而不是 Responses。

JunoChat Completionschoices 是复数形式,但几乎总是只有一个。它的存在是为了 n 参数,该参数要求对同一个请求进行多个独立的完成,即使大多数代码都让 n 保持为 1,这个结构仍然保留了。

这解释了你即将放弃的人体工程学设计。每次读取都要付出一个数组索引的代价,而那个索引没有提供任何信息,每次你在对话中增加一个轮次时,都要手动把它加回这个平坦的列表中。

Responses ​

Responses 给系统提示提供了自己的 instructions 字段。对于简单请求,input 可以是一个直接的字符串:

js
const response = await client.responses.create({
  model: process.env.AI_MODEL,
  instructions: "You are a helpful assistant.",
  input: "Give me a short explanation of why open-source tools matter.",
});

当你需要消息形式时,同样的直接字符串可以移到一个 role/content 消息对象中:

js
input: [
  {
    role: "user",
    content: "Give me a short explanation of why open-source tools matter.",
  },
],

Responses 直接暴露回复文本:

js
response.output_text

它也把完整的响应结构保存在 response.output 中。基本的文本响应通常包含一个消息项,其 content 包括相同的输出文本。

JunoResponses 系统提示获得了自己的 instructions 字段而不是共享列表,当你只是问一个问题时 input 可以是普通字符串。

回复位于 response.output_text,只需要一步而不是三步。如果 Responses 按钮在 Runner 标签页中打印了回复,说明你的提供商和模型支持这个 API。

JunoResponsesinstructions 把系统提示保持在对话之外,所以你不需要每轮都重新构建数组来让它保持在最前面,output_text 直接给你文本。

当你需要时 input 仍然接受数组消息形式,这就是课程后面部分使用的对话历史。字符串形式只是简单情况下的快捷方式,不是一个不同的 API。

JunoResponsesoutput_text 是对 output 的 SDK 辅助,它是真正的返回值:是类型化项目的列表而不是单个消息。一个调用工具的轮次会在列表中返回工具调用项,单个内容字符串就无处放置它们了。

所以读 output_text 可以得到最终答案,读 output 可以知道模型实际做了什么。

为什么本课程使用 Responses ​

Chat Completions 仍然是一个支持的 API,本课程的智能体功能也可以用它来构建。

本课程使用 Responses 是因为你已经能在上面两个代码块中看到的区别。读取回复是 response.output_text 而不是 response.choices[0].message.content,系统提示有一个命名的位置而不是数组中的第一项(你还要往里面追加用户轮次)。一旦工具调用和对话历史开始积累,这个结构意味着更少的代码来把各个部分连接在一起。

OpenRouter 也支持 Responses API,但与 OpenAI 有一个区别:它不在服务器上存储对话状态。这不会影响本课程,因为本课程在代码中保持自己的对话历史。

需要结构时使用完整输出

output_text 是读取最终文本回复的便捷方式。需要检查完整的响应项集合时使用 output。

JunoWhy the course uses Responses 两个 API 都能做本课程需要的一切。Responses 是打字更少的那一个:回复只需要一步,系统提示有自己的字段。

你不需要记住这些区别。在这里使用 Responses,在别的地方遇到 Chat Completions 时识别它就行。

JunoWhy the course uses Responses 选择是关乎你要在调用周围写多少粘合代码。Chat Completions 仍然受支持,很多生产代码都用它,所以如果你继承了基于它构建的代码库,这里的内容都不说它需要重写。

如果你在用 OpenRouter,服务器端状态的缺失是为什么本课程存储自己的历史。每个请求都要携带它需要的对话。

JunoWhy the course uses Responses 差异是复合的而不是在第一个请求中就显现出来。在工具调用循环中,你追加模型的轮次,然后是工具结果,然后再调用,Chat Completions 让你每次都重新构建那个平坦的数组,同时还要把系统消息钉在最前面。

Responses 把轮次建模为项目,这是循环实际拥有的形状。上面两个玩具例子无法展示这一点,这就是为什么在你遇到它之前值得了解。

接下来 ​

本地运行代码解释了在你从 Scrimba 下载课程后浏览器和 Express 服务器如何连接。如果你还没有选择模型,推荐模型涵盖了智能体工作需要什么。

JunoWhere this goes next 如果两个按钮都在 Runner 标签页中打印了回复,说明你的设置没问题,可以继续课程。本地指南只有在你想在自己的计算机上运行相同项目时才需要。
JunoWhere this goes next 这里的按钮到路由的设置和你本地运行时的设置一样:页面向 /api 发送请求,Vite 把它传给 Express,模型调用发生在服务器上。
JunoWhere this goes next 一个在这里 Responses 请求失败的模型在课程中走不了多远,因为这之后的每个课程都建立在这个 API 之上。如果失败了,在做其他任何事情之前先检查模型是否在推荐列表中。