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 服务器上的接口,这是课程项目和页面一起运行的一个小型 Node 程序,目的是让模型请求不经过浏览器代码,从而不会暴露你的 API 密钥。在 Scrimba 上,这些服务器接口的输出会显示在 Runner 标签页,而不是 Console 标签页。

下面两个例子用的是同一个客户端,它是根据你在配置服务商中保存的值一次性创建的:

js
import OpenAI from "openai"

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

Chat Completions

Chat Completions 把系统提示词和用户输入一起放进 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 一次调用可以返回好几个候选回复。

你几乎总是只需要第一个,所以 choices[0].message.content 会变成一种下意识的写法。记住这条路径也很有用,因为一看到它,你就能立刻判断出某段代码用的是 Chat Completions 而不是 Responses。

JunoChat Completionschoices 这个复数形式几乎从来都只有一个元素。它源自早期 completion API 的 n 参数,那个参数可以在一次请求里要求返回多个独立样本,如今大多数人早就不用这个功能了,但这种响应结构却一直保留了下来。

了解这一点很有用,因为它说明了你即将放弃的那些便利。每次读取都要多绕一个没有任何实际信息的数组下标,而且每追加一轮工具调用,都要手动把它重新拼进同一个扁平列表里。

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 里。一步到位,不用绕三层。

JunoResponses 两个实际的不同点。第一,instructions 把系统提示词和对话内容分开了,所以你不用每一轮都重新拼一个数组来把它保持在最前面。第二,output_text 直接给你文本内容。

input 在需要的时候仍然可以用消息数组的形式,课程后面讲对话历史和工具调用结果时就会用到这种形式。字符串形式只是简单场景下的一种便捷写法,并不是另一套 API。

JunoResponsesoutput_text 只是对 output 的一种便捷封装,真正的返回值其实是 output:一个由带类型的条目组成的列表,而不是单个消息。这个结构才是关键所在,因为一轮调用了工具的回复会把工具调用条目和文本一起返回,扁平的字符串内容根本装不下这些东西。

所以,要拿最终答案就读 output_text,要弄清模型到底做了什么就读 output。一旦你用上第一个工具,你就会开始盯着后者看了。

本课程为什么选用 Responses

Chat Completions 仍然是一个可用的 API,本课程里的智能体功能完全也可以用它来搭建。

本课程选用 Responses,原因就是你在上面两段代码里已经能看出来的那个差异。读取回复只需要 response.output_text,而不是 response.choices[0].message.content;系统提示词有了自己专属的位置,不再是一个数组里的第一项,还要和你后续追加的用户轮次挤在一起。一旦工具调用和对话历史开始累积,这种结构就意味着你不用写那么多代码去把各个部分粘合在一起。

需要完整结构时用 output

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

Juno本课程为什么选用 Responses 这两个 API 都能满足本课程的所有需求。Responses 需要敲的代码更少:读取回复只要一步,不用绕三层,而且系统提示词有自己专属的字段。

你不需要死记硬背它们的区别。这里用 Responses 就好,等你在别处碰到 Chat Completions 时,能认出它就够了。

Juno本课程为什么选用 Responses 这个选择关乎你要写多少胶水代码来包裹这次调用。用 Responses 的话,系统提示词有了专属位置,不再是必须固定在第零位的数组元素,读取回复也只需要深入一层属性。

Chat Completions 并没有被废弃,很多生产环境的代码仍在用它。如果你接手的是一个基于它搭建的代码库,这里讲的内容不需要你去重写它。

Juno本课程为什么选用 Responses 这个差异不是在第一次请求时就能看出来的,而是会随着使用不断累积放大。在一个工具调用循环里,你要先追加模型这一轮的输出,再追加工具的结果,然后再次调用——用 Chat Completions 的话,每次都得自己手动重新拼出那整个扁平数组,还要把系统消息一直固定在最前面。

Responses 把一轮交互建模成一组条目,这恰好就是这类循环实际需要的结构。这才是真正的关键所在,只是在上面两个简单示例里完全看不出来,所以在你真正碰到它之前,值得先把这一点说清楚。

接下来去哪儿

在本地运行代码讲解了从 Scrimba 下载课程后,浏览器和 Express 服务器是如何连接起来的。如果你还没选好模型,可以看看推荐模型,里面介绍了智能体工作对模型的要求。