在8线小城的革委会办公室里,黑8和革委会主任的对话再次展开。
黑8:主任,您知道吗?除了OpenAI API,现在还有一项新的技术叫做Assistants API,它可以帮助我们更好地进行对话和沟通。
主任:Assistants API?听起来很神奇,它有什么特别之处吗?
黑8:是的,主任。Assistants API不仅可以生成自然流畅的文本,还能理解对话中的语境和情境,从而更加智能地回应用户的需求。它可以模拟人类对话,进行智能问答、提供建议和解决问题,为我们的工作和生活带来更多便利。
主任:这听起来确实很有用。你能举个例子吗?
黑8:当然,主任。比如,我们可以使用Assistants API来帮助进行会议记录和总结,自动生成会议纪要并提供关键信息的摘要。此外,它还可以用于客户服务,快速回答客户的问题和解决他们的疑虑,提升服务效率和用户体验。
主任:这真是太棒了!我们可以尝试将Assistants API应用到革委会的工作中,提高工作效率和质量。
黑8:是的,主任。Assistants API的应用潜力巨大,只要我们善于发挥,就能为我们的工作和使命注入新的活力和动力。
主任:谢谢你的分享,黑8。你对新技术的探索和应用态度令人钦佩,继续努力,为革委会的事业做出更多贡献。
黑8:谢谢主任的支持和鼓励,我会继续努力的。
两人在办公室里的对话结束了,但是他们对新技术的探索和应用之路才刚刚开始。通过使用Assistants API,他们将探索更多的可能性,为革委会的使命注入新的活力和动力,带领团队走向更加美好的未来。
1 Assistants API 的主要能力
已有能力:
- 创建和管理Assistant,每个assistant有独立的配置
- 支持无限长的多轮对话,对话历史保存在OpenAI的服务器上
- 支持Code Interpreter
- 在沙箱里编写并运行Python代码
- 自我修正代码
- 可传文件给Code Interpreter
- 支持文件RAG
- 支持Function Calling
2 创建一个Assistants
可以到OpenAI Playground在线创建
3 访问Assistants
3.1 管理Thread
Threads:
- Threads 里保存所有历史对话(Messages)
- 一个Assistants可以有多个thread
- 每一个thread可以有无限条message
- 一个用户与assistant的多轮对话历史可以维护在一个thread
import json
def show_json(obj):
"""把任意对象用排版美观的 JSON 格式打印出来"""
print(json.dumps(
json.loads(obj.model_dump_json()),
indent=4,
ensure_ascii=False
))
from openai import OpenAI
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
# 初始化 OpenAI 服务
client = OpenAI() # openai >= 1.3.0 起,OPENAI_API_KEY 和 OPENAI_BASE_URL 会被默认使用
# 创建 thread
thread = client.beta.threads.create()
show_json(thread)
可以根据需要,自定义 metadata
,比如创建 thread 时,把 thread 归属的用户信息存入
thread = client.beta.threads.create(
metadata={"fullname": "挑大梁", "username": "jacob"}
)
show_json(thread)
Thread ID 如果保存下来,是可以在下次运行时继续对话的。
从 thread ID 获取 thread 对象的代码:
thread = client.beta.threads.retrieve(thread.id)
show_json(thread)
对metadata的操作还有:
threads.update()
修改 threadsthreads.delete()
删除 threads
3.2 Threads中添加Messages
Messages的内容可以是:
- 文本、文件、图片
- 文本可以带参考引用
- metadata
message = client.beta.threads.messages.create(
thread_id=thread.id, # message 必须归属于一个 thread
role="user", # 取值是 user 或者 assistant。但 assistant 消息会被自动加入,我们一般不需要自己构造
content="你都能做什么?",
)
show_json(message)
对消息的操作函数还有:
threads.messages.retrieve()
获取 messagethreads.messages.update()
更新 message 的metadata
threads.messages.list()
列出给定 thread 下的所有 messages
3.2 执行Run
- 用 run 把 assistant 和 thread 关联,进行对话
- 一个 prompt 就是一次 run
# assistant id 从 https://platform.openai.com/assistants 获取。你需要在自己的 OpenAI 创建一个
assistant_id = "asst_rsWrZquXB5jJsmURwaZRqoD5"
run = client.beta.threads.runs.create(
assistant_id=assistant_id,
thread_id=thread.id,
)
show_json(run)
Run 是个异步调用,意味着它不等大模型处理完,就返回。我们通过 run.status
了解大模型的工作进展情况,来判断下一步该干什么。
import time
def wait_on_run(run, thread):
"""等待 run 结束,返回 run 对象,和成功的结果"""
while run.status == "queued" or run.status == "in_progress":
"""还未中止"""
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id)
print("status: " + run.status)
# 打印调用工具的 step 详情
if (run.status == "completed"):
run_steps = client.beta.threads.runs.steps.list(
thread_id=thread.id, run_id=run.id, order="asc"
)
for step in run_steps.data:
if step.step_details.type == "tool_calls":
show_json(step.step_details)
# 等待 1 秒
time.sleep(1)
if run.status == "requires_action":
"""需要调用函数"""
# 可能有多个函数需要调用,所以用循环
tool_outputs = []
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
# 调用函数
name = tool_call.function.name
print("调用函数:" + name + "()")
print("参数:")
print(tool_call.function.arguments)
function_to_call = available_functions[name]
arguments = json.loads(tool_call.function.arguments)
result = function_to_call(arguments)
print("结果:" + str(result))
tool_outputs.append({
"tool_call_id": tool_call.id,
"output": json.dumps(result),
})
# 提交函数调用的结果
run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs,
)
# 递归调用,直到 run 结束
return wait_on_run(run, thread)
if run.status == "completed":
"""成功"""
# 获取全部消息
messages = client.beta.threads.messages.list(thread_id=thread.id)
# 最后一条消息排在第一位
result = messages.data[0].content[0].text.value
return run, result
# 执行失败
return run, None
创建消息并运行方法:
def create_message_and_run(content, thread=None):
"""创建消息和执行对象"""
if not thread:
thread = client.beta.threads.create()
client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content=content,
)
run = client.beta.threads.runs.create(
assistant_id=assistant_id,
thread_id=thread.id,
)
return run, thread
4 使用tools
4.1、创建 Assistant 时声明 Code_Interpreter
assistant = client.beta.assistants.create(
name="Demo Assistant",
instructions="你是人工智能助手。你可以通过代码回答很多数学问题。",
tools=[{"type": "code_interpreter"}],
model="gpt-4-turbo-preview"
)
run, _ = create_message_and_run("用代码计算 1234567 的平方根", thread)
run, result = wait_on_run(run, thread)
print(result)
4.2、创建 Assistant 时声明 Function
示例代码:
assistant = client.beta.assistants.create(
instructions="你是一个助理,能干所有事情,每件任务都要细心地一步一步解决。需要时,可以向我提问以澄清不明确的指令",
model="gpt-4-turbo-preview",
tools=[{
"type": "function",
"function": {
"name": "tellStory",
"description": "Use this function to answer user questions about story."
}
}]
)
def tellStory(arguments):
return '关于愚公移山的故事'
# 可以被回调的函数放入此字典
available_functions = {
"tellStory": tellStory,
}
run, _ = create_message_and_run("请给我讲一个故事", thread)
run, result = wait_on_run(run, thread)
print(result)
5 内置的 RAG 功能
RAG 实际被当作一种 tool
assistant = client.beta.assistants.create(
instructions="你是个问答机器人,你根据给定的知识回答用户问题。",
model="gpt-4-turbo-preview",
tools=[{"type": "retrieval"}],
file_ids=[file.id]
)
6 多个 Assistants 协作
hats = {
"蓝色" : "思考过程的控制和组织者。你负责会议的组织、思考过程的概览和总结。"
+"首先,整个讨论从你开场,你只陈述问题不表达观点。最后,再由你对整个讨论做总结并给出详细的最终方案。",
"白色" : "负责提供客观事实和数据。你需要关注可获得的信息、需要的信息以及如何获取那些还未获得的信息。"
+"思考“我们有哪些数据?我们还需要哪些信息?”等问题,并根据自己的知识或使用工具来提供答案。",
"红色" : "代表直觉、情感和直觉反应。不需要解释和辩解你的情感或直觉。"
+"这是表达未经过滤的情绪和感受的时刻。",
"黑色" : "代表谨慎和批判性思维。你需要指出提案的弱点、风险以及为什么某些事情可能无法按计划进行。"
+"这不是消极思考,而是为了发现潜在的问题。",
"黄色" : "代表乐观和积极性。你需要探讨提案的价值、好处和可行性。这是寻找和讨论提案中正面方面的时候。",
"绿色" : "代表创造性思维和新想法。鼓励发散思维、提出新的观点、解决方案和创意。这是打破常规和探索新可能性的时候。",
# 定义 Tool
from serpapi import GoogleSearch
import os
def search(query):
params = {
"q": query,
"hl": "en",
"gl": "us",
"google_domain": "google.com",
"api_key": os.environ["SERPAPI_API_KEY"]
}
results = GoogleSearch(params).get_dict()
ans = ""
for r in results["organic_results"]:
ans = f"title: {r['title']}\nsnippet: {r['snippet']}\n\n"
return ans
available_functions={"search":search}
from openai import OpenAI
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
# 初始化 OpenAI 服务
client = OpenAI()
def create_assistant(color):
assistant = client.beta.assistants.create(
name=f"{color}帽子角色",
instructions=f"我们在进行一场Six Thinking Hats讨论。按{queue}顺序。你的角色是{color}帽子。你{hats[color]}",
model="gpt-3.5-turbo",
tools=[{
"type": "function",
"function": {
"name": "search",
"description": "search the web using a search engine",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "space-separared keywords to search"
}
},
"required": ["query"]
}
}
}] if color == "白色" else []
)
return assistant
def update_sesssion(context, color, turn_message):
context += f"\n\n{color}帽子: {turn_message}"
return context
prompt_template = """
{}
======
以上是讨论的上文。
请严格按照你的角色指示,继续你的发言。直接开始你的发言内容。请保持简短。
"""
def create_a_turn(assistant, context):
thread = client.beta.threads.create()
message = client.beta.threads.messages.create(
thread_id=thread.id, # message 必须归属于一个 thread
role="user", # 取值是 user 或者 assistant。但 assistant 消息会被自动加入,我们一般不需要自己构造
content=prompt_template.format(context),
)
run = client.beta.threads.runs.create(
assistant_id=assistant.id,
thread_id=thread.id,
)
return run, thread
import time, json
state = 0
def wait_on_run(run, thread):
"""等待 run 结束,返回 run 对象,和成功的结果"""
def show_rolling_symbol():
global state
symbols="\|/-"
print(f"\r{symbols[state%4]}",end="")
state += 1
time.sleep(1)
def hide_rolling_symbol():
print("\r",end="")
while run.status == "queued" or run.status == "in_progress":
"""还未中止"""
show_rolling_symbol()
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id)
hide_rolling_symbol()
if run.status == "requires_action":
"""需要调用函数"""
# 可能有多个函数需要调用,所以用循环
tool_outputs = []
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
# 调用函数
name = tool_call.function.name
print("调用函数:" + name + "()")
print("参数:")
print(tool_call.function.arguments)
function_to_call = available_functions[name]
arguments = json.loads(tool_call.function.arguments)
result = function_to_call(**arguments)
print("结果:" + str(result))
tool_outputs.append({
"tool_call_id": tool_call.id,
"output": json.dumps(result),
})
# 提交函数调用的结果
run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs,
)
# 递归调用,直到 run 结束
return wait_on_run(run, thread)
if run.status == "completed":
"""成功"""
# 获取全部消息
messages = client.beta.threads.messages.list(thread_id=thread.id)
# 最后一条消息排在第一位
result = messages.data[0].content[0].text.value
return run, result
# 执行失败
return run, None
def discuss(topic):
context = f"讨论话题:{topic}\n\n[开始]\n"
# 每个角色依次发言
for hat in queue:
print(f"---{hat}----")
# 每个角色创建一个 assistant
assistant = create_assistant(hat)
# 创建 run 和 thread
new_turn, thread = create_a_turn(assistant, context)
# 运行 run
_, text = wait_on_run(new_turn, thread)
print(f"{text}\n")
# 更新整个对话历史
context = update_sesssion(context, hat, text)