Assistant API 流式传输中接入外部函数实例(完整代码)

一、Submit Tool Outputs方法详解

  1. 适用场景剖析:在OpenAI Assistant API的应用中,submit_tool_outputs方法具有特定的使用场景。当我们创建的Assistant对象配备了外部工具,并且模型根据用户输入判断需要调用这些工具时,在手动调用工具并获得参数及返回结果后,submit_tool_outputs方法就发挥作用了。它将工具调用的结果添加到线程中,使得模型能够基于这些结果继续完成后续任务。如果在应用中没有定义外部工具,仅仅是让模型生成常规回复,那就无需使用该方法来启用流式输出。
  2. 重要性解读:在构建功能丰富的AI agent时,往往需要为其配备各种复杂的工具。submit_tool_outputs方法在这一过程中扮演着关键角色,它是实现利用外部工具增强AI agent功能的重要环节。掌握该方法的使用,对于开发高效、智能的应用至关重要。
    在这里插入图片描述

二、前期准备工作

  1. 定义外部函数:为了演示submit_tool_outputs方法在函数调用中的流式传输,我们编写了一个模拟获取实时天气的外部函数get_current_weather。这个函数根据输入的地点(如北京、上海、广州)返回相应的温度信息,如果输入的地点不在预设列表中,则返回未知温度。函数接收location(表示地点)和unit(表示温度单位,默认值为华氏度)两个参数。
def get_current_weather(location, unit="celsius"):
    if "beijing" in location.lower():
        return json.dumps({"location": "Beijing", "temperature": "15", "unit": unit})
    elif "shanghai" in location.lower():
        return json.dumps({"location": "Shanghai", "temperature": "20", "unit": unit})
    elif "guangzhou" in location.lower():
        return json.dumps({"location": "Guangzhou", "temperature": "25", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})
  1. 编写JSON Schema表示:为了让大模型清楚何时以及如何调用外部函数get_current_weather,我们需要定义一个JSON Schema表示get_weather_desc。在这个定义中,明确了类型为function,函数名为get_current_weather,描述了函数的功能是获取给定地点的当前天气。同时,详细定义了参数,包括location(必填,字符串类型,用于指定城市和州)和unit(字符串类型,取值为celsiusfahrenheit)。
get_weather_desc = {
    "type": "function",
    "function": {
        "name": "get_current_weather",
        "description": "获取给定地点的当前天气",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "城市和州,例如北京"
                },
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location"]
        }
    }
}
  1. 建立函数名与函数对象的映射:为了方便在模型识别到需要调用外部函数时动态执行函数,我们创建了一个available_functions字典,将函数名get_current_weather映射到对应的函数对象,这样在后续操作中可以快速调用函数并获取执行结果。
available_functions = {
    "get_current_weather": get_current_weather
}

三、搭建AI agent框架

  1. 实例化客户端与创建Assistant对象:首先,我们需要实例化OpenAI客户端,这是与OpenAI服务进行交互的基础。然后创建Assistant对象,为其设置名称、指令、使用的模型,并通过tools参数传递get_weather_desc,使Assistant能够识别并使用外部工具。这里设置名称为“你是一个实时天气小助理”,指令为“你可以调用工具,获取到当前的实时天气,再给出最终的回复”,模型选择gpt-4o-mini-2024-07-18
from openai import OpenAI
client = OpenAI()
assistant = client.assistants.create(
    name="你是一个实时天气小助理",
    instructions="你可以调用工具,获取到当前的实时天气,再给出最终的回复",
    model="gpt-4o-mini-2024-07-18",
    tools=[get_weather_desc]
)

在这里插入图片描述

  1. 创建Thread对象并添加消息:接下来创建Thread对象实例,它用于管理对话线程。然后通过messages向Thread中添加用户的问题,这里我们添加的问题是“北京现在的天气怎么样?”
thread = client.threads.create()
message = client.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="北京现在的天气怎么样?"
)
  1. 执行运行并监控事件流:执行运行时,设置stream=True开启流式传输,以便实时获取运行过程中的事件流信息。在运行过程中,我们会观察到一系列事件,如ThreadRunCreated(表示创建运行事件流)、ThreadRunQueued(进入队列状态)、ThreadRunInProgress(执行状态)等。与未使用外部工具时不同,加入外部工具函数后,会出现ThreadRunStepDelta事件,并且最终运行状态会停留在ThreadRunRequiresAction,此时模型并未直接回答问题,而是等待进一步操作。
run = client.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id,
    stream=True
)

在这里插入图片描述
在这里插入图片描述

四、应对ThreadRunRequiresAction事件

  1. 提取关键信息:当遇到ThreadRunRequiresAction事件时,我们通过event.data获取相关数据,从中提取Function Info(包含运行的详细信息)、Required Action(需要执行的具体动作信息)和Tool Calls(工具调用信息),并进行打印展示,以便了解当前的运行状态和需要执行的操作。
    在这里插入图片描述
    在这里插入图片描述
#后续实际处理
for event in run:
    if event.event == 'thread.run.requires_action':
        function_info = event.data
        print("Function Info:")
        print(function_info)
        required_action = function_info.required_action
        print("Required Action:")
        print(required_action)
        tool_calls = required_action.submit_tool_outputs.tool_calls
        print("Tool Calls:")
        print(tool_calls)
  1. 执行外部函数:利用循环遍历tool_calls,依次获取每个工具调用的id、函数名和参数。然后借助available_functions字典,根据函数名动态执行函数,并将参数传递进去获取执行结果。将每个工具调用的结果添加到tool_outputs列表中,以便后续提交。
tool_outputs = []
for tool_call in tool_calls:
    tool_id = tool_call.id
    function = tool_call.function
    function_name = function.name
    function_args = json.loads(function.arguments)
    function_result = available_functions[function_name](**function_args)
    print("Function Result for {}:{}".format(function_name, function_result))
    tool_outputs.append({"tool_call_id": tool_id, "output": function_result})
print("Tool Outputs:")
print(tool_outputs)

在这里插入图片描述
在这里插入图片描述

五、运用submit_tool_outputs提交结果

  1. 提交结果并继续流程:在获取到工具调用的结果后,使用client.threads.runs.submit_tool_outputs方法提交结果。在调用该方法时,需要指定thread_id(与之前创建的线程一致)、run_id(上一个运行的ID)以及tool_outputs(包含工具调用结果的列表),同时设置stream=True开启流式传输,这样结果会被提交到当前线程并再次进入队列执行后续任务。
run_tools = client.threads.runs.submit_tool_outputs(
    thread_id=thread.id,
    run_id=function_info.id,
    tool_outputs=tool_outputs,
    stream=True
)
  1. 处理新的事件流数据:在新的事件流中,通过判断event_tool.event的类型来处理不同阶段的数据。当event_tool.eventthread.message.delta时,提取并打印模型回复的增量信息;当event_tool.eventthread.message.completed时,提取并打印完整的模型回复信息。
for event_tool in run_tools:
    if event_tool.event == 'thread.message.delta':
        text = event_tool.data.delta.content[0].text.value
        print("Delta Text Output:", text)
        print("------")
    elif event_tool.event == 'thread.message.completed':
        full_text = event_tool.data.content[0].text.value
        print("Completed Message Text:", full_text)
        print("------")

在这里插入图片描述

六、总结与未来展望

import json
# 导入OpenAI库,用于与OpenAI的API进行交互
from openai import OpenAI  

# 定义一个名为get_current_weather的函数,用于获取指定地点的当前天气状况
def get_current_weather(location, unit="celsius"):  
    # 如果输入的地点中包含“beijing”(不区分大小写)
    if "beijing" in location.lower():  
        # 返回北京的天气信息,包括地点、温度和单位,以JSON字符串形式
        return json.dumps({"location": "Beijing", "temperature": "15", "unit": unit})  
    # 如果输入的地点中包含“shanghai”(不区分大小写)
    elif "shanghai" in location.lower():  
        # 返回上海的天气信息,包括地点、温度和单位,以JSON字符串形式
        return json.dumps({"location": "Shanghai", "temperature": "20", "unit": unit})  
    # 如果输入的地点中包含“guangzhou”(不区分大小写)
    elif "guangzhou" in location.lower():  
        # 返回广州的天气信息,包括地点、温度和单位,以JSON字符串形式
        return json.dumps({"location": "Guangzhou", "temperature": "25", "unit": unit})  
    else:
        # 如果输入的地点不在上述列表中,返回未知温度的信息,包括地点和“unknown”温度,以JSON字符串形式
        return json.dumps({"location": location, "temperature": "unknown"})  

# 定义JSON Schema表示,用于描述get_current_weather函数的相关信息,以便大模型理解如何调用
get_weather_desc = {  
    "type": "function",  # 表明这是一个函数的描述
    "function": {  
        "name": "get_current_weather",  # 函数名称
        "description": "获取给定地点的当前天气",  # 函数功能描述
        "parameters": {  
            "type": "object",  # 参数是一个对象
            "properties": {  
                "location": {  # location参数
                    "type": "string",  # 类型为字符串
                    "description": "城市和州,例如北京"  # 参数描述
                },
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}  # unit参数,类型为字符串,取值为"celsius"或"fahrenheit"
            },
            "required": ["location"]  # location参数是必填的
        }
    }
}

# 创建一个字典,将函数名映射到具体的函数对象,方便后续动态调用函数
available_functions = {  
    "get_current_weather": get_current_weather
}

# 实例化OpenAI客户端,用于与OpenAI的服务进行通信
client = OpenAI()  
# 创建一个Assistant对象
assistant = client.beta.assistants.create(  
    name="你是一个实时天气小助理",  # Assistant的名称
    instructions="你可以调用工具,获取到当前的实时天气,再给出最终的回复",  # 指令,告知Assistant的任务
    model="gpt-4o-mini-2024-07-18",  # 使用的模型
    tools=[get_weather_desc]  # 传递get_weather_desc,让Assistant知道有哪些可用工具及其使用方式
)

# 创建一个Thread对象,用于管理对话线程
thread = client.beta.threads.create()  
# 向Thread中添加一条用户消息,询问北京现在的天气情况
message = client.beta.threads.messages.create(  
    thread_id=thread.id,  # 指定消息所属的线程ID
    role="user",  # 消息角色为用户
    content="北京现在的天气怎么样?"  # 消息内容
)

# 执行第一次Run,开启流式传输,以便实时获取运行过程中的事件流信息
run = client.beta.threads.runs.create(  
    thread_id=thread.id,  # 指定线程ID
    assistant_id=assistant.id,  # 指定Assistant ID
    stream=True  # 开启流式传输
)

# 初始化一个列表,用于存储外部函数调用的输出结果
tool_outputs = []  
# 遍历run中的事件
for event in run:  
    # 如果事件类型是thread.run.step.delta,表示模型正在生成调用外部函数的参数
    if event.event == 'thread.run.step.delta':  
        print("Delta Event Arguments:")  
        # 打印模型生成的用于调用外部函数的参数
        print(event.data.delta.step_details.tool_calls[0].function.arguments)  
        print("------")  
    # 如果事件类型是thread.run.requires_action,表示模型需要调用外部函数来处理用户请求
    elif event.event == 'thread.run.requires_action':  
        # 获取事件中的数据,包含运行相关信息
        function_info = event.data  
        print("Function Info:")  
        # 打印运行相关信息
        print(function_info)  
        # 获取需要执行的动作信息
        required_action = function_info.required_action  
        print("Required Action:")  
        # 打印需要执行的动作信息
        print(required_action)  
        # 获取工具调用信息
        tool_calls = required_action.submit_tool_outputs.tool_calls  
        print("Tool Calls:")  
        # 打印工具调用信息
        print(tool_calls)  
        # 遍历每个工具调用
        for tool_call in tool_calls:  
            # 获取工具调用的ID
            tool_id = tool_call.id  
            # 获取工具调用对应的函数
            function = tool_call.function  
            # 获取函数名称
            function_name = function.name  
            # 解析函数调用的参数
            function_args = json.loads(function.arguments)  
            # 通过available_functions字典动态调用函数,并传入参数,获取函数执行结果
            function_result = available_functions[function_name](**function_args)  
            print("Function Result for {}:{}".format(function_name, function_result))  
            # 将工具调用的ID和输出结果添加到tool_outputs列表中
            tool_outputs.append({"tool_call_id": tool_id, "output": function_result})  
        print("Tool Outputs:")  
        # 打印所有工具调用的输出结果
        print(tool_outputs)  

# 使用submit_tool_outputs方法提交工具输出结果,继续处理对话流程
run_tools = client.beta.threads.runs.submit_tool_outputs(  
    thread_id=thread.id,  # 指定线程ID
    run_id=function_info.id,  # 指定上一次运行的ID
    tool_outputs=tool_outputs,  # 传递工具调用的输出结果
    stream=True  # 开启流式传输
)

# 遍历新的事件流
for event_tool in run_tools:  
    # 如果事件类型是thread.message.delta,表示模型正在逐步生成回复
    if event_tool.event == 'thread.message.delta':  
        # 提取模型回复的增量信息
        text = event_tool.data.delta.content[0].text.value  
        print("Delta Text Output:", text)  
        print("------")  
    # 如果事件类型是thread.message.completed,表示模型完成了回复
    elif event_tool.event == 'thread.message.completed':  
        # 提取模型的完整回复
        full_text = event_tool.data.content[0].text.value  
        print("Completed Message Text:", full_text)  
        print("------")  
  1. 两次Run操作核心要点:在流式传输且涉及函数调用的过程中,会进行两次Run操作。第一次Run主要负责生成函数参数并手动执行外部函数,第二次Run则通过run_id与第一次Run绑定,提取函数调用结果,并基于此生成对用户问题的完整模型响应,这是实现连贯、准确回复的关键。

  2. 课程回顾与后续展望:全面介绍了OpenAI Assistant API的多种调用方法和应用技巧,涵盖了普通调用、生命周期、内置工具使用、外部工具函数调用以及流式输出等重要内容。OpenAI预计近期会推出Assistant API的新版本,鉴于其以往更新的风格,新版本可能会带来颠覆性的变化,值得我们密切关注。后续课程将介绍基于Assistant API构建的异步流式传输AI agent开发框架,该框架具有高扩展性,在满足生产环境使用API条件的情况下,可便捷地接入实际生产环境,为开发者提供更强大的开发支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值