
本文详细阐述了在Openai Assistant API中处理函数调用并正确提交工具输出的方法。针对常见的`BadRequestError`问题,文章深入分析了错误原因,并提供了使用`client.beta.threads.runs.submit_tool_outputs`的正确解决方案。通过完整的python代码示例和注意事项,旨在帮助开发者高效、准确地实现Assistant的函数调用功能,确保API交互的顺畅与可靠。
理解openai Assistant的函数调用机制
OpenAI Assistant API提供了一个强大的功能,允许助手在对话过程中调用外部工具或函数来获取信息或执行操作。当助手需要调用某个函数时,它会将对话状态从“in_progress”转换为“requires_action”,并在run.required_action.submit_tool_outputs.tool_calls中提供需要调用的函数信息,包括函数名和参数。开发者的任务是执行这些函数,并将结果反馈给助手,以便其继续生成响应。
常见的错误与原因分析
许多开发者在首次尝试向Assistant提交函数执行结果时,可能会遇到类似于以下代码片段所示的错误:
# 错误的尝试 message = client.beta.threads.messages.create( thread_id=thread.id, role="tool", # 错误:此角色不适用于创建新消息来提交工具输出 content=ret, )
当尝试使用client.beta.threads.messages.create方法并指定role=”tool”来提交函数执行结果时,API会返回openai.BadRequestError: Error code: 400 – {‘error’: {‘message’: “1 validation error for Requestnbody -> rolen value is not a valid enumeration member; permitted: ‘user’ (type=type_error.enum; enum_values=[
立即学习“Python免费学习笔记(深入)”;
这个错误清晰地表明,client.beta.threads.messages.create方法中的role参数仅接受”user”(用户消息)或”assistant”(助手消息)。它并不是用来提交工具函数执行结果的接口。工具函数的结果需要通过专门的机制来通知Assistant,而不是作为普通对话消息的一部分。
正确的函数结果提交方法
OpenAI Assistant API为提交工具函数执行结果提供了专用的方法:client.beta.threads.runs.submit_tool_outputs。这个方法允许你将一个或多个工具调用的输出结果与特定的运行(Run)关联起来,从而让Assistant能够获取这些结果并继续处理。
其基本用法如下:
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": "your_function_output_string_here", }, # 如果有多个工具调用,可以在这里添加更多字典 ] )
- thread_id: 当前会话的线程ID。
- run_id: 当前处于requires_action状态的运行ID。
- tool_outputs: 一个列表,包含所有需要提交的工具输出。每个输出是一个字典,必须包含:
- tool_call_id: 助手在run.required_action.submit_tool_outputs.tool_calls中提供的具体工具调用的ID。这个ID用于将输出与助手请求的特定函数调用进行匹配。
- output: 对应工具函数执行后的结果字符串。
完整的示例代码
下面是一个完整的Python示例,演示了如何正确地处理Assistant的函数调用并提交结果:
from openai import OpenAI import time import os import json # 导入json库,用于处理函数参数 # 初始化OpenAI客户端 client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) # 建议从环境变量获取API_KEY # 假设你已经创建并配置了一个Assistant,其ID存储在环境变量中 # 并添加了名为 'funnyfunc' 的工具 # 例如:assistant = client.beta.assistants.create( # name="Function Caller Assistant", # instructions="You are a helpful assistant that can call the funnyfunc.", # model="gpt-4-turbo", # tools=[{"type": "function", "function": {"name": "funnyfunc", "description": "Returns a funny value."}}] # ) # 然后将 assistant.id 存储到环境变量 ALEXA_ASSISTANT_ID_OPENAI 中 assistant_id = os.environ.get("ALEXA_ASSISTANT_ID_OPENAI") if not assistant_id: raise ValueError("请设置环境变量 ALEXA_ASSISTANT_ID_OPENAI 为您的Assistant ID") # 创建一个新的线程 thread = client.beta.threads.create() # 辅助函数:等待Run完成 def wait_on_run(run_obj, thread_obj): """ 等待Run对象完成执行,并返回最新的Run状态。 """ while run_obj.status in ["queued", "in_progress", "cancelling"]: time.sleep(0.5) # 适当增加等待时间以减少api调用频率 run_obj = client.beta.threads.runs.retrieve( thread_id=thread_obj.id, run_id=run_obj.id, ) return run_obj # 模拟自定义函数 def funnyfunc(): """一个简单的模拟函数,返回一个字符串。""" print("Executing funnyfunc...") return "The funniest number is five!" # 核心函数:向Assistant提问并处理函数调用 def ask_question_and_handle_tools(question_content): """ 向Assistant提问,并处理可能的函数调用。 """ # 1. 向线程添加用户消息 client.beta.threads.messages.create( thread_id=thread.id, role="user", content=question_content, ) # 2. 创建并运行Assistant run = client.beta.threads.runs.create( thread_id=thread.id, assistant_id=assistant_id, ) # 3. 等待Run完成或需要动作 run = wait_on_run(run, thread) # 4. 循环处理 'requires_action' 状态 while run.status == 'requires_action': if run.required_action.type == 'submit_tool_outputs': tool_outputs_to_submit = [] for tool_call in run.required_action.submit_tool_outputs.tool_calls: function_name = tool_call.function.name tool_call_id = tool_call.id # 假设我们只处理 'funnyfunc' if function_name == 'funnyfunc': # 在实际应用中,这里会解析 tool_call.function.arguments 并传递给实际函数 # 例如:args = json.loads(tool_call.function.arguments) # result = funnyfunc(**args) result = funnyfunc() # 调用模拟函数 tool_outputs_to_submit.append({ "tool_call_id": tool_call_id, "output": result, }) else: # 对于未实现的函数,可以返回一个错误或默认值 print(f"Warning: Unimplemented function called: {function_name}") tool_outputs_to_submit.append({ "tool_call_id": tool_call_id, "output": f"Error: Function '{function_name}' not implemented.", }) # 提交工具输出 run = client.beta.threads.runs.submit_tool_outputs( thread_id=thread.id, run_id=run.id, tool_outputs=tool_outputs_to_submit ) # 提交后,需要再次等待Run完成 run = wait_on_run(run, thread) else: raise NotImplementedError(f"Unhandled required action type: {run.required_action.type}") # 5. Run完成后,获取Assistant的响应 if run.status == 'completed': messages = client.beta.threads.messages.list( thread_id=thread.id, order="desc", # 获取最新消息 limit=1 # 只获取一条 ) # 查找最新的Assistant消息 for message in messages.data: if message.role == "assistant": for content_block in message.content: if content_block.type == "text": return content_block.text.value return "No assistant response found." else: return f"Run finished with status: {run.status}" # 提问并打印结果 response = ask_question_and_handle_tools("What is funnyfunc() equal to right now?") print("nassistant's final response:") print(response)
运行前请确保:
- 你已设置 OPENAI_API_KEY 环境变量。
- 你已创建了一个OpenAI Assistant,并为其添加了名为 funnyfunc 的Function Tool。
- 将你的Assistant ID赋值给 ALEXA_ASSISTANT_ID_OPENAI 环境变量。
注意事项
- 错误处理与重试机制:在生产环境中,wait_on_run函数应包含更健壮的错误处理和指数退避重试机制,以应对API调用失败或暂时性服务问题。
- 多工具调用:如果Assistant在一个requires_action状态下请求调用多个工具,run.required_action.submit_tool_outputs.tool_calls将是一个列表。你的代码需要遍历这个列表,为每个工具调用执行相应的函数,并收集所有结果,然后一次性通过submit_tool_outputs提交。
- 函数参数解析:当Assistant调用函数时,tool_call.function.arguments会包含函数参数的JSON字符串。你需要使用json.loads()来解析这些参数,并将其传递给你的实际函数。
- 状态管理:Assistant API是异步的。每次提交工具输出后,都需要再次等待Run完成或进入下一个requires_action状态。
- API版本:请确保你的openai Python库是最新版本,以兼容OpenAI Assistant API的最新功能和行为。
总结
正确处理OpenAI Assistant的函数调用是构建智能对话系统的关键一步。核心在于理解client.beta.threads.messages.create与client.beta.threads.runs.submit_tool_outputs之间的区别。前者用于常规的对话消息,而后者则是专门用于向Assistant反馈外部工具或函数执行结果的接口。通过遵循本文提供的指南和示例代码,开发者可以有效避免常见的BadRequestError,并成功地将外部功能集成到OpenAI Assistant中,从而实现更丰富、更强大的交互体验。