
如何快速实现REST API集成以优化业务流程
上次我们介绍了 OpenAI 的新版 API,包括语音转文字、生成图片和图片识别等功能,这次 API 的更新还包含了一个重量级的功能,就是类似 GPTs 的 Assistant API,它不仅可以完成 GPTs 的所有功能,还能使用自定义的工具,可以说是比 GPTs 更加强大。今天我们就来介绍 Assistant API 的基本原理和使用方法,最后通过一些代码示例来展示它的强大功能。
上面是整理的 Assistant API 对象关系图,在图中我们可以看到如下关系:
API 执行过程如下:
首先我们需要创建一个 Assistant 对象,因为这个 API 是 beta 版本,如果是通过 curl 调用 API 的话,需要在 header 中加上OpenAI-Beta: assistants=v1
,如果是使用 OpenAI 的 Python 或 Npm 包的话则不需要,这些包已经默认帮你添加了。示例代码如下:
from openai import OpenAI
client = OpenAI()
def create_assistants(instructions, tools=[], file_ids=[]):
assistant = client.beta.assistants.create(
name="Help assistant",
instructions=instructions,
tools=tools,
model="gpt-3.5-turbo-1106",
file_ids=file_ids,
)
return assistant
上传文件的方法如下:
def create_file(file_path):
file = client.files.create(file=open(file_path, "rb"), purpose="assistants")
return file
Assistant[2] 和 File[3] 更多的 API 可以看官方的 API 文档。
接下来是创建一个 Thread,可以单独创建,也可以和 Message 一起创建,这里我们连同 Message 一起创建,示例代码如下:
def create_thread(prompt):
thread = client.beta.threads.create(
messages=[
{
"role": "user",
"content": prompt,
"file_ids": [file.id],
}
]
)
return thread
Thread 更多的 API 可以看官方的 API 文档[4]。
然后是创建 Run,创建 Run 时需要指定 Assistant 和 Thread,示例代码如下:
def run_assistant(thread, assistant):
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
)
return run
下面是 Run 的状态流转图:
当创建完了 Run 后,我们需要根据 Run 的 ID 来获取 Run,查询其状态,这是一个重复的过程,下面是查询 Run 的方法:
def retrieve_run(thread, run):
run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)
return run
Run 更多的 API 可以看官方的 API 文档[5]。
当 Run 运行完成后,我们可以通过获取 Run 的步骤来查看执行的过程,示例代码如下:
def list_run_steps(thread, run):
run_steps = client.beta.threads.runs.steps.list(
thread_id=thread.id,
run_id=run.id,
)
return run_steps
Run Step 也有对应的状态,状态流转图如下所示:
Run Step 状态比 Run 的状态要简单一些,状态的流转条件跟 Run 的一样,这里就不再赘述了。
当 Run 运行完成后,我们还需要获取 message 的结果,示例代码如下:
def list_messages(thread):
thread_messages = client.beta.threads.messages.list(thread_id=thread.id)
return thread_messages
Message 更多的 API 可以看官方的 API 文档[6]。
下面我们来通过几个示例来演示下 Assistant API 的具体功能。
我们使用代码解释器来解一道方程式,示例代码如下:
from time import sleep
def code_interpreter():
assistant = create_assistants(
instructions="你是一个数学导师。写代码来回答数学问题。",
tools=[{"type": "code_interpreter"}]
)
thread = create_thread(prompt="我需要计算这个方程式的解:`3x + 11 = 14`。你能帮我吗?")
run = run_assistant(thread, assistant)
while True:
run = retrieve_run(thread=thread, run=run)
if run.status == "completed":
break
sleep(1)
messages = list_messages(thread)
print(messages.data[0].content[0].text.value)
print(f"messages: {messages.json()}")
run_steps = list_run_steps(thread=thread, run=run)
print(f"run_steps: {run_steps.json()}")
你是一个数学导师。写代码来回答数学问题
。{"type": "code_interpreter"}
。运行结果如下:
方程式`3x + 11 = 14`的解为 x = 1。
我们再来看这个 Run 中产生的所有 Messages 信息:
{
"data": [
{
"id": "msg_7cyjjNTgjXOtWlnLOFIeMKW4",
"object": "thread.message",
"role": "assistant",
"content": [
{
"text": {
"annotations": [],
"value": "方程式`3x + 11 = 14`的解为x = 1。"
},
"type": "text"
}
]
},
{
"id": "msg_xVpdPAd4VOO6Ve5bJ5XoOoiw",
"object": "thread.message",
"role": "user",
"content": [
{
"text": {
"annotations": [],
"value": "我需要计算这个方程式的解:`3x + 11 = 14`。你能帮我吗?"
},
"type": "text"
}
]
}
],
"object": "list",
"has_more": false
}
总共只有 2 条消息,一条是 user 输入的问题,另一条是 assistant 返回的结果,中间并没有工具的消息。我们再来看这个 Run 中的所有 Run Step 信息:
{
"data": [
{
"object": "thread.run.step",
"status": "completed",
"step_details": {
"message_creation": { "message_id": "msg_7cyjjNTgjXOtWlnLOFIeMKW4" },
"type": "message_creation"
},
"type": "message_creation"
},
{
"object": "thread.run.step",
"status": "completed",
"step_details": {
"tool_calls": [
{
"id": "call_mTRfGO52jA6oPLLMACKr5HD5",
"code_interpreter": {
"input": "from sympy import symbols, Eq, solve\r\n\r\n# Define the variable\r\nx = symbols('x')\r\n\r\n# Define the equation\r\nequation = Eq(3*x + 11, 14)\r\n\r\n# Solve the equation\r\nsolution = solve(equation, x)\r\nsolution",
"outputs": [{ "logs": "[1]", "type": "logs" }]
},
"type": "code_interpreter"
}
],
"type": "tool_calls"
},
"type": "tool_calls"
}
],
"object": "list",
"has_more": false
}
这个 Run 有 2 个步骤,一个是创建消息,另外一个是代码解释器的执行,其中代码解释器中执行过程中产生的 input 信息并不会显示到最终的结果中,只是 LLM 的一个思考过程,类似 LangChain 的 Agent 里面的 debug 信息。
下面我们再使用知识检索工具来演示一下功能,知识检索工具需要上传一个文件,文件通过 API 上传后,OpenAI 后端会自动分割文档、embedding、存储向量,并提供根据用户问题检索文档相关内容的功能,这些都是自动完成的,用户只需要上传文档即可。我们就随便找一个 pdf 文件来做演示,下面是腾讯云搜的产品文档:
下面是知识检索的示例代码:
def knownledge_retrieve():
file = create_file("tengxunyun.pdf")
assistant = create_assistants(
instructions="你是一个客户支持机器人,请用你的专业知识回答客户的问题。",
tools=[{"type": "retrieval"}],
file_ids=[file.id],
)
thread = create_thread(prompt="腾讯云云搜是什么")
run = run_assistant(thread, assistant)
while True:
run = retrieve_run(thread=thread, run=run)
if run.status == "completed":
break
sleep(1)
messages = list_messages(thread)
print(messages.data[0].content[0].text.value)
print(f"messages: {messages.json()}")
run_steps = list_run_steps(thread=thread, run=run)
print(f"run_steps: {run_steps.json()}")
{"type": "retrieval"}
。运行结果如下:
腾讯云云搜是腾讯云的一站式搜索托管服务平台,提供数据处理、检索串识别、搜索结果获取与排序,搜索数据运营等一整套搜索相关服务。
该平台继承了腾讯 SOSO 在搜索引擎领域多年的技术财富,在搜索架构、海量数据存储和计算、智能排序、用户意图识别、搜索质量运营等方面有很深的技术沉淀。
腾讯云云搜负责了腾讯主要产品的搜索业务,包括微信朋友圈、手机 QQ、腾讯视频、QQ 音乐、应用宝、腾讯地图、QQ 空间等。
它提供数据处理、用户检索串智能识别、排序可定制、高级功能、运营支持等功能,以帮助开发者优化搜索服务,并提供丰富的运营数据查询功能,
包括检索量、文档新增量、检索耗时、检索失败率、热榜等。开发者可以通过腾讯云云搜让自己的搜索更具个性化,更匹配应用的需求。【1†source】
可以看到答案确实是从文档中查找而来,内容基本一致,在结尾处还有一个引用【1†source】
,这个是 Message 的注释内容,关于 Message 的注释功能这里不过多介绍,后面有机会再写文章说明,关于 Message 注释功能可以看这里[7]。
我们再来看下知识检索的 Run Step 信息:
{
"data": [
{
"type": "message_creation"
// ......
},
{
"object": "thread.run.step",
"status": "completed",
"step_details": {
"tool_calls": [
{
"id": "call_19YklOZJq1HDP8WfmMydcVeq",
"retrieval": {},
"type": "retrieval"
}
],
"type": "tool_calls"
},
"thread_id": "thread_ejr0YGodJ2NmG7dFf1hZMPUL",
"type": "tool_calls"
}
]
}
在检索工具的步骤中,只是返回了工具的类型,但检索的内容并没有放在步骤中,也就是说检索工具并没有产生内部推理过程的信息。
最后我们再来看下自定义工具的示例,我们沿用上一篇文章用到的查询天气工具get_current_weather
,我们先定义工具集 tools:
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取某个地方当前的天气情况",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名,比如:北京, 上海",
},
},
"required": ["location"],
},
},
}
]
接着我们使用 Assistant API 来调用自定义工具,示例代码如下:
def get_current_weather(location):
"""Get the current weather in a given location"""
if "北京" in location.lower():
return json.dumps({"location": location, "temperature": "10°"})
elif "上海" in location.lower():
return json.dumps({"location": location, "temperature": "15°"})
else:
return json.dumps({"location": location, "temperature": "20°"})
def function_calling():
assistant = create_assistants(
instructions="你是一个天气机器人,使用提供的工具来回答问题。",
tools=tools,
)
thread = create_thread(prompt="今天北京、上海和成都的天气怎么样?")
available_functions = {
"get_current_weather": get_current_weather,
}
run = run_assistant(thread, assistant)
# 下面是处理 Run 并提交工具的返回结果
# 中间这部分代码在下面演示
messages = list_messages(thread)
print(messages.data[0].content[0].text.value)
print(f"messages: {messages}")
run_steps = list_run_steps(thread=thread, run=run)
print(f"run_steps: {run_steps}")
available_functions
来做函数的动态调用get_current_weather
方法使用一些简单的逻辑来模拟天气查询的功能:下面是处理 Run 状态并提交工具返回结果的方法,代码如下:
while True:
run = retrieve_run(thread=thread, run=run)
if run.status == "completed":
break
if run.status == "requires_action":
tool_calls = run.required_action.submit_tool_outputs.tool_calls
tool_infos = []
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
location=function_args.get("location"),
)
tool_infos.append(
{"call_id": tool_call.id, "function_response": function_response}
)
submit_tool_outputs(thread=thread, run=run, tool_infos=tool_infos)
sleep(1)
get_current_weather
方法,因此我们新建了一个数组 tool_infos 来保存工具返回的结果拿到工具返回的结果后,我们通过 Run API 来提交这些结果:
def submit_tool_outputs(thread, run, tool_infos):
tool_outputs = []
for tool_info in tool_infos:
tool_outputs.append(
{
"tool_call_id": tool_info["call_id"],
"output": tool_info["function_response"],
}
)
client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs,
)
运行结果如下:
今天北京的温度是 10℃,上海的温度是 15℃,成都的温度是 20℃。
在 Assistant API 的使用过程中,我们发现现在获取 Run 的状态都是通过轮询的方式,这可能会导致更多的 token 损耗和性能问题,OpenAI 官方介绍在未来会对这一机制进行改进,将其替换成通知模式,另外还有其他改进计划如下:
以上就是 Assistant API 的基本原理和功能介绍,和 GPTs 相比两者各有优势,GPTs 更适合没有编程经验的用户使用,而 Assistant API 则适合有开发经验的开发人员或团队使用,而且可以使用自定义工具来打造更为强大的功能,但也有一些缺点,就是无法使用 DALL-E 3 画图工具和上传图片(这也意味着无法做图片识别),这些功能相信在未来会逐步支持。如果文中有错误或者不足之处,还请在评论区留言。
关注我,一起学习各种人工智能和 AIGC 新技术,欢迎交流,如果你有什么想问想说的,欢迎在评论区留言。
这里: https://platform.openai.com/docs/assistants/how-it-works/managing-threads-and-messages[1]
这里: https://platform.openai.com/docs/assistants/tools/supported-files[2]
Assistant: https://platform.openai.com/docs/api-reference/assistants[3]
File: https://platform.openai.com/docs/api-reference/files[4]
官方的 API 文档: https://platform.openai.com/docs/api-reference/threads[5]
官方的 API 文档: https://platform.openai.com/docs/api-reference/runs[6]
官方的 API 文档: https://platform.openai.com/docs/api-reference/messages[7]
文章转自微信公众号@极客与黑客之路