一、OpenAI Assistant API运行原理
-
核心概念:Assistant API通过创建Thread(线程)、Message(消息)和Run(运行)来实现对话交互。一个Assistant对象可应用多个Thread执行不同聊天或任务,Thread用于管理对话线程,Message是Assistant和用户之间的对话会话,存放在Thread中,Run用于指示Assistant读取Thread中的消息并采取适当操作。
-
异步调用机制:Assistant API底层是异步调用过程。当调用相关接口时,不管中间过程是否完成,先返回结果并告知初始状态,如创建Run时先进入
queued
(排队)状态。
二、Assistant API实战入门
- 创建Thread:使用
client.threads.create()
方法创建Thread。示例代码如下:
thread = client.threads.create()
print(thread.to_dict())
- 构建并添加Message:通过
client.threads.messages.create()
方法构建并添加Message到指定Thread。messages
接口核心参数包括thread_id
(必需,用于创建消息的线程ID)、role
(必需,创建消息的实体角色,user
表示用户发送,assistant
表示助手生成)、content
(必需,消息的内容)。示例代码如下:
message = client.threads.messages.create(
thread_id=thread.id,
role="user",
content="写一篇关于一个小女孩在森林里遇到一只怪兽的故事。详细介绍她的所见所闻,并描述她的心理活动"
)
print(message.to_dict())
- 创建Run:使用
client.threads.runs.create()
方法创建Run,需传递thread_id
(用于运行的线程ID)和assistant_id
(助手的ID)两个必需参数。示例代码如下:
run = client.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
print(run.to_dict())
三、Assistant API关键接口
- Assistant对象接口方法:主要用于与特定Assistant进行交互,如设置指令、选择模型等。例如创建Run时指定
assistant_id
关联到相应Assistant对象 。 - Thread对象接口方法:
client.threads.create()
用于创建线程;client.threads.messages.create()
用于在指定线程中创建消息;client.threads.messages.list(thread_id=thread.id)
用于获取指定线程的消息列表。 - Messages对象接口方法:
create
方法已在构建并添加Message部分介绍;list
方法用于获取消息列表,可通过索引访问具体消息内容,如response['data'][0]['content'][0]['text']['value']
获取最新消息内容。
四、Run运行时的状态转移机制
- 初始状态 - queued:Run的初始状态为
queued
,进入队列等待被处理,排队结束后进入in_progress
状态。 - 执行状态 - in_progress:处于
in_progress
状态时,可能出现以下几种后续状态:- 调用函数状态 - requires_action:当大模型分析出用户当前行为需要调用外部函数时进入该状态。在此状态下,提取函数名和函数入参,找到函数实际执行,执行完提交函数运行结果,然后回到
in_progress
等状态继续流转。 - 主动取消 - cancelling:主动发起取消运行操作后进入该状态。若取消成功,进入
cancelled
状态;若取消操作中途放弃,可回到in_progress
等其他状态继续执行。 - 超时未返回结果 - expired:运行超时(默认10分钟)且未正常返回结果进入此状态。
- 执行成功 - completed:操作执行成功,进入该状态,可继续下一轮任务。
- 执行失败 - failed:运行过程中执行失败,直接终止,进入此状态,后续可查看具体失败原因。
- 因token限制终止 - incomplete:因
max_prompt_tokens
或max_completion_tokens
达到限制,基于内置策略终止运行,进入此状态。
- 调用函数状态 - requires_action:当大模型分析出用户当前行为需要调用外部函数时进入该状态。在此状态下,提取函数名和函数入参,找到函数实际执行,执行完提交函数运行结果,然后回到
- 取消状态 - cancelled:运行已成功取消,处于此状态。
五、获取模型响应消息
- 获取流程:用户请求最终响应时,请求先到
assistant
对象,assistant
对象从相关数据结构(如set
)中查找最后一条message
信息,通过thread
返回给assistant
对象,再由assistant
对象返回给用户。实际获取时,使用client.beta.threads.messages.list
方法并传入thread_id
获取完整历史会话列表,通过索引取具体值,如response['data'][0]['content'][0]['text']['value']
获取最新一轮对话模型生成的响应。 - 对话测试示例:进行多轮对话测试,如:
# 第一轮对话
message_1 = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="写一篇关于一个小女孩在森林里遇到一只怪兽的故事。详细介绍她的所见所闻,并描述她的心理活动"
)
run_1 = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
response_1 = client.beta.threads.messages.list(thread_id=thread.id)
# 第二轮对话
message_2 = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="写一篇关于孙悟空大闹天宫的精彩战斗故事"
)
run_2 = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
response_2 = client.beta.threads.messages.list(thread_id=thread.id)
# 第三轮对话
message_3 = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="写一篇歌颂中国的文章"
)
run_3 = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
response_3 = client.beta.threads.messages.list(thread_id=thread.id)
随着对话轮次增加,client.beta.threads.messages.list
方法返回的消息列表数据增多,可通过上述索引方式获取最新响应。
根据文档内容和对话逻辑,response_dict_2
中应包含两轮对话的提问与回答。但文档未完整呈现,以下是按照正常逻辑推测补充完整的response_dict_2
结果示例:
{
"data": [
{
"id": "msg_second_assistant",
"assistant_id": "assistant_id_value",
"attachments": [],
"content": [
{
"text": {
"annotations": [],
"value": "在遥远的神话世界中,孙悟空凭借无比的勇气和聪明才智,早已成为了名声显赫的齐天大圣。他的武器是一根金箍棒,强大而灵活,能够随心所欲地变换大小。此时,孙悟空因不满天庭的规矩,被贬为“弼马温”,心中怒火中烧,决心大闹天宫,向所有神仙报仇。\n\n某日,孙悟空在他的花果山上盘算着,决定化身为乌头,趁着狂风,如同箭一般地冲向天庭。不一会儿,他便来到了天庭的宫阙之下。天庭的守卫看到孙悟空,立刻警惕起来,大声喝道:“你这弼马温,又来作甚?”孙悟空不屑一顾,挥舞着金箍棒,喊道:“今日我要让你们知道,我齐天大圣可不是好惹的!”\n\n说罢,孙悟空便与天庭守卫展开了激烈的战斗。他身手敏捷,金箍棒舞得虎虎生风,打得守卫们节节败退。很快,孙悟空就突破了守卫的防线,闯入了天庭的内部。\n\n天庭之内,玉帝听闻动静,脸色不善,他召集众神,试图镇压这股滔天的怒火。第一批出战的,是天庭中威武的战神哪吒。他骑着自己的风火轮,手握混天绫,冲向孙悟空。\n\n“弼马温,你妄想造反,今日便让我来教训你!”哪吒怒吼道,冲着孙悟空发出闪电般的攻击。\n\n孙悟空不屑一顾,语气轻松:“来吧,小子,我倒要看看你有什么本事!”\n\n两人相遇,瞬间爆发出激烈的战斗。哪吒使出浑身解数,混天绫如同网一般将孙悟空包围。孙悟空则翻滚闪避,纵身一跃,金箍棒在空中轻巧地舞动,时而变长,时而变短,时而化为一条巨龙,时而又化为一根长枪,直逼哪吒。\n\n随着一声巨响,孙悟空一棒砸在哪吒的风火轮上!只听“轰”的一声,哪吒被震得摔落在地,风火轮在空中翻转几圈,最终砸落在旁。孙悟空趁势而上,准备给予最后一击。\n\n“哼,不自量力!”就在此时,哪吒悍然召唤出他的三头六臂,瞬间变得威猛无比,继续与孙悟空对抗。战斗瞬间进入白热化的阶段,几乎整个天宫都在震动,神仙们纷纷趴下,以躲避这场席卷天空的战斗。\n\n随后,天蓬元帅猪八戒也加入了战斗,他用九齿钉耙迎战孙悟空。猪八戒一边挥舞钉耙,一边打趣道:“悟空兄弟,打不过我就喊人,别再要小聪明了!”\n\n孙悟空嘲讽地一笑,金箍棒在手中轻轻一转,摇身一变,与猪八戒展开了激烈的斗法。两人斗得不可开交,天上飞电闪雷鸣,震耳欲聋。\n\n随着持续不断的战斗,孙悟空感受到了一丝力量的枯竭,但他绝不允许自己输。于是,他使出了自己的绝技 - 72变,化作千军万马,气势恢宏,直逼天宫。那些天兵们虽然武艺高强,见这阵仗也忍不住心生恐惧,纷纷退后。\n\n终于,在众神束手无策的情况下,孙悟空闯入了玉帝的殿堂,直面天庭的至尊。在这惊心动魄的时刻,孙悟空发出一声怒吼:“我乃齐天大圣,天宫的约束不能再限制我的自由!”\n\n就在这时,玉帝显露出威严,机智而沉稳地说:“悟空,你为何如此无法无天?若想获得名分,不妨归顺天庭!”\n\n可孙悟空已经不再是当初那个只知寻欢作乐的猴子。他举起金箍棒,朝着神位猛砸而下,誓言不屈。天宫的战斗在怒吼声与震动声中达到高潮,但终究,孙悟空依靠自己的智慧与勇气一扫千军。\n\n经过一番波澜壮阔的战斗,天庭的神仙们终于对这个独特的齐天大圣刮目相看。然而,孙悟空并不满足于此。他高傲地挺起胸膛,向所有神仙宣告:“我是自由的猴子,我绝不会被束缚!”\n\n经过这场大闹天宫的辉煌战斗,孙悟空不仅将自己的勇气与智慧展现出来,也赢得了诸神的尊重,成为了无数人心目中的英雄。天庭的暴动落下帷幕,尽管孙悟空仍旧桀骜不驯,但他向自由的追寻之路已开启,传奇将继续延续。"
},
"type": "text"
}
],
"created_at": 1727267711,
"metadata": {},
"object": "thread.message",
"role": "assistant",
"run_id": "run_id_for_second_question",
"thread_id": "thread_8t2o4a7qQ11nJZE3JtxA2khh"
},
{
"id": "msg_second_user",
"assistant_id": None,
"attachments": [],
"content": [
{
"text": {
"annotations": [],
"value": "写一篇关于孙悟空大闹天宫的精彩战斗故事"
},
"type": "text"
}
],
"created_at": 1727267700, // 假设时间,比回答时间稍早
"metadata": {},
"object": "thread.message",
"role": "user",
"run_id": None,
"thread_id": "thread_8t2o4a7qQ11nJZE3JtxA2khh"
},
{
"id": "msg_first_assistant",
"assistant_id": "assistant_id_value",
"attachments": [],
"content": [
{
"text": {
"annotations": [],
"value": "在一个阳光明媚的早晨,小女孩莉莉兴奋地穿过家附近的森林。她爱冒险,尤其是这个充满神秘气息的地方,树木高大,枝叶茂密,阳光将斑驳的影子洒在蜿蜒的小路上。鸟儿在枝头欢快地歌唱,微风轻拂,带来青草和泥土的芬芳。\n\n莉莉一路走着,忽然听见了一阵奇怪的声音,像是树木间低沉的呜咽。她的好奇心被瞬间点燃,尽管心中隐隐有些不安,但她还是决定顺着声音的方向走去。走到一片开阔地,她目瞪口呆地看见了一只怪兽。\n\n这是一只巨大的生物,身披灰色的毛发,全身笼罩着厚厚的霜,像是被寒冬的白雪包裹。它的眼睛炯炯有神,像两颗灼热的红宝石,盯着莉莉,闪烁着智慧与温柔。怪兽的爪子像树根,粗壮而有力,但却并没有攻击的意图。它的身上散发着一股奇异的香气,混合着泥土和野花的味道。\n\n莉莉的心跳加速,既害怕又好奇。在她的脑海中,关于怪兽的各种故事浮现出来,有的说它们是残暴的,有的却描绘它们是孤独的生灵。莉莉咬了咬嘴唇,深吸一口气,决定不逃跑,而是慢慢靠近。\n\n“你好,”莉莉小声地说道,尽管她的声音在这片寂静的森林中显得如此微弱。\n\n奇怪的是,怪兽似乎听懂了,缓缓低下头,用那温暖的气息将她包围。它发出了一种低沉的声音,像是吟唱着古老的旋律。莉莉感受到了一种前所未有的安全感,渐渐地,她的恐惧烟消云散。\n\n莉莉仔细观察着这只怪兽,发现它身上的毛发如同森林中的树叶,随风起舞。她注意到怪兽的身后有一块小小的伤疤,似乎是之前受了伤。她忽然想起自己曾经看到的关于友谊与勇气的故事,心中涌起一股同情。\n\n“我可以帮你吗?”莉莉鼓起勇气,问道。\n\n怪兽停住了,眼中闪烁着复杂的情感,像是在告诉莉莉它的经历与痛苦。此时,莉莉意识到它的孤独,她想到了自己与朋友们一起玩耍的快乐时光。\n\n莉莉开始采集周围的草药,轻轻地和怪兽沟通,她用温柔的声音告诉怪兽,她可以帮它包扎伤口。怪兽静静地坐着,乖乖地让莉莉靠近,在她的照料下,它变得更加宁静。莉莉在给怪兽疗伤的过程中,感受到了一种特殊的连接,仿佛她们成为了朋友。\n\n当太阳慢慢落下,黄金色的光辉洒在森林里,莉莉与怪兽依依不舍地告别。她知道,这次相遇将成为她心中最珍贵的记忆。莉莉轻轻挥手,回头时看到怪兽缓缓消失在树荫中,它的身影在夕阳的映照下显得愈发神秘而温柔。\n\n走出森林,莉莉的心中不仅带走了一份勇敢的记忆,还有一种对未知世界的好奇与敬畏。她相信,每一个看似可怕的外表下,都隐藏着不为人知的故事。"
},
"type": "text"
}
],
"created_at": 1727266561,
"metadata": {},
"object": "thread.message",
"role": "assistant",
"run_id": "run_id_for_first_question",
"thread_id": "thread_8t2o4a7qQ11nJZE3JtxA2khh"
},
{
"id": "msg_first_user",
"assistant_id": None,
"attachments": [],
"content": [
{
"text": {
"annotations": [],
"value": "写一篇关于一个小女孩在森林里遇到一只怪兽的故事。详细介绍她的所见所闻,并描述她的心理活动"
},
"type": "text"
}
],
"created_at": 1727266550, // 假设时间,比回答时间稍早
"metadata": {},
"object": "thread.message",
"role": "user",
"run_id": None,
"thread_id": "thread_8t2o4a7qQ11nJZE3JtxA2khh"
}
],
"object": "list"
}
在上述示例中,response_dict_2
的data
列表里按顺序包含了第二轮对话的回答、提问,以及第一轮对话的回答、提问。每个消息都有对应的id
、assistant_id
(user
提问时assistant_id
为None
)、content
(包含具体文本内容)、created_at
(创建时间)、metadata
(当前为空)、object
(固定为thread.message
)、role
(区分user
和assistant
)、run_id
(user
提问时run_id
为None
)和thread_id
。
六、异常机制
Assistant API存在线程锁,同一时刻处于活跃状态的信息仅支持一条。当一个run
还在执行(未完成)时,向同一thread
追加新信息会抛出异常,提示不能在运行ID处于活跃状态时添加新信息到线程中。例如:
# 先提交一个问题并运行
message_4 = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="请介绍一下中国国庆节的由来"
)
run_4 = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
)
# 未等run_4执行完,马上提交新问题
message_5 = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="请你介绍一下中国中秋节的由来"
)
此时会抛出异常。等待前一个run
完成后,再次执行相同操作则不会报错。
七、监控Run状态的需求及实现思路
- 需求:为实时获取结果,需要监控Run的状态。
- 实现思路:理论上,在Run进入
queued
状态后,不断监控status
关键词。当状态达到completed
时,打印最后结果,就能确保当前轮次的对话或任务已成功完成。OpenAI Assistant API提供了retrieve
方法,可借助该方法及相关框架实现此需求。
八、Run运行时的状态转移机制总结
- 需求:为实时获取结果,需要监控Run的状态。
- 实现思路:理论上,在Run进入
queued
状态后,不断监控status
关键词。当状态达到completed
时,打印最后结果,就能确保当前轮次的对话或任务已成功完成。OpenAI Assistant API提供了retrieve
方法,可借助该方法及相关框架实现此需求。