在构建基于OpenAI的AI Agent时,多轮对话管理往往是容易出错的一环。传统Chat Completions接口要求客户端每次都把完整历史消息重新发送给模型,不仅增加Token消耗,还容易在并发场景下出现状态错乱。OpenAI Assistants API引入Threads与Messages后,服务端可以替我们保存会话状态,客户端只需维护Thread ID即可继续对话。本文以Python SDK为例,逐步拆解如何用这两个资源管理Agent对话线程。

理解Threads与Messages的职责边界
在Assistants API中,Thread代表一个独立的对话线程,本质上是一个消息容器。它不包含任何模型参数,也不直接执行推理,只负责把属于同一会话的消息按时间顺序组织起来。每个Thread有一个唯一ID,客户端可以在任意时间点向该Thread追加消息,无需反复提交历史上下文。这种设计把会话持久化从应用层下沉到平台层,大大简化了Agent的无状态化部署。
Message则是Thread内部的具体内容单元。每条Message都有角色字段,可以是user或assistant,还可以携带文本、图片文件等附件。当用户发送一句话,实际上就是在指定Thread中创建一条role为user的Message。随后调用Run让Assistant读取Thread中的所有消息并生成回复,回复会以assistant角色的Message自动写回同一个Thread。理解这个读写循环是掌握整个API的关键。
一个常见的误区是把Thread当成Assistant的配置项。实际上Thread与Assistant是解耦的:同一个Assistant可以服务多个Thread,同一个Thread也可以切换不同的Assistant来处理后续消息。这种灵活性允许我们为每个用户或每个工单创建独立Thread,同时复用同一个Agent逻辑。
创建线程并添加首条消息
使用Python SDK时,首先需要初始化OpenAI客户端并创建一个Thread。创建Thread的动作非常轻量,默认不需要任何参数。下面的代码展示了从环境变量读取API Key、创建Thread并打印ID的过程。
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 创建一个空线程
thread = client.beta.threads.create()
print("Thread ID:", thread.id)
接下来向该Thread添加第一条用户消息。Messages API的create方法需要指定thread_id、角色和内容。内容可以是纯文本,也可以是包含文本和图片的复杂结构。这里先发送一个简单问题,让Agent开始工作。
message = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="请帮我分析最近一周服务器错误日志的异常模式"
)
print("Message ID:", message.id)
上述两步完成后,Thread中已经有了一条用户消息,但Assistant还没有产生任何回复。此时如果直接查询消息列表,只会看到这条用户消息。要让模型开始处理,必须创建并等待一个Run。Run是连接Assistant与Thread的执行单元,它负责读取Thread上下文、调用模型、执行工具并最终写入回复。下文将详细说明Run的管理方式。
运行Assistant并获取回复
创建Run时需要指定两个关键参数:assistant_id和thread_id。Assistant是提前配置好的智能体,包含模型选择、指令、工具等。下面的代码创建一个Run并立即进入轮询等待,直到状态变为completed。
import time
assistant_id = "asst_你的助手ID"
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id
)
# 轮询Run状态
while run.status not in ["completed", "failed", "cancelled", "expired"]:
time.sleep(1)
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
print("Run status:", run.status)
if run.status == "completed":
messages = client.beta.threads.messages.list(
thread_id=thread.id,
order="asc"
)
for msg in messages.data:
if msg.role == "assistant":
print("Assistant:", msg.content[0].text.value)
Run的状态流转包括queued、in_progress、requires_action、completed等。对于简单对话,轮询即可;对于生产环境,建议使用事件订阅或设置较长的超时时间,避免频繁请求造成限流。requires_action状态通常出现在Assistant配置了函数工具时,需要客户端提交工具执行结果后才能继续,这部分会在后续扩展中详细讨论。
获取回复时需要注意,消息列表默认返回最新消息在前。使用order="asc"可以让对话按时间顺序排列,更符合阅读习惯。每条Message的content是一个列表,文本内容位于msg.content[0].text.value。如果消息包含图片或文件引用,content列表中会出现对应类型的对象,需要根据类型分支处理。
多轮对话与上下文管理策略
有了Thread和Run的基础流程,实现多轮对话只需要做两件事:继续向同一个Thread添加用户消息,然后再次创建Run。由于整个历史都保存在Thread中,Assistant会自动看到前几轮的内容,无需客户端手动拼接历史。下面的代码演示了如何追加消息并触发第二轮处理。
follow_up = client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="进一步聚焦到503错误,按时间分布汇总"
)
run2 = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id
)
while run2.status not in ["completed", "failed", "cancelled", "expired"]:
time.sleep(1)
run2 = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run2.id
)
虽然Thread会自动保存完整历史,但上下文长度并非无限。每个模型的上下文窗口有限,当Thread中的消息Token总量接近窗口上限时,旧消息可能被截断。Assistants API提供了truncation_strategy参数,可以在创建Run时指定截断策略。例如设置truncation_strategy={"type": "last_messages", "last_messages": 20}可以只保留最近20条消息,避免旧内容挤占新输入。对于需要长期运行的生产级Agent,建议定期评估Thread长度,或在业务层做摘要压缩。
另一种常见策略是为每个任务创建独立Thread,任务完成后及时删除。Thread本身不收费,但存储大量过期会话会增加管理复杂度。你可以通过client.beta.threads.delete(thread_id)清理不再使用的线程,或使用元数据字段记录用户、会话状态等业务信息,方便检索和批量维护。
常见问题与优化建议
在实际项目中使用Threads与Messages时,开发者经常遇到回复获取延迟、Token消耗过高以及并发冲突等问题。针对回复延迟,建议不要在Web请求主线程中同步轮询Run,而是把Run ID写入任务队列,由后台Worker处理状态更新。这样即使模型推理耗时数秒到数十秒,也不会阻塞用户界面。
Token消耗方面,由于Thread会累积全部历史,每次Run都会重新处理所有可见消息。如果对话轮次很多,成本会线性增长。除了使用截断策略,还可以在应用层实现对话摘要:当消息数超过阈值时,调用模型生成历史摘要,将摘要作为system级别的消息写入新的Thread,再继续后续交互。这种做法可以显著降低长期会话的推理成本。
并发冲突主要出现在多个用户同时操作同一个Thread的情况。Thread本身不做乐观锁,两个并发的Run可能会生成重复回复。建议在业务层为每个Thread增加状态字段,通过数据库或Redis锁保证同一时间只有一个Run处于活动状态。如果需要完全隔离,可以为每个用户会话分配独立Thread,这是最简单可靠的方案。
最后提醒一点:虽然Messages列表支持分页,但默认每页返回20条。在长对话中读取完整历史时,要通过limit和after参数逐页拉取,避免遗漏关键上下文。掌握这些细节后,你就能稳定地用Threads与Messages API为Agent构建可靠的对话管理模块。
OpenAI ThreadsMessages APIAgent对话线程修改时间:2026-08-27 02:09:25