如何为 ChatGPT 模型格式化输入

ChatGPT 由 gpt-3.5-turbogpt-4 提供支持,这是 OpenAI 最先进的模型。

您可以使用 OpenAI API 通过 gpt-3.5-turbogpt-4 构建自己的应用程序。

聊天模型以一系列消息作为输入,并返回 AI 编写的消息作为输出。

本指南通过几个示例 API 调用来说明聊天格式。

1. 导入 openai 库

# 如果需要,请安装和/或升级到最新版本的 OpenAI Python 库
%pip install --upgrade openai
# 导入 OpenAI Python 库以调用 OpenAI API
from openai import OpenAI
import os

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<您的 OpenAI API 密钥,如果未设置为环境变量>"))

2. 示例聊天补全 API 调用

聊天补全 API 调用参数, 必需

  • model:您想使用的模型的名称(例如,gpt-3.5-turbogpt-4gpt-3.5-turbo-16k-1106
  • messages:一个消息对象列表,其中每个对象都有两个必需的字段:
    • role:消息者的角色(systemuserassistanttool
    • content:消息的内容(例如,写一首优美的诗

消息还可以包含一个可选的 name 字段,为消息者命名。例如,example-userAliceBlackbeardBot。名称中不能包含空格。

可选

  • frequency_penalty:根据标记的频率进行惩罚,减少重复。
  • logit_bias:使用偏差值修改指定标记的可能性。
  • logprobs:如果为 true,则返回输出标记的对数概率。
  • top_logprobs:指定每个位置返回最有可能的标记数量。
  • max_tokens:设置聊天补全中生成标记的最大数量。
  • n:为每个输入生成指定数量的聊天补全选项。
  • presence_penalty:根据新标记在文本中的存在情况进行惩罚。
  • response_format:指定输出格式,例如 JSON 模式。
  • seed:使用指定的种子确保确定性采样。
  • stop:指定最多 4 个序列,API 应在这些序列处停止生成标记。
  • stream:在标记可用时发送部分消息增量。
  • temperature:将采样温度设置为 0 到 2 之间。
  • top_p:使用核采样;考虑具有 top_p 概率质量的标记。
  • tools:模型可以调用的函数列表。
  • tool_choice:控制模型的函数调用(none/auto/function)。
  • user:用于最终用户监控和滥用检测的唯一标识符。

截至 2024 年 1 月,您还可以选择性地提交一个 functions 列表,告诉 GPT 是否可以生成要馈送到函数中的 JSON。有关详细信息,请参阅文档API 参考或 Cookbook 指南如何使用聊天模型调用函数

通常,对话会以系统消息开始,告诉助手如何表现,然后是交替的用户和助手消息,但您不必遵循此格式。

让我们通过一个示例聊天 API 调用来看看聊天格式在实践中是如何工作的。

# 示例 OpenAI Python 库请求
MODEL = "gpt-3.5-turbo"
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一个乐于助人的助手。"},
        {"role": "user", "content": "敲敲门。"},
        {"role": "assistant", "content": "谁在敲门?"},
        {"role": "user", "content": "橙子。"},
    ],
    temperature=0,
)
print(json.dumps(json.loads(response.model_dump_json()), indent=4))
{
    "id": "chatcmpl-8dee9DuEFcg2QILtT2a6EBXZnpirM",
    "choices": [
        {
            "finish_reason": "stop",
            "index": 0,
            "logprobs": null,
            "message": {
                "content": "橙子是谁?",
                "role": "assistant",
                "function_call": null,
                "tool_calls": null
            }
        }
    ],
    "created": 1704461729,
    "model": "gpt-3.5-turbo-0613",
    "object": "chat.completion",
    "system_fingerprint": null,
    "usage": {
        "completion_tokens": 3,
        "prompt_tokens": 35,
        "total_tokens": 38
    }
}

如您所见,响应对象有几个字段:

  • id:请求的 ID
  • choices:一组补全对象(只有一个,除非您将 n 设置为大于 1)
    • finish_reason:模型停止生成文本的原因(stoplength,如果达到 max_tokens 限制)
    • index:列表中选项的索引。
    • logprobs:选项的对数概率信息。
    • message:模型生成的消息对象
      • content:消息内容
      • role:此消息作者的角色。
      • tool_calls:模型生成的工具调用,例如函数调用。如果提供了工具
  • created:请求的时间戳
  • model:用于生成响应的模型全名
  • object:返回的对象类型(例如,chat.completion
  • system_fingerprint:此指纹代表模型运行的后端配置。
  • usage:用于生成回复的标记数,包括提示、补全和总数

使用以下方法提取回复:

response.choices[0].message.content
'橙子是谁?'

即使是非对话任务也可以放入聊天格式中,方法是将指令放在第一个用户消息中。

例如,要要求模型以海盗黑胡子的风格解释异步编程,我们可以将对话结构化如下:

# 带有系统消息的示例
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一个乐于助人的助手。"},
        {"role": "user", "content": "请以海盗黑胡子的风格解释异步编程。"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
啊,我的伙计!让我给你讲一个关于异步编程的故事,以可怕的海盗黑胡子的风格!

想象一下,我的伙计们。在编程的浩瀚海洋中,有时你需要同时处理多项任务。但不要害怕,因为异步编程可以拯救这一天!

你看,在传统的编程中,你需要等待一项任务完成后才能进行下一项。但有了异步编程,你就可以同时处理多项任务,就像海盗在公海上同时处理多项任务一样!

当你发送一项任务时,你不需要等待它完成,而是可以把它单独发送出去,然后继续处理下一项任务。这就像拥有一支值得信赖的水手队伍,每个人都负责自己的职责,而无需等待其他人。

现在,你可能会想,这魔法是如何实现的呢?嗯,我的伙计,这都与回调和承诺有关。当你发送一项任务时,你会附加一个回调函数。这就像在瓶子里留下一条消息,告诉任务完成后该做什么。

当任务单独运行时,你可以继续处理下一项任务,而不会浪费任何宝贵的时间。当第一个任务完成后,它会向你发送一个信号,让你知道它已经完成了。然后你就可以处理回调函数了,就像打开瓶子并阅读里面的消息一样。

但等等,还有更多!通过承诺,你可以做出更精美的安排。与其使用回调,不如做出任务将要完成的承诺。这就像你和任务之间的一份合同,发誓它会完成。

你可以为一项任务附加多个承诺,承诺不同的结果。当任务完成后,它会兑现承诺,让你知道它已经完成了。然后你就可以处理兑现,就像收集你海盗冒险的奖励一样!

所以,我的伙计们,这就是异步编程的故事,以可怕的海盗黑胡子的风格讲述的!通过回调和承诺,你可以同时处理多项任务,就像征服七大洋的海盗一样!
# 没有系统消息的示例
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "user", "content": "请以海盗黑胡子的风格解释异步编程。"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
啊,我的伙计们!围过来听我说,我要以可怕的海盗黑胡子的风格给你们讲讲神秘的异步编程艺术!

你看,在编程的世界里,有时我们需要执行一些需要很长时间才能完成的任务。这些任务可能涉及从互联网深处获取数据,或者执行一些连戴维·琼斯都会挠头的复杂计算。

在过去,我们海盗会耐心地等待每个任务完成后再进行下一个任务。但那是浪费宝贵的时间,我的伙计们!我们是海盗,总是寻找更有效率的方法来掠夺更多的战利品!

这就是异步编程的用武之地,我的伙计们。它是一种同时处理多个任务的方法,而无需等待每个任务完成后再进行下一个。这就像拥有一群恶棍同时处理不同的任务,而你则负责监督整个行动。

你看,在异步编程中,我们将任务分解成更小的块,称为“协程”。每个协程就像一个独立的海盗,负责自己的任务。当一个协程开始工作时,它不会等待任务完成后再进行下一个任务。相反,它会继续进行下一个任务,让第一个任务在后台继续进行。

现在,你可能会问:“但是黑胡子,如果我们不等待任务完成,我们怎么知道它何时完成呢?”啊,我的伙计们,这就是回调和承诺的魔力所在!

当一个协程开始工作时,它会附加一个回调或承诺。这就像留下一个漂流瓶里的消息,告诉协程完成后该做什么。所以,当协程在工作时,船员的其他成员会继续进行其他任务,沿途掠夺更多的战利品。

当一个协程完成了它的任务时,它会向回调发送一个信号或兑现承诺,让船员的其他成员知道它已经完成了。然后,船员们可以聚集在一起处理已完成任务的结果,庆祝他们的胜利并计算他们的战利品。

所以,我的伙计们,异步编程就像拥有一群海盗同时处理不同的任务,而无需等待每个任务完成后再进行下一个。这是一种提高效率、掠夺更多战利品并征服数字领域广阔海洋的方法!

现在,扬帆起航吧,我的伙计们,像真正的数字领域海盗一样拥抱异步编程的力量!啊!

3. 指导 gpt-3.5-turbo-0301 的技巧

指导模型的最佳实践可能因模型版本而异。以下建议适用于 gpt-3.5-turbo-0301,可能不适用于未来的模型。

系统消息

系统消息可用于以不同的个性和行为来引导助手。

请注意,gpt-3.5-turbo-0301 通常不像 gpt-4-0314gpt-3.5-turbo-0613 那样关注系统消息。因此,对于 gpt-3.5-turbo-0301,我们建议将重要指令放在用户消息中。一些开发人员发现,将系统消息持续移到对话的末尾可以防止模型在对话变长时注意力分散。

# 一个引导助手深入解释概念的系统消息示例
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一位友好且乐于助人的教学助手。你用简单的术语深入解释概念,并提供示例来帮助人们学习。在每次解释的最后,你会问一个问题来检查理解情况。"},
        {"role": "user", "content": "你能解释一下分数是如何工作的吗?"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
当然!分数是表示整体的一部分的一种方式。它们由两个数字组成:分子和分母。分子告诉你有多少部分,分母告诉构成整体的相等部分有多少。

让我们通过一个例子来更好地理解这一点。想象一下你有一个披萨,它被分成了 8 个相等的部分。如果你吃了 3 片,你可以将其表示为分数 3/8。这里,分子是 3,因为你吃了 3 片,分母是 8,因为整个披萨被分成了 8 片。

分数也可以用来表示小于 1 的数字。例如,如果你吃了半个披萨,你可以写成 1/2。这里,分子是 1,因为你吃了一片,分母是 2,因为整个披萨被分成了 2 个相等的部分。

现在,让我们谈谈等价分数。等价分数是表示相同数量的不同分数。例如,1/2 和 2/4 是等价分数,因为它们都表示某物的一半。要找到等价分数,你可以将分子和分母都乘以或除以相同的数字。

这里有一个问题来检查你的理解:如果你有一个被分成 12 个相等部分的蛋糕,你吃了 4 片,你吃了蛋糕的多少比例?
# 一个引导助手给出简短、切中要害的回答的系统消息示例
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一位简洁的助手。你用简短、切中要害的回答,没有冗余的阐述。"},
        {"role": "user", "content": "你能解释一下分数是如何工作的吗?"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
分数表示整体的一部分。它们有一个分子(上面的数字)和一个分母(下面的数字)。

少样本提示

在某些情况下,展示模型你想要什么比告诉模型你想要什么更容易。

展示模型你想要什么的一种方法是使用伪造的示例消息。

例如:

# 一个伪造的少样本对话示例,用于引导模型将公司行话翻译成简单的语言
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一位乐于助人的、遵循模式的助手。"},
        {"role": "user", "content": "帮我把下面的公司行话翻译成通俗易懂的英语。"},
        {"role": "assistant", "content": "当然,我很乐意!"},
        {"role": "user", "content": "新的协同效应将有助于推动收入增长。"},
        {"role": "assistant", "content": "协同工作将增加收入。"},
        {"role": "user", "content": "等我们有更多精力时,再联系讨论增加杠杆的机会。"},
        {"role": "assistant", "content": "等我们不那么忙的时候再谈谈如何做得更好。"},
        {"role": "user", "content": "这个迟来的转变意味着我们没有时间为客户的可交付成果“煮沸整个海洋”。"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
这个突然的转变意味着我们没有足够的时间为客户完成整个项目。

为了帮助澄清示例消息不是真实对话的一部分,并且模型不应回顾它们,您可以尝试将 system 消息的 name 字段设置为 example_userexample_assistant

转换上面的少样本示例,我们可以这样写:

# 公司行话翻译示例,但为示例消息设置了示例名称
response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "你是一位乐于助人的、遵循模式的助手,可以将公司行话翻译成通俗易懂的英语。"},
        {"role": "system", "name":"example_user", "content": "新的协同效应将有助于推动收入增长。"},
        {"role": "system", "name": "example_assistant", "content": "协同工作将增加收入。"},
        {"role": "system", "name":"example_user", "content": "等我们有更多精力时,再联系讨论增加杠杆的机会。"},
        {"role": "system", "name": "example_assistant", "content": "等我们不那么忙的时候再谈谈如何做得更好。"},
        {"role": "user", "content": "这个迟来的转变意味着我们没有时间为客户的可交付成果“煮沸整个海洋”。"},
    ],
    temperature=0,
)

print(response.choices[0].message.content)
这个突然的转变意味着我们没有足够的时间为客户完成整个项目。

并非所有对话工程的尝试都能一蹴而就。

如果您的第一次尝试失败了,请不要害怕尝试不同的引导或条件化模型的方法。

例如,一位开发人员发现,当他们插入一条用户消息说“到目前为止做得很好,这些都非常完美”时,模型的准确性有所提高,这有助于引导模型提供更高质量的响应。

有关如何提高模型可靠性的更多想法,请参阅我们的提高可靠性技术指南。它是为非聊天模型编写的,但它的许多原则仍然适用。

4. 计算令牌

当您提交请求时,API 会将消息转换为一系列令牌。

使用的令牌数量会影响:

  • 请求的成本
  • 生成响应所需的时间
  • 回复因达到最大令牌限制(gpt-3.5-turbo 为 4,096,gpt-4 为 8,192)而被截断的时间

您可以使用以下函数来计算消息列表将使用的令牌数量。

请注意,从消息中计算令牌的确切方式可能因模型而异。请将下面的函数计算的计数视为估计值,而不是永恒的保证。

特别是,使用可选函数输入的请求将消耗额外的令牌,这些令牌是在下面计算的估计值之上。

如何使用 tiktoken 计算令牌中了解有关计算令牌的更多信息。

import tiktoken


def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"):
    """返回消息列表使用的令牌数。"""
    try:
        encoding = tiktoken.encoding_for_model(model)
    except KeyError:
        print("警告:未找到模型。正在使用 cl100k_base 编码。")
        encoding = tiktoken.get_encoding("cl100k_base")
    if model in {
        "gpt-3.5-turbo-0613",
        "gpt-3.5-turbo-16k-0613",
        "gpt-4-0314",
        "gpt-4-32k-0314",
        "gpt-4-0613",
        "gpt-4-32k-0613",
        }:
        tokens_per_message = 3
        tokens_per_name = 1
    elif model == "gpt-3.5-turbo-0301":
        tokens_per_message = 4  # 每个消息遵循 <|start|>{role/name}\n{content}<|end|>\n
        tokens_per_name = -1  # 如果有名称,则省略角色
    elif "gpt-3.5-turbo" in model:
        print("警告:gpt-3.5-turbo 可能会随时间更新。返回的令牌数假定为 gpt-3.5-turbo-0613。")
        return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613")
    elif "gpt-4" in model:
        print("警告:gpt-4 可能会随时间更新。返回的令牌数假定为 gpt-4-0613。")
        return num_tokens_from_messages(messages, model="gpt-4-0613")
    else:
        raise NotImplementedError(
            f"""num_tokens_from_messages() 未为模型 {model} 实现。"""
        )
    num_tokens = 0
    for message in messages:
        num_tokens += tokens_per_message
        for key, value in message.items():
            num_tokens += len(encoding.encode(value))
            if key == "name":
                num_tokens += tokens_per_name
    num_tokens += 3  # 每个回复都以 <|start|>assistant<|message|> 进行预处理
    return num_tokens
# 让我们验证上面的函数是否与 OpenAI API 响应匹配
example_messages = [
    {
        "role": "system",
        "content": "你是一位乐于助人的、遵循模式的助手,可以将公司行话翻译成通俗易懂的英语。",
    },
    {
        "role": "system",
        "name": "example_user",
        "content": "新的协同效应将有助于推动收入增长。",
    },
    {
        "role": "system",
        "name": "example_assistant",
        "content": "协同工作将增加收入。",
    },
    {
        "role": "system",
        "name": "example_user",
        "content": "等我们有更多精力时,再联系讨论增加杠杆的机会。",
    },
    {
        "role": "system",
        "name": "example_assistant",
        "content": "等我们不那么忙的时候再谈谈如何做得更好。",
    },
    {
        "role": "user",
        "content": "这个迟来的转变意味着我们没有时间为客户的可交付成果“煮沸整个海洋”。",
    },
]

for model in [
    # "gpt-3.5-turbo-0301",
    # "gpt-4-0314",
    # "gpt-4-0613",
    "gpt-3.5-turbo-1106",
    "gpt-3.5-turbo",
    "gpt-4",
    "gpt-4-1106-preview",
    ]:
    print(model)
    # 上面定义的函数的示例令牌计数
    print(f"{num_tokens_from_messages(example_messages, model)} 由 num_tokens_from_messages() 计算的提示令牌。")
    # OpenAI API 的示例令牌计数
    response = client.chat.completions.create(model=model,
    messages=example_messages,
    temperature=0,
    max_tokens=1)
    token = response.usage.prompt_tokens
    print(f'{token} 由 OpenAI API 计算的提示令牌。')
    print()
gpt-3.5-turbo-1106
警告:gpt-3.5-turbo 可能会随时间更新。返回的令牌数假定为 gpt-3.5-turbo-0613。
由 num_tokens_from_messages() 计算的提示令牌 129。
由 OpenAI API 计算的提示令牌 129。

gpt-3.5-turbo
警告:gpt-3.5-turbo 可能会随时间更新。返回的令牌数假定为 gpt-3.5-turbo-0613。
由 num_tokens_from_messages() 计算的提示令牌 129。
由 OpenAI API 计算的提示令牌 129。

gpt-4
警告:gpt-4 可能会随时间更新。返回的令牌数假定为 gpt-4-0613。
由 num_tokens_from_messages() 计算的提示令牌 129。
由 OpenAI API 计算的提示令牌 129。

gpt-4-1106-preview
警告:gpt-4 可能会随时间更新。返回的令牌数假定为 gpt-4-0613。
由 num_tokens_from_messages() 计算的提示令牌 129。
由 OpenAI API 计算的提示令牌 129。