跳到主要内容

助手 API 概述(Python SDK)

nbviewer

新的助手API是我们聊天完成API的有状态演进,旨在简化助手式体验的创建,并使开发者能够访问强大的工具,如代码解释器和检索。

助手API图示

聊天完成API与助手API

聊天完成API 的基本组件是消息,您可以使用模型gpt-3.5-turbogpt-4等)对其执行完成。它轻巧而强大,但本质上是无状态的,这意味着您必须手动管理对话状态、工具定义、检索文档和代码执行。

助手API 的基本组件包括

  • 助手,它封装了基本模型、说明、工具和(上下文)文档,
  • 线程,代表对话的状态,以及
  • 运行,用于在线程上执行助手,包括文本响应和多步骤工具使用。

让我们看看如何利用这些组件创建强大的、有状态的体验。

设置

Python SDK

注意 我们已经更新了我们的Python SDK,以添加对助手API的支持,因此您需要将其更新到最新版本(撰写时为1.2.3)。

!pip install --upgrade openai

并通过运行以下命令确保其为最新版本:

!pip show openai | grep Version

Version: 1.2.3

漂亮打印助手

import json

def show_json(obj):
display(json.loads(obj.model_dump_json()))

使用助手API的完整示例

助手

通过Assistants Playground是开始使用助手API的最简单方式。

助手游乐场

让我们开始创建一个助手!我们将创建一个数学辅导助手,就像我们的文档中所介绍的那样。

创建新助手

您可以在助手仪表板中查看您创建的助手。

助手仪表板

您还可以通过Assistants API直接创建助手,就像这样:

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))


assistant = client.beta.assistants.create(
name="Math Tutor",
instructions="You are a personal math tutor. Answer questions briefly, in a sentence or less.",
model="gpt-4-1106-preview",
)
show_json(assistant)

{'id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'created_at': 1699828331,
'description': None,
'file_ids': [],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'metadata': {},
'model': 'gpt-4-1106-preview',
'name': 'Math Tutor',
'object': 'assistant',
'tools': []}

无论您是通过仪表板还是通过API创建助手,您都需要跟踪助手ID。这是您在Threads和Runs中引用助手的方式。

接下来,我们将创建一个新的线程并向其添加一条消息。这将保存我们对话的状态,这样我们就不必每次重新发送整个消息历史记录。

线程

创建一个新线程:

thread = client.beta.threads.create()
show_json(thread)

{'id': 'thread_bw42vPoQtYBMQE84WubNcJXG',
'created_at': 1699828331,
'metadata': {},
'object': 'thread'}

然后将消息添加到线程中:

message = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="I need to solve the equation `3x + 11 = 14`. Can you help me?",
)
show_json(message)

{'id': 'msg_IBiZDAWHhWPewxzN0EfTYNew',
'assistant_id': None,
'content': [{'text': {'annotations': [],
'value': 'I need to solve the equation `3x + 11 = 14`. Can you help me?'},
'type': 'text'}],
'created_at': 1699828332,
'file_ids': [],
'metadata': {},
'object': 'thread.message',
'role': 'user',
'run_id': None,
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG'}

注意 即使您不再每次发送整个历史记录,但每次运行时仍会收取整个对话历史记录的令牌费用。

运行

注意我们创建的线程与之前创建的助手没有关联!线程独立于助手存在,这可能与您之前使用ChatGPT时的体验不同(在ChatGPT中,线程与模型/GPT绑定)。

要为给定的线程获取助手的完成,我们必须创建一个运行。创建一个运行将指示助手查看线程中的消息并采取行动:可以是添加单个响应,也可以是使用工具。

注意 运行是助手API和聊天完成API之间的一个关键区别。在聊天完成中,模型只会以单个消息作出响应,而在助手API中,一个运行可能导致助手使用一个或多个工具,并可能向线程中添加多条消息。

为了让我们的助手回复用户,让我们创建这个运行。如前所述,您必须同时指定助手和线程。

run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
)
show_json(run)

{'id': 'run_LA08RjouV3RemQ78UZXuyzv6',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'cancelled_at': None,
'completed_at': None,
'created_at': 1699828332,
'expires_at': 1699828932,
'failed_at': None,
'file_ids': [],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'last_error': None,
'metadata': {},
'model': 'gpt-4-1106-preview',
'object': 'thread.run',
'required_action': None,
'started_at': None,
'status': 'queued',
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG',
'tools': []}

与在Chat Completions API中创建完成不同,创建运行是一个异步操作。它会立即返回包含运行元数据的信息,其中包括一个status,最初将设置为queuedstatus将随着助手执行操作(如使用工具和添加消息)而更新。

要知道助手何时完成处理,我们可以在循环中轮询运行。(流式传输的支持即将推出!)虽然在这里我们只检查queuedin_progress状态,但实际上一个运行可能会经历各种状态变化,您可以选择向用户展示。 (这些称为步骤,稍后将介绍。)

import time

def wait_on_run(run, thread):
while run.status == "queued" or run.status == "in_progress":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)
time.sleep(0.5)
return run

run = wait_on_run(run, thread)
show_json(run)

{'id': 'run_LA08RjouV3RemQ78UZXuyzv6',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'cancelled_at': None,
'completed_at': 1699828333,
'created_at': 1699828332,
'expires_at': None,
'failed_at': None,
'file_ids': [],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'last_error': None,
'metadata': {},
'model': 'gpt-4-1106-preview',
'object': 'thread.run',
'required_action': None,
'started_at': 1699828332,
'status': 'completed',
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG',
'tools': []}

消息

现在运行已经完成,我们可以列出线程中的消息,看看助手添加了什么。

messages = client.beta.threads.messages.list(thread_id=thread.id)
show_json(messages)

{'data': [{'id': 'msg_S0ZtKIWjyWtbIW9JNUocPdUS',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'content': [{'text': {'annotations': [],
'value': 'Yes. Subtract 11 from both sides to get `3x = 3`, then divide by 3 to find `x = 1`.'},
'type': 'text'}],
'created_at': 1699828333,
'file_ids': [],
'metadata': {},
'object': 'thread.message',
'role': 'assistant',
'run_id': 'run_LA08RjouV3RemQ78UZXuyzv6',
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG'},
{'id': 'msg_IBiZDAWHhWPewxzN0EfTYNew',
'assistant_id': None,
'content': [{'text': {'annotations': [],
'value': 'I need to solve the equation `3x + 11 = 14`. Can you help me?'},
'type': 'text'}],
'created_at': 1699828332,
'file_ids': [],
'metadata': {},
'object': 'thread.message',
'role': 'user',
'run_id': None,
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG'}],
'object': 'list',
'first_id': 'msg_S0ZtKIWjyWtbIW9JNUocPdUS',
'last_id': 'msg_IBiZDAWHhWPewxzN0EfTYNew',
'has_more': False}

正如您所看到的,消息是按照时间顺序的倒序排列的 - 这样做是为了始终将最近的结果显示在第一页(因为结果可以进行分页)。请注意这一点,因为这与聊天完成 API 中消息的顺序相反。

让我们请助手进一步解释一下结果!

# 在我们的讨论串中添加一条信息
message = client.beta.threads.messages.create(
thread_id=thread.id, role="user", content="Could you explain this to me?"
)

# 执行我们的运行
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
)

# 等待完成
wait_on_run(run, thread)

# 检索自我们上次用户消息之后添加的所有消息。
messages = client.beta.threads.messages.list(
thread_id=thread.id, order="asc", after=message.id
)
show_json(messages)

{'data': [{'id': 'msg_9MAeOrGriHcImeQnAzvYyJbs',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'content': [{'text': {'annotations': [],
'value': 'Certainly. To solve for x in the equation `3x + 11 = 14`:\n\n1. Subtract 11 from both sides: `3x + 11 - 11 = 14 - 11` simplifies to `3x = 3`.\n2. Divide both sides by 3: `3x / 3 = 3 / 3` simplifies to `x = 1`.\n\nSo, the solution is `x = 1`.'},
'type': 'text'}],
'created_at': 1699828335,
'file_ids': [],
'metadata': {},
'object': 'thread.message',
'role': 'assistant',
'run_id': 'run_IFHfsubkJv7RSUbDZpNVs4PG',
'thread_id': 'thread_bw42vPoQtYBMQE84WubNcJXG'}],
'object': 'list',
'first_id': 'msg_9MAeOrGriHcImeQnAzvYyJbs',
'last_id': 'msg_9MAeOrGriHcImeQnAzvYyJbs',
'has_more': False}

这可能会让人觉得获取响应的步骤很多,尤其是对于这个简单的示例来说。然而,您很快就会看到,我们可以在几乎不改变任何代码的情况下为我们的助手添加非常强大的功能!

示例

让我们看看如何将所有这些内容潜在地组合在一起。下面是您需要使用已创建的助手的所有代码。

由于我们已经创建了我们的数学助手,我已经将其ID保存在MATH_ASSISTANT_ID中。然后我定义了两个函数:

  • submit_message:在线程上创建一条消息,然后启动(并返回)一个新的运行
  • get_response:返回线程中的消息列表
from openai import OpenAI

MATH_ASSISTANT_ID = assistant.id # 或者像硬编码的ID一样 "asst-..."

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))

def submit_message(assistant_id, thread, user_message):
client.beta.threads.messages.create(
thread_id=thread.id, role="user", content=user_message
)
return client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id,
)


def get_response(thread):
return client.beta.threads.messages.list(thread_id=thread.id, order="asc")

我还定义了一个create_thread_and_run函数,我可以重复使用(实际上几乎与我们API中的client.beta.threads.create_and_run复合函数相同;))。最后,我们可以将我们的模拟用户请求提交到一个新的Thread。

请注意,所有这些API调用都是异步操作;这意味着我们实际上在代码中获得了异步行为,而无需使用异步库!(例如asyncio

def create_thread_and_run(user_input):
thread = client.beta.threads.create()
run = submit_message(MATH_ASSISTANT_ID, thread, user_input)
return thread, run


# 模拟并发用户请求
thread1, run1 = create_thread_and_run(
"I need to solve the equation `3x + 11 = 14`. Can you help me?"
)
thread2, run2 = create_thread_and_run("Could you explain linear algebra to me?")
thread3, run3 = create_thread_and_run("I don't like math. What can I do?")

# 现在所有运行都在执行中...

一旦所有运行都开始了,我们可以等待每个运行并获取响应。

import time

# 美观打印助手
def pretty_print(messages):
print("# Messages")
for m in messages:
print(f"{m.role}: {m.content[0].text.value}")
print()


# 在循环中等待
def wait_on_run(run, thread):
while run.status == "queued" or run.status == "in_progress":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)
time.sleep(0.5)
return run


# 等待运行1
run1 = wait_on_run(run1, thread1)
pretty_print(get_response(thread1))

# 等待运行2
run2 = wait_on_run(run2, thread2)
pretty_print(get_response(thread2))

# 等待运行3
run3 = wait_on_run(run3, thread3)
pretty_print(get_response(thread3))

# 感谢线程3上的助手 :)
run4 = submit_message(MATH_ASSISTANT_ID, thread3, "Thank you!")
run4 = wait_on_run(run4, thread3)
pretty_print(get_response(thread3))

# Messages
user: I need to solve the equation `3x + 11 = 14`. Can you help me?
assistant: Yes, subtract 11 from both sides to get `3x = 3`, then divide both sides by 3 to find `x = 1`.

# Messages
user: Could you explain linear algebra to me?
assistant: Linear algebra is the branch of mathematics that deals with vector spaces, linear equations, and matrices, focusing on the properties and operations that can be applied to vectors and linear transformations.

# Messages
user: I don't like math. What can I do?
assistant: Try finding aspects of math that relate to your interests or daily life, and consider a tutor or interactive resources to make learning more enjoyable.

# Messages
user: I don't like math. What can I do?
assistant: Try finding aspects of math that relate to your interests or daily life, and consider a tutor or interactive resources to make learning more enjoyable.
user: Thank you!
assistant: You're welcome! If you have any more questions, feel free to ask.

现在就可以了!

您可能已经注意到,这段代码实际上并不特定于我们的数学助手… 只需通过更改助手ID,这段代码就可以适用于您创建的任何新助手!这就是助手API的强大之处。

工具

Assistants API的一个关键特性是能够为我们的助手配备工具,比如代码解释器、检索和自定义函数。让我们逐个来看看每个工具。

代码解释器

让我们为我们的数学助教配备代码解释器工具,我们可以从仪表板中完成这个操作…

启用代码解释器

…或者使用助手ID访问API。

assistant = client.beta.assistants.update(
MATH_ASSISTANT_ID,
tools=[{"type": "code_interpreter"}],
)
show_json(assistant)

{'id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'created_at': 1699828331,
'description': None,
'file_ids': [],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'metadata': {},
'model': 'gpt-4-1106-preview',
'name': 'Math Tutor',
'object': 'assistant',
'tools': [{'type': 'code_interpreter'}]}

现在,让我们请助手使用它的新工具。

thread, run = create_thread_and_run(
"Generate the first 20 fibbonaci numbers with code."
)
run = wait_on_run(run, thread)
pretty_print(get_response(thread))

# Messages
user: Generate the first 20 fibbonaci numbers with code.
assistant: The first 20 Fibonacci numbers are: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, and 4181.

到此为止!助手在后台使用了代码解释器,并给出了最终的响应。

对于一些用例来说,这可能已经足够了 - 但是,如果我们想要更多关于助手具体在做什么的细节,我们可以查看运行步骤。

步骤

一个运行由一个或多个步骤组成。与运行类似,每个步骤都有一个可以查询的“状态”。这对于向用户展示步骤的进度非常有用(例如,在助手编写代码或执行检索时显示一个旋转的指示器)。

run_steps = client.beta.threads.runs.steps.list(
thread_id=thread.id, run_id=run.id, order="asc"
)

让我们来看看每个步骤的step_details

for step in run_steps.data:
step_details = step.step_details
print(json.dumps(show_json(step_details), indent=4))

{'tool_calls': [{'id': 'call_WMNqd63PtX8vZzTwaA6eWpBg',
'code_interpreter': {'input': '# Python function to generate the first 20 Fibonacci numbers\ndef fibonacci(n):\n fib_sequence = [0, 1]\n while len(fib_sequence) < n:\n fib_sequence.append(fib_sequence[-1] + fib_sequence[-2])\n return fib_sequence\n\n# Generate the first 20 Fibonacci numbers\nfirst_20_fibonacci = fibonacci(20)\nfirst_20_fibonacci',
'outputs': [{'logs': '[0,\n 1,\n 1,\n 2,\n 3,\n 5,\n 8,\n 13,\n 21,\n 34,\n 55,\n 89,\n 144,\n 233,\n 377,\n 610,\n 987,\n 1597,\n 2584,\n 4181]',
'type': 'logs'}]},
'type': 'code_interpreter'}],
'type': 'tool_calls'}
null
{'message_creation': {'message_id': 'msg_z593lE5bvcD6BngeDFHDxzwm'},
'type': 'message_creation'}
null

我们可以查看两个步骤的step_details

  1. tool_calls(复数形式,因为在单个步骤中可能有多个)
  2. message_creation

第一个步骤是一个tool_calls,具体使用了包含以下内容的code_interpreter

  • input,在调用工具之前生成的Python代码,以及
  • output,运行代码解释器的结果。

第二个步骤是一个message_creation,其中包含添加到线程中的message,用于向用户传达结果。

检索

在助手API中另一个强大的工具是检索:即上传文件,助手在回答问题时将使用这些文件作为知识库。我们可以从仪表板或API中启用此功能,上传我们希望使用的文件。

启用检索
# 上传文件
file = client.files.create(
file=open(
"data/language_models_are_unsupervised_multitask_learners.pdf",
"rb",
),
purpose="assistants",
)
# 更新助手
assistant = client.beta.assistants.update(
MATH_ASSISTANT_ID,
tools=[{"type": "code_interpreter"}, {"type": "retrieval"}],
file_ids=[file.id],
)
show_json(assistant)

{'id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'created_at': 1699828331,
'description': None,
'file_ids': ['file-MdXcQI8OdPp76wukWI4dpLwW'],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'metadata': {},
'model': 'gpt-4-1106-preview',
'name': 'Math Tutor',
'object': 'assistant',
'tools': [{'type': 'code_interpreter'}, {'type': 'retrieval'}]}
thread, run = create_thread_and_run(
"What are some cool math concepts behind this ML paper pdf? Explain in two sentences."
)
run = wait_on_run(run, thread)
pretty_print(get_response(thread))

# Messages
user: What are some cool math concepts behind this ML paper pdf? Explain in two sentences.
assistant: I am unable to find specific sections referring to "cool math concepts" directly in the paper using the available tools. I will now read the beginning of the paper to identify any mathematical concepts that are fundamental to the paper.
assistant: The paper discusses leveraging large language models as a framework for unsupervised multitask learning, where tasks are implicitly defined by the context within sequences of text. It explores the zero-shot learning capabilities of such models by showing that when a language model is trained on a vast dataset, it begins to generalize and perform tasks without explicit supervision, achieving competitive results across various natural language processing tasks using a probabilistic framework based on sequential modeling and conditional probabilities.

注意 在检索中还有更多复杂的内容,比如注释,这可能会在另一本手册中介绍。

函数

作为您的助手的最后一个强大工具,您可以指定自定义函数(类似于聊天完成API中的函数调用)。在运行期间,助手可以指示它想要调用您指定的一个或多个函数。然后,您需要负责调用该函数,并将输出提供给助手。

让我们通过为我们的数学辅导员定义一个display_quiz()函数来看一个示例。

此函数将接受一个title和一个question数组,显示测验,并为每个问题从用户获取输入:

  • title
  • questions
    • question_text
    • question_type: [MULTIPLE_CHOICE, FREE_RESPONSE]
    • choices: [“choice 1”, “choice 2”, …]

不幸的是,我不知道如何在Python Notebook中获取用户输入,所以我将使用get_mock_response...来模拟响应。这是您将获取用户实际输入的地方。

def get_mock_response_from_user_multiple_choice():
return "a"


def get_mock_response_from_user_free_response():
return "I don't know."


def display_quiz(title, questions):
print("Quiz:", title)
print()
responses = []

for q in questions:
print(q["question_text"])
response = ""

# 如果是多项选择题,请打印选项。
if q["question_type"] == "MULTIPLE_CHOICE":
for i, choice in enumerate(q["choices"]):
print(f"{i}. {choice}")
response = get_mock_response_from_user_multiple_choice()

# 否则,只需获取响应即可。
elif q["question_type"] == "FREE_RESPONSE":
response = get_mock_response_from_user_free_response()

responses.append(response)
print()

return responses

这是一个示例测验的样本:

responses = display_quiz(
"Sample Quiz",
[
{"question_text": "What is your name?", "question_type": "FREE_RESPONSE"},
{
"question_text": "What is your favorite color?",
"question_type": "MULTIPLE_CHOICE",
"choices": ["Red", "Blue", "Green", "Yellow"],
},
],
)
print("Responses:", responses)

Quiz: Sample Quiz

What is your name?

What is your favorite color?
0. Red
1. Blue
2. Green
3. Yellow

Responses: ["I don't know.", 'a']

现在,让我们用JSON格式定义这个函数的接口,这样我们的助手就可以调用它了:

function_json = {
"name": "display_quiz",
"description": "Displays a quiz to the student, and returns the student's response. A single quiz can have multiple questions.",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string"},
"questions": {
"type": "array",
"description": "An array of questions, each with a title and potentially options (if multiple choice).",
"items": {
"type": "object",
"properties": {
"question_text": {"type": "string"},
"question_type": {
"type": "string",
"enum": ["MULTIPLE_CHOICE", "FREE_RESPONSE"],
},
"choices": {"type": "array", "items": {"type": "string"}},
},
"required": ["question_text"],
},
},
},
"required": ["title", "questions"],
},
}

让我们再次通过仪表板或API更新我们的助手。

启用自定义功能

注意 将函数JSON粘贴到仪表板中有点棘手,因为缩进等原因。我只是让ChatGPT将我的函数格式化为仪表板上的一个示例一样 :).

assistant = client.beta.assistants.update(
MATH_ASSISTANT_ID,
tools=[
{"type": "code_interpreter"},
{"type": "retrieval"},
{"type": "function", "function": function_json},
],
)
show_json(assistant)

{'id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'created_at': 1699828331,
'description': None,
'file_ids': ['file-MdXcQI8OdPp76wukWI4dpLwW'],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'metadata': {},
'model': 'gpt-4-1106-preview',
'name': 'Math Tutor',
'object': 'assistant',
'tools': [{'type': 'code_interpreter'},
{'type': 'retrieval'},
{'function': {'name': 'display_quiz',
'parameters': {'type': 'object',
'properties': {'title': {'type': 'string'},
'questions': {'type': 'array',
'description': 'An array of questions, each with a title and potentially options (if multiple choice).',
'items': {'type': 'object',
'properties': {'question_text': {'type': 'string'},
'question_type': {'type': 'string',
'enum': ['MULTIPLE_CHOICE', 'FREE_RESPONSE']},
'choices': {'type': 'array', 'items': {'type': 'string'}}},
'required': ['question_text']}}},
'required': ['title', 'questions']},
'description': "Displays a quiz to the student, and returns the student's response. A single quiz can have multiple questions."},
'type': 'function'}]}

现在,我们要求进行一次测验。

thread, run = create_thread_and_run(
"Make a quiz with 2 questions: One open ended, one multiple choice. Then, give me feedback for the responses."
)
run = wait_on_run(run, thread)
run.status

'requires_action'

现在,当我们检查运行的status时,我们看到requires_action!让我们仔细看一下。

show_json(run)

{'id': 'run_98PGE3qGtHoaWaCLoytyRUBf',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'cancelled_at': None,
'completed_at': None,
'created_at': 1699828370,
'expires_at': 1699828970,
'failed_at': None,
'file_ids': ['file-MdXcQI8OdPp76wukWI4dpLwW'],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'last_error': None,
'metadata': {},
'model': 'gpt-4-1106-preview',
'object': 'thread.run',
'required_action': {'submit_tool_outputs': {'tool_calls': [{'id': 'call_Zf650sWT1wW4Uwbf5YeDS0VG',
'function': {'arguments': '{\n "title": "Mathematics Quiz",\n "questions": [\n {\n "question_text": "Explain why the square root of a negative number is not a real number.",\n "question_type": "FREE_RESPONSE"\n },\n {\n "question_text": "What is the value of an angle in a regular pentagon?",\n "choices": [\n "72 degrees",\n "90 degrees",\n "108 degrees",\n "120 degrees"\n ],\n "question_type": "MULTIPLE_CHOICE"\n }\n ]\n}',
'name': 'display_quiz'},
'type': 'function'}]},
'type': 'submit_tool_outputs'},
'started_at': 1699828370,
'status': 'requires_action',
'thread_id': 'thread_bICTESFvWoRdj0O0SzsosLCS',
'tools': [{'type': 'code_interpreter'},
{'type': 'retrieval'},
{'function': {'name': 'display_quiz',
'parameters': {'type': 'object',
'properties': {'title': {'type': 'string'},
'questions': {'type': 'array',
'description': 'An array of questions, each with a title and potentially options (if multiple choice).',
'items': {'type': 'object',
'properties': {'question_text': {'type': 'string'},
'question_type': {'type': 'string',
'enum': ['MULTIPLE_CHOICE', 'FREE_RESPONSE']},
'choices': {'type': 'array', 'items': {'type': 'string'}}},
'required': ['question_text']}}},
'required': ['title', 'questions']},
'description': "Displays a quiz to the student, and returns the student's response. A single quiz can have multiple questions."},
'type': 'function'}]}

required_action字段表示工具正在等待我们运行它并将其输出提交回助手。具体来说,是display_quiz函数!让我们从解析namearguments开始。

注意 虽然在这种情况下我们知道只有一个工具调用,但在实际情况中,助手可能选择调用多个工具。

# 提取单个工具调用
tool_call = run.required_action.submit_tool_outputs.tool_calls[0]
name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)

print("Function Name:", name)
print("Function Arguments:")
arguments

Function Name: display_quiz
Function Arguments:
{'title': 'Mathematics Quiz',
'questions': [{'question_text': 'Explain why the square root of a negative number is not a real number.',
'question_type': 'FREE_RESPONSE'},
{'question_text': 'What is the value of an angle in a regular pentagon?',
'choices': ['72 degrees', '90 degrees', '108 degrees', '120 degrees'],
'question_type': 'MULTIPLE_CHOICE'}]}

现在让我们实际调用我们的display_quiz函数,使用助手提供的参数:

responses = display_quiz(arguments["title"], arguments["questions"])
print("Responses:", responses)

Quiz: Mathematics Quiz

Explain why the square root of a negative number is not a real number.

What is the value of an angle in a regular pentagon?
0. 72 degrees
1. 90 degrees
2. 108 degrees
3. 120 degrees

Responses: ["I don't know.", 'a']

好的!(记住,这些响应是我们之前模拟的。实际上,我们会从这个函数调用中得到后端的输入。)

现在我们有了我们的响应,让我们将它们提交回助手。我们需要tool_call ID,这个ID可以在之前解析出来的tool_call中找到。我们还需要将我们的响应列表编码为一个字符串。

run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=[
{
"tool_call_id": tool_call.id,
"output": json.dumps(responses),
}
],
)
show_json(run)

{'id': 'run_98PGE3qGtHoaWaCLoytyRUBf',
'assistant_id': 'asst_9HAjl9y41ufsViNcThW1EXUS',
'cancelled_at': None,
'completed_at': None,
'created_at': 1699828370,
'expires_at': 1699828970,
'failed_at': None,
'file_ids': ['file-MdXcQI8OdPp76wukWI4dpLwW'],
'instructions': 'You are a personal math tutor. Answer questions briefly, in a sentence or less.',
'last_error': None,
'metadata': {},
'model': 'gpt-4-1106-preview',
'object': 'thread.run',
'required_action': None,
'started_at': 1699828370,
'status': 'queued',
'thread_id': 'thread_bICTESFvWoRdj0O0SzsosLCS',
'tools': [{'type': 'code_interpreter'},
{'type': 'retrieval'},
{'function': {'name': 'display_quiz',
'parameters': {'type': 'object',
'properties': {'title': {'type': 'string'},
'questions': {'type': 'array',
'description': 'An array of questions, each with a title and potentially options (if multiple choice).',
'items': {'type': 'object',
'properties': {'question_text': {'type': 'string'},
'question_type': {'type': 'string',
'enum': ['MULTIPLE_CHOICE', 'FREE_RESPONSE']},
'choices': {'type': 'array', 'items': {'type': 'string'}}},
'required': ['question_text']}}},
'required': ['title', 'questions']},
'description': "Displays a quiz to the student, and returns the student's response. A single quiz can have multiple questions."},
'type': 'function'}]}

我们现在可以再次等待运行完成,然后检查我们的线程!

run = wait_on_run(run, thread)
pretty_print(get_response(thread))

# Messages
user: Make a quiz with 2 questions: One open ended, one multiple choice. Then, give me feedback for the responses.
assistant: Thank you for attempting the quiz.

For the first question, it's important to know that the square root of a negative number is not a real number because real numbers consist of all the numbers on the number line, and that includes all positive numbers, zero, and negative numbers. However, the square root of a negative number is not on this number line; instead, it is what we call an imaginary number. When we want to take the square root of a negative number, we typically use the imaginary unit \(i\), where \(i\) is defined as \(\sqrt{-1}\).

For the second question, the correct answer is "108 degrees." In a regular pentagon, which is a five-sided polygon with equal sides and angles, each interior angle is \(108\) degrees. This is because the sum of the interior angles of a pentagon is \(540\) degrees, and when divided by \(5\) (the number of angles), it gives \(108\) degrees per angle. The choice you selected, "72 degrees," actually refers to the external angle of a regular pentagon, not the internal angle.

哇哦 🎉

结论

在这个笔记本中,我们涵盖了很多内容,给自己一个高五!希望现在你应该已经有了一个坚实的基础,可以利用Code Interpreter、Retrieval和Functions等工具构建强大的、有状态的体验!

出于简洁起见,我们没有涵盖一些部分,所以这里有一些资源供进一步探索:

  • Annotations:解析文件引用
  • Files:线程范围 vs 助手范围
  • Parallel Function Calls:在单个步骤中调用多个工具
  • 多助手线程运行:单个线程,来自多个助手的消息
  • 流式处理:即将推出!

现在去构建一些令人惊叹的东西吧!