OpenAI Assistants API 中 client.beta.threads.messages.create方法,兼谈一星*和两星**解包

一、client.beta.threads.messages.create 方法

前文学习了"OpenAI Assistants API 架构",其中 client.beta.threads.messages.create向指定对话线程(Thread)添加消息的唯一方式,也是构建对话上下文、实现用户与助手交互的基础。

1、方法核心作用

client.beta.threads.messages.create 的核心功能是:

  • 向一个已存在的 Thread(对话线程)中新增一条消息
  • 消息可以是「用户发送的内容」(role="user"),也可以是「手动补充的助手回复」(role="assistant",通常由 Run 自动生成);
  • 新增的消息会作为线程的上下文,后续调用 threads.runs.create 时,助手会基于这些消息生成回复;
  • 支持附带文件、自定义元数据等扩展能力。

2、方法基本语法

1. 基础调用格式
python 复制代码
# 最简调用(纯文本用户消息)
message = client.beta.threads.messages.create(
    thread_id="线程ID",  # 必选
    role="user",        # 必选
    content="用户消息内容"  # 必选
)

# 带可选参数的调用(如附件、元数据)
message = client.beta.threads.messages.create(
    thread_id="线程ID",
    role="user",
    content="用户消息内容",
    attachments=[{"file_id": "文件ID", "tools": [{"type": "code_interpreter"}]}],  # 可选
    metadata={"user_id": "12345"}  # 可选
)
2. 关键说明
  • 该方法是同步调用,调用成功后立即返回新增的消息对象;
  • 消息一旦创建,会永久关联到该线程(除非手动调用 threads.messages.delete 删除);
  • 支持的 Python SDK 版本:openai>=1.0.0(旧版 openai<1.0.0 语法不同,已淘汰)。

3、完整参数详解

按「必选/可选」分类,结合实际使用场景说明,所有参数的 key 需和方法形参完全一致(也是之前 params 字典的核心依据):

参数名 类型 是否必选 取值范围/说明 示例
thread_id 字符串 ✅ 必选 目标线程的唯一 ID(创建线程时返回的 id "thread_abc123xyz789"
role 字符串 ✅ 必选 消息发送者角色: ✅ user:用户发送的消息(最常用) ✅ assistant:助手回复(通常由 Run 自动生成,手动创建需谨慎) "user"
content 字符串 / 列表 ✅ 必选 消息内容,分两种格式: 1. 纯文本(推荐):直接传字符串 2. 多类型内容(文本+图片/文件):传列表,每个元素是「内容块」 纯文本:"帮我分析这个CSV文件" 多类型:[{"type":"text","text":{"value":"这张图里有什么?"}}, {"type":"image_file","image_file":{"file_id":"file_123"}}]
attachments 列表 ❌ 可选 消息附带的文件及绑定的工具,每个元素是字典: - file_id:上传文件的 ID(需先调用 files.create 上传文件) - tools:该文件要使用的工具列表(如 code_interpreter 解析文件、retrieval 检索文件内容) [{"file_id":"file_123","tools":[{"type":"code_interpreter"}]}]
metadata 字典 ❌ 可选 自定义元数据(键值对),OpenAI 不解析该字段,仅原样存储/返回,可用于关联业务数据(如用户ID、会话ID) {"user_id": "u_9876", "biz_id": "order_123"}
file_ids 列表 ❌ 可选(已废弃) 旧版参数,用于关联文件 ID,OpenAI 推荐用 attachments 替代 ["file_123"]

4、典型使用示例

结合实际开发场景,给出可直接运行的示例(需替换自己的 API Key/ID):

示例1:添加纯文本用户消息(最常用)
python 复制代码
import openai

# 初始化客户端
client = openai.OpenAI(api_key="你的API Key")

# 1. 先创建一个线程
thread = client.beta.threads.create()

# 2. 向线程添加纯文本用户消息
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="请问如何用Python计算1到100的和?"
)

# 打印新增消息的核心信息
print(f"消息ID:{message.id}")
print(f"消息角色:{message.role}")
print(f"消息内容:{message.content[0].text.value}")
示例2:添加带文件附件的用户消息(解析Excel/CSV)
python 复制代码
# 前提:先上传文件(获取file_id)
file = client.files.create(
    file=open("数据.csv", "rb"),
    purpose="assistants"  # 必须指定purpose为assistants
)

# 向线程添加带附件的消息(绑定code_interpreter工具)
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="帮我分析这个CSV文件,计算平均值和中位数",
    attachments=[
        {"file_id": file.id, "tools": [{"type": "code_interpreter"}]}
    ]
)

print(f"带附件的消息ID:{message.id}")
print(f"关联的文件ID:{message.attachments[0].file_id}")
示例3:添加多类型内容(文本+图片)
python 复制代码
# 前提:先上传图片文件
image_file = client.files.create(
    file=open("截图.png", "rb"),
    purpose="assistants"
)

# 添加文本+图片的消息
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content=[
        {"type": "text", "text": {"value": "请描述这张截图里的内容"}},
        {"type": "image_file", "image_file": {"file_id": image_file.id}}
    ]
)

# 提取图片内容块
image_block = [c for c in message.content if c.type == "image_file"][0]
print(f"图片文件ID:{image_block.image_file.file_id}")

5、返回值结构

调用该方法后,会返回一个「Thread Message 对象」(OpenAI 封装的类字典对象),核心字段和 messages.data 元素一致:

python 复制代码
# 返回值示例(简化版)
{
    "id": "msg_789abc",          # 消息唯一ID
    "object": "thread.message",  # 对象类型标记
    "created_at": 1735689600,    # 创建时间戳(秒)
    "thread_id": "thread_123xyz",# 所属线程ID
    "role": "user",              # 消息角色
    "content": [                 # 消息内容(列表)
        {"type": "text", "text": {"value": "用户消息内容", "annotations": []}}
    ],
    "attachments": [],           # 附件列表(无则空)
    "metadata": {},              # 元数据(无则空)
    "status": "completed",       # 消息状态(通常为completed)
    "assistant_id": None,        # 助手消息才会有值,用户消息为None
    "run_id": None               # 关联的Run ID,用户消息为None
}
注意事项
  1. 角色限制
    • 手动创建消息时,role 建议只使用 userassistant 角色的消息通常由 threads.runs.create 自动生成,手动创建可能导致上下文混乱。
  2. content 格式
    • 纯文本优先用字符串格式(简洁),多类型内容才用列表格式;
    • 列表格式中,type 支持 text/image_file,其他类型(如 file_citation)由 API 自动生成。
  3. 文件相关
    • 上传文件时 purpose 必须为 assistants,否则无法关联到消息;
    • 绑定工具时,code_interpreter 用于解析文件(Excel/CSV/代码),retrieval 用于检索文本文件内容。
  4. 速率限制
    • 避免短时间内向同一线程创建大量消息,需遵守 OpenAI 的 API 速率限制(可参考官方文档)。
  5. 异常处理
    • 调用时需捕获 openai.APIError(如无效 thread_id、文件不存在),示例:

      python 复制代码
      try:
          message = client.beta.threads.messages.create(...)
      except openai.APIError as e:
          print(f"创建消息失败:{e}")

二、参数的两星解包

上述程序中client.beta.threads.messages.create(参数),参数是一个复杂的数据结构(是一个字典,字典的键值对需要传递给函数)。通常我们会简化成: client.beta.threads.messages.create(**params)

1、构建它的params 字典

params 是一个Python 字典 ,其核心要求是:字典的 key 必须完全匹配 client.beta.threads.messages.create 方法的参数名value 必须符合该参数的类型和 OpenAI API 规范。

该函数场景,params 的结构分为「必选参数」和「可选参数」两部分,以下是完整说明:

1. 核心结构(示例代码)
python 复制代码
# 构建的 params 示例(含附件)
params = {
    # 必选参数(缺一不可)
    "thread_id": "thread_xxxxxxx",  # 字符串:消息所属的线程 ID
    "role": "user",                 # 字符串:消息角色,仅支持 "user" 或 "assistant"
    "content": "请问1+1等于多少?",   # 字符串/列表:消息内容(纯文本用字符串,多类型用列表)
    
    # 可选参数
    "attachments": [                # 列表:消息附带的文件/工具配置
        {
            "file_id": "file_xxxxxxx",  # 字符串:上传的文件 ID
            "tools": [{"type": "code_interpreter"}]  # 列表:绑定的工具(解析文件用)
        }
    ],
    
    # 其他可选参数(扩展用)
    "metadata": {"user_id": "123"}, # 字典:自定义元数据(如用户标识、业务ID)
    "file_ids": []                  # 字符串列表:已废弃(推荐用 attachments)
}
2. 参数详细说明(与前述的表一致)
Key 名 类型 是否必选 含义
thread_id 字符串 消息要归属的线程 ID,确保消息进入正确的对话上下文
role 字符串 消息发送者角色: - user:用户发送的消息 - assistant:助手返回的消息(通常由 API 自动生成)
content 字符串 / 列表 消息内容: - 纯文本:直接传字符串 - 多类型内容(文本+图片):传列表,例:[{"type":"text","text":{"value":"文本"}}, {"type":"image_file","image_file":{"file_id":"file_xxx"}}]
attachments 列表 消息附带的文件及工具配置,每个元素是字典,包含 file_id(文件ID)和 tools(要使用的工具,如 code_interpreter/retrieval
metadata 字典 自定义元数据,可存储业务相关信息(如用户ID、会话ID),OpenAI 不会解析该字段,仅原样返回
file_ids 列表 旧版参数(已推荐用 attachments),用于关联文件 ID

2、两颗星(**) 字典解包

** 是 Python 的「字典解包运算符」,作用是把字典的键值对拆解成「关键字参数」(key=value)的形式传递给函数

1. 核心原理(对比两种写法)
写法1:不用 **(硬编码参数)
python 复制代码
# 如果不用**,需要这样写(繁琐且不灵活)
client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content=user_message,
    # 附件需要单独判断,代码冗余
    attachments=attachments if file_ids else None
)
写法2:用 **(字典解包)
python 复制代码
# 先构建字典(动态添加参数)
params = {"thread_id": thread.id, "role": "user", "content": user_message}
if file_ids:
    params["attachments"] = [...]  # 动态添加附件参数

# 解包字典,等价于上面的硬编码写法
client.beta.threads.messages.create(**params)
2. 使用 ** 的核心优势(贴合函数场景)
  1. 动态添加参数,代码更简洁

    函数中 file_ids 是可选参数,只有传入时才需要加 attachments。用字典解包可以先构建基础参数,再根据条件动态添加,避免写冗长的 if-else 来拼接函数参数。

  2. 避免参数冗余,可维护性更高

    如果后续需要添加新参数(比如 metadata),只需在 params 字典中加一个 key-value,无需修改函数调用行,代码扩展性更好。

  3. 符合 Python 最佳实践

    当函数参数较多或参数需要动态生成时,字典解包是标准写法,比硬编码参数更易读、易调试(比如可以先打印 params 确认参数是否正确)。

三、既有一星角包又有两星解包

1. *** 的区别(避免混淆)

运算符 作用 适用对象 示例
* 列表/元组解包,拆成「位置参数」 列表、元组 func(*[1,2,3])func(1,2,3)
** 字典解包,拆成「关键字参数」 字典 func(**{"a":1,"b":2})func(a=1,b=2)

2、 示例程序

python 复制代码
# ===================== 第一步:定义一个需要多参数的函数 =====================
# 函数功能:计算商品订单的最终金额
# 位置参数(按顺序传值):product_name(商品名)、quantity(数量)
# 关键字参数(按key传值):unit_price(单价)、discount(折扣,默认1.0即无折扣)
def calculate_order_amount(product_name, quantity, unit_price, discount=1.0):
    subtotal = quantity * unit_price  # 小计 = 数量 × 单价
    final_amount = subtotal * discount  # 最终金额 = 小计 × 折扣
    print(f"【{product_name}】订单详情:")
    print(f"- 数量:{quantity}件 | 单价:{unit_price}元/件")
    print(f"- 折扣:{discount}折 | 最终金额:{final_amount}元")
    return final_amount

# ===================== 第二步:准备解包用的数据源 =====================
# 1. 列表(用于*解包成位置参数:对应 product_name、quantity)
position_args_list = ["Python编程手册", 5]  # 商品名=Python编程手册,数量=5

# 2. 元组(和列表一样,也能被*解包,效果相同)
# position_args_tuple = ("Python编程手册", 5)

# 3. 字典(用于**解包成关键字参数:对应 unit_price、discount)
keyword_args_dict = {
    "unit_price": 89.9,  # 单价89.9元
    "discount": 0.8      # 折扣8折
}

# ===================== 第三步:用解包方式调用函数 =====================
print("=== 方式1:解包调用(* + ** 结合)===")
# *position_args_list:把列表拆成位置参数 → 等价于 "Python编程手册", 5
# **keyword_args_dict:把字典拆成关键字参数 → 等价于 unit_price=89.9, discount=0.8
calculate_order_amount(*position_args_list, **keyword_args_dict)

# ===================== 对比:直接传参(和解包效果完全一致)=====================
print("\n=== 方式2:直接传参(等价于解包)===")
calculate_order_amount("Python编程手册", 5, unit_price=89.9, discount=0.8)

# ===================== 拓展:OpenAI API 场景的解包示例 =====================
print("\n=== 拓展:OpenAI API 中的解包===")
import openai

# 初始化客户端(替换为你的API Key)
client = openai.OpenAI(api_key="你的API Key")

# 1. 准备位置参数(线程ID、角色)→ 用元组存储(*解包)
thread_id = client.beta.threads.create().id  # 创建临时线程
position_args = (thread_id, "user")  # 对应 thread_id、role 位置参数

# 2. 准备关键字参数(内容、元数据)→ 用字典存储(**解包)
keyword_args = {
    "content": "帮我计算5本Python手册的总价",
    "metadata": {"order_id": "ORD12345"}  # 自定义订单ID
}

# 3. 调用OpenAI API时结合*和**解包
message = client.beta.threads.messages.create(*position_args, **keyword_args)
print(f"创建的消息ID:{message.id}")
print(f"消息内容:{message.content[0].text.value}")

运行结果(示例)

复制代码
=== 方式1:解包调用(* + ** 结合)===
【Python编程手册】订单详情:
- 数量:5件 | 单价:89.9元/件
- 折扣:0.8折 | 最终金额:359.6元

=== 方式2:直接传参(等价于解包)===
【Python编程手册】订单详情:
- 数量:5件 | 单价:89.9元/件
- 折扣:0.8折 | 最终金额:359.6元

=== 拓展:OpenAI API 中的解包 ===
创建的消息ID:msg_abc123xyz
消息内容:帮我计算5本Python手册的总价

说明

1. * 列表/元组解包(位置参数)
  • position_args_list = ["Python编程手册", 5] 是一个列表,*position_args_list 会把它拆成按顺序排列的位置参数 ,等价于直接写 "Python编程手册", 5
  • 位置参数的核心是「顺序」:列表/元组的第一个元素对应函数的第一个位置参数,第二个对应第二个,不能乱序;
  • 元组和列表的解包效果完全一致(*position_args_tuple*position_args_list 没区别)。
2. ** 字典解包(关键字参数)
  • keyword_args_dict = {"unit_price": 89.9, "discount": 0.8} 是字典,**keyword_args_dict 会把它拆成key=value形式的关键字参数 ,等价于直接写 unit_price=89.9, discount=0.8
  • 关键字参数的核心是「key名匹配」:字典的key必须和函数的参数名完全一致(比如不能把 unit_price 写成 price),顺序无关。
3. 两种解包结合使用

在函数调用中,* 解包的位置参数必须放在 ** 解包的关键字参数前面(符合 Python 的参数传递规则),比如:

python 复制代码
# ✅ 正确:位置参数在前,关键字参数在后
calculate_order_amount(*position_args_list, **keyword_args_dict)

# ❌ 错误:关键字参数不能在位置参数前面
# calculate_order_amount(**keyword_args_dict, *position_args_list)

记住要点

  1. * 解包:把列表/元组拆成位置参数(按顺序传值,核心是"顺序匹配");
  2. ** 解包:把字典拆成关键字参数(按key传值,核心是"名称匹配");
  3. 实战价值:client.beta.threads.messages.create(**params),当参数需要动态生成(比如根据用户输入拼接参数列表/字典)时,解包能让代码更简洁、灵活,这也是在 OpenAI API 中用 **params 的核心原因。
相关推荐
databook2 小时前
当条形图遇上极坐标:径向与圆形条形图的视觉革命
python·数据分析·数据可视化
阿部多瑞 ABU2 小时前
`chenmo` —— 可编程元叙事引擎 V2.3+
linux·人工智能·python·ai写作
acanab2 小时前
VScode python插件
ide·vscode·python
知乎的哥廷根数学学派3 小时前
基于生成对抗U-Net混合架构的隧道衬砌缺陷地质雷达数据智能反演与成像方法(以模拟信号为例,Pytorch)
开发语言·人工智能·pytorch·python·深度学习·机器学习
WangYaolove13143 小时前
Python基于大数据的电影市场预测分析(源码+文档)
python·django·毕业设计·源码
知乎的哥廷根数学学派4 小时前
基于自适应多尺度小波核编码与注意力增强的脉冲神经网络机械故障诊断(Pytorch)
人工智能·pytorch·python·深度学习·神经网络·机器学习
cnxy1885 小时前
Python爬虫进阶:反爬虫策略与Selenium自动化完整指南
爬虫·python·selenium
用户8356290780516 小时前
Python 实现 Excel 条件格式自动化
后端·python
深蓝电商API6 小时前
Scrapy管道Pipeline深度解析:多方式数据持久化
爬虫·python·scrapy