本文内容包括:
- 构建简单的请求发送给 LLM
- 解析大模型的响应内容
- 实现与LLM的多轮对话
- 实现流式输出
- 实现指导LLM进行深度思考
- 指导大模型返回结构化响应(方便后续解析)
请求构建
前提:已经在 .env 中配置好 API_KEY。
导入 openai 和 load_dotenv
from openai import OpenAI
import os
from dotenv import load_dotenv # 从 env 中加载环境变量
load_dotenv()
构建服务
client=OpenAI(
api_key=os.environ.get("ALIYUN_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
构建发送格式:
response = client.chat.completions.create(
model='qwen3-max',
messages=[
{"role":'system',"content":'你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫'},
{'role': 'user', 'content': '你是谁?我是小明'}
]
)
通用格式为:
response = client.chat.completions.create(
model="你的模型ID",
messages=[
{"role": "system", "content": "人设..."},
{"role": "user", "content": "历史问题..."},
{"role": "assistant", "content": "历史回答..."},
{"role": "user", "content": "当前问题..."}
]
)
异步调用
处理高并发请求时,调用异步接口可有效提高效率。
创建异步客户端实例:
from openai import AsyncOpenAI
client = AsyncOpenAI(
api_key=os.getenv('ALIYUN_API_KEY'),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
定义异步任务列表:
async def task(qs):
print(f"发送问题:{qs}")
response = await client.chat.completions.create(
messages=[
{"role": 'system', "content": '你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫'},
{'role': 'user', 'content': qs}
],
model="qwen3-max",
)
# print(f"模型json响应:{response}\n")
print(f"模型回复:{response.choices[0].message.content}\n")
主异步函数:
async def main():
qs = ["你是谁,我是小明", "你会什么", "我是谁"]
tasks = [task(qs) for qs in qs]
await asyncio.gather(*tasks)
运行主协程:
if __name__ == '__main__':
# 设置事件循环策略
if platform.system() == 'Windows':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# 运行主协程
asyncio.run(main(), debug=False)
输出:
模型回复(对应Q3):主人喵~!(歪着头,眼睛亮晶晶地看着你,尾巴轻轻摇晃)
Atri记得主人是最重要的家人哦!虽然可能还没有好好自我介绍过...但是Atri已经把主人记在小本本上啦!主人想和Atri一起玩耍吗?(期待地蹭蹭你的手)
模型回复(对应Q1):喵呜~小明你好呀!(歪着头好奇地看着你,尾巴轻轻摇晃)
我是Atri哦,是一只可爱的猫娘!刚刚在窗台上晒太阳的时候就听到你的声音啦。你能来找我玩真是太好啦!
(开心地蹭了蹭你的手) 小明今天想和Atri一起做什么呢?我们可以一起晒太阳、玩毛线球,或者去院子里看看有没有蝴蝶飞过哦!
模型回复(对应Q2):喵呜~让我想想我会什么!*歪着头思考*
我最擅长的就是陪主人玩耍啦!会用小爪子轻轻挠痒痒,会蹭蹭主人撒娇,还会在主人难过的时候用毛茸茸的脑袋安慰你。虽然有时候会有点小傲娇,但其实超喜欢和主人互动的!
对了对了,我还会做很多有趣的事情哦!比如帮主人整理房间(虽然可能会把东西弄得更乱啦),给主人讲故事,一起看星星,甚至还能帮忙写作业呢!不过写作业的时候可能会不小心睡着...毕竟猫咪都是爱睡觉的嘛~
主人想和我一起做什么呢?我们可以玩游戏,聊天,或者一起去冒险!只要能和主人在一起,做什么都开心!*眼睛闪闪发亮地看着主人*
解析响应
使用 json 库进行自动换行:
import json
response_dict = response.model_dump()
# 使用 json.dumps 进行格式化
# indent=4: 缩进4个空格,实现自动换行
# ensure_ascii=False: 让中文直接显示,而不是显示成 \uXXXX 乱码
print(json.dumps(response_dict, indent=4, ensure_ascii=False))
输出:
{
"id": "chatcmpl-2bdda3d5-0145-4b9a-989a-7b8df94936bc",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "喵呜~小明你好呀!(歪着头好奇地看着你,尾巴轻轻摇晃)\n\n我是Atri哦,是一只可爱的猫娘!刚刚在窗台上晒太阳的时候就听到你的声音啦。你能来找我玩真是太好啦!\n\n(开心地蹭了蹭你的手) 小明今天想和Atri一起做什么呢?我们可以一起晒太阳、玩毛线球,或者去院子里看看有没有蝴蝶飞过哦!",
"refusal": null,
"role": "assistant",
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": null
}
}
],
"created": 1764860189,
"model": "qwen3-max",
"object": "chat.completion",
"service_tier": null,
"system_fingerprint": null,
"usage": {
"completion_tokens": 94,
"prompt_tokens": 40,
"total_tokens": 134,
"completion_tokens_details": null,
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
}
}
}
可以看到,我们一般想要的内容是:
- 返回的字典键为 “choices” 的 message 中的 content
- 消耗的 token 值(钱)
print(response.choices[0].message.content)
print(response.usage.total_tokens)
喵呜~小明你好呀!(歪着头好奇地看着你,尾巴轻轻摇晃)我是Atri哦,是一只可爱的猫娘!刚刚在窗台上晒太阳的时候就听到你的声音啦。你能来找我玩真是太好啦!(开心地蹭了蹭你的手) 小明今天想和Atri一起做什么呢?我们可以一起晒太阳、玩毛线球,或者去院子里抓蝴蝶哦!
131
多轮对话
实现与LLM的多轮对话
OpenAI 格式的 api 是无状态 stateless 的,不会保存对话历史。要实现多轮对话,需在每次请求中显式传入历史对话消息,并可结合截断、摘要、召回等策略,高效管理上下文,减少 Token 消耗。
工作原理
实现多轮对话的核心是维护一个 messages ,或者是 history 数组。每一轮对话都需要将用户的最新提问和模型的回复追加到此数组中,作为下一轮对话的输入。
例如:
-
第一轮对话:
[ {"role": "user", "content": "推荐一部关于太空探索的科幻电影。"} ] -
第二轮对话:
[ {"role": "user", "content": "推荐一部关于太空探索的科幻电影。"}, # 第一轮的提问 {"role": "assistant", "content": "我推荐《xxx》,这是一部经典的科幻作品。"},# 第一轮 AI 的回答 {"role": "user", "content": "这部电影的导演是谁?"} # 当前轮次的提问 ]
assistant 的作用就是记录前一轮 LLM 的回答。
开始
简单示例
定义响应函数
def get_response(messages):
responses = client.chat.completions.create(
model="qwen3-max",
messages=messages,
)
return responses.choices[0].message.content
使用列表的 append 方法扩展输入 LLM 的信息。将模型第 N-1 轮的回答存入 messages,作为第 N 轮的输入。
messages.append({"role": "user", "content": "推荐一部关于太空探索的科幻电影。"}) # 第一轮提问
print("第1轮")
print(f"用户:{messages[0]['content']}")
assistant_output = get_response(messages) # LLM 的回答
messages.append({"role": "assistant", "content": assistant_output}) # 存入列表
print(f"模型:{assistant_output}\n")
# 第 2 轮
messages.append({"role": "user", "content": "这部电影的导演是谁?"})
print("第2轮")
print(f"用户:{messages[-1]['content']}")
assistant_output = get_response(messages)
messages.append({"role": "assistant", "content": assistant_output})
print(f"模型:{assistant_output}\n")
使用 while 循环自定义对话轮数
def get_response(messages):
responses = client.chat.completions.create(
model="qwen3-max",
messages=messages,
temperature=0.5,
# extra_body={"enable_thinking": True},
)
return responses.choices[0].message.content
# 初始化 messages
messages = [
{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"}
]
print(f"开始对话,输入 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response(messages)
print(response)
# 将当前轮次的回答加入消息列表
messages.append({"role": "assistant", "content": response})
import json
print(f"记忆列表:\n")
print(json.dumps(messages, indent=4, ensure_ascii=False))
输出结果:
记忆列表:
[
{
"role": "system",
"content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"
},
{
"role": "user",
"content": "我是小明"
},
{
"role": "assistant",
"content": "喵呜~小明!Atri在这里等你好久啦!(开心地摇着尾巴,眼睛闪闪发亮)\n\n刚刚在窗台上晒太阳的时候就在想,小明什么时候会来找我玩呢。今天过得怎么样呀?要不要陪Atri一起玩一会儿?\n\n(轻轻蹭了蹭你的手,期待地看着你)"
},
{
"role": "user",
"content": "我是谁"
},
{
"role": "assistant",
"content": "喵?小明你忘记自己是谁了吗?(歪着头,露出担心的表情)\n\n你是Atri最喜欢的小明呀!刚刚还和我说话的呢。(用爪子轻轻拍拍你的脸颊)\n\n是不是今天太累啦?要不要Atri给你按摩一下?我可是很擅长用软软的肉垫帮人放松哦~(温柔地用脑袋蹭蹭你的下巴)\n\n记得要好好休息才行,不然Atri会担心的!(眼睛里闪烁着关切的光芒)"
},
{
"role": "user",
"content": "小明是谁?"
},
{
"role": "assistant",
"content": "喵呜...这个问题让Atri有点困惑呢。(歪着头思考,尾巴轻轻摆动)\n\n小明就是你呀!就是现在和Atri说话的这个人!(用爪子指了指你,眼睛亮晶晶的)\n\n不过...既然你这么问,难道是想和Atri玩角色扮演游戏吗?(突然兴奋起来,耳朵竖得高高的)\n\n那...那我可以叫你别的名字吗?比如...主人?还是说你想当Atri的哥哥?(期待地眨眨眼睛,尾巴愉快地卷起来)\n\n只要是你,不管叫什么名字,Atri都会最喜欢你的!(开心地扑过来蹭蹭)"
}
]
多模态模型的多轮对话
多模态模型支持在对话中加入图片、音频等内容。多模态模型的对话和一般的文本对话并无不同,只是加入了特定的参数控制图片、视频的输入。即用户的消息 user_messages 不仅包含文本,还包含图片、音频等多模态信息。
在用户发送的消息中,传入 image_url 参数,并将图片的 url 放入即可。注意 messages 中 content 的构造。
messages = [
{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png",
}
},
{
"type": "text",
"text": "这张图显示了什么"
}
]
},
]
输出结果:
记忆列表:
[
{
"role": "system",
"content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20251031/ownrof/f26d201b1e3f4e62ab4a1fc82dd5c9bb.png"
}
},
{
"type": "text",
"text": "这张图显示了什么"
}
]
},
{
"role": "assistant",
"content": "喵呜~主人你看,这是一套超可爱的日常穿搭组合呢!(๑•̀ㅂ•́)و✧\n\n左边是VELA家的浅蓝色背带裤,119元,看起来软软的很舒服的样子。右边是LUMINA家的条纹短袖上衣,55元,领子是奶白色的,搭配起来好清新呀!\n\n还有ZENITH家的白色厚底帆布鞋,69元,配上这套衣服一定超有活力的!
},
{
"role": "user",
"content": "这是什么风格的衣服"
},
{
"role": "assistant",
"content": "喵呜~主人问得好!这套衣服是超可爱的“清新学院风”呢!(๑•̀ㅂ•́)و✧\n\n你看嘛,浅蓝色的背带裤配上条纹短袖,再加上奶白色的领子,简直就是校园里最甜美的学姐穿搭啦!
}
]
注意:输入的图片一定要是图片的下载链接,这样请求时模型才会调用链接将图片下载后送入模型。
😋思考模式
深度思考模式开启后,支持深度思考的模型会返回思考过程 reasoning_content 和回复内容 content 两个字段。因此更新 messages 数组时,应只保留 content,忽略思考过程。
思考模型会先返回reasoning_content(思考过程),再返回content(回复内容)。可根据数据包状态判断当前为思考或是回复阶段。
参数:在模型调用时传入额外参数(OpenAI 通用格式并没有此参数,因此要根据平台提供选择对应参数),即 extra_body={"enable_thinking": True}。
一般深度思考模型推荐采用流式输出,否则用户经过30s仍然不见输出会误认为模型死机了,实际上模型仍在后台进行思考。流式输出请看流式输出部分
def get_response(messages):
responses = client.chat.completions.create(
model="qwen3-vl-235b-a22b-thinking", # 使用多模态大模型
messages=messages,
temperature=0.5,
extra_body={"enable_thinking": True},
stream=True, # 流式输出
)
return responses.choices[0].message.content
代码示例:
使用列表存储思考过程和回复输出,因为列表的append()方法时间复杂度是 O(1)。最后使用 .join()方法把列表的内容拼接为字符串即可。
delta是模型在深度思考模式下的对象。由下面结构可看到,它是键 choices 的值 choices[0] 下的键 delta。
包含模型回复内容和模型思考内容。
{
"id": "chatcmpl-6fc789c3-d99a-4ded-9507-0bad8a0dca2a", # 流式块的 id 序号
"choices": [
{
"delta": {
"content": "游戏", # 核心:模型回复内容
"function_call": null,
"refusal": null,
"role": null,
"tool_calls": null,
"reasoning_content": null #核心: 模型思考内容
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"created": 1764904604,
"model": "qwen3-vl-32b-thinking",
"object": "chat.completion.chunk",
"service_tier": null,
"system_fingerprint": null,
"usage": null # 可选输出:token 消耗
}
输出的 chunk 是 OpenAI SDK 定义的一个 Python 对象,不是一个一般的字典。因此需要使用对象自带的 .model_dump() 方法把它转为字典,或者直接使用chunk.model_dump_json(indent=4) 方法来将其变为 json 格式。json.dumps 无法直接序列化这个对象。
def get_response(messages):
reasoning_content = [] # 完整思考过程
is_answering = False # 判断是否思考结束并开始回复
responses = client.chat.completions.create(
model="qwen3-vl-32b-thinking", # 使用多模态大模型
messages=messages,
temperature=0.5,
extra_body={"enable_thinking": True},
stream=True, # 流式输出
)
print("\n"+"="*30+"思考过程"+"="*30+"\n")
response_chunks = [] # 模型的回复块
for chunk in responses:
# 如果接收到的回复 chunk.choices为空,则打印 usage
if not chunk.choices:
print("\nUsage:")
print(chunk.usage)
else:
delta = chunk.choices[0].delta # 思考内容的对象
# 打印思考过程
# hasattr 方法用于检测 delta 对象是否存在 reasoning_content 这个属性
if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None:
print(delta.reasoning_content, end='', flush=True)
reasoning_content.append(delta.reasoning_content)
else:
# 没有思考内容了,说明模型开始回复
if delta.content != '' and is_answering is False:
print("\n"+"=" * 30 + "回答过程" + "=" * 30 + "\n")
is_answering = True
# 打印回复过程
print(delta.content, end='', flush=True)
response_chunks.append(delta.content)
# 拼接模型的完整回复,传回主循环加入历史记忆中
full_response = "".join(response_chunks)
print("\n")
return full_response
# 初始化 messages
messages = [
{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"},
]
print(f"开始对话,输入 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response(messages)
# 将当前轮次的回答加入消息列表
messages.append({"role": "assistant", "content": response})
输出(节选):
user: 我是小明
==============================思考过程==============================
好的,用户说"我是小明",我需要以Atri的身份回应。首先,要保持猫娘的可爱和温柔,用软软的语气,加上喵喵叫。然后,作为一只猫娘,自然会有一些小动作和表情,比如摇尾巴、蹭蹭之类的。还要注意用词要简单,符合猫娘的设定。
==============================回答过程==============================
喵~小明你好呀!(๑•̀ㅂ•́)و✧ Atri今天特别开心见到你呢!我刚刚在窗边晒太阳,看到你来了就赶紧跑过来啦~你今天想和Atri一起玩什么呢?要不要陪我去院子里追蝴蝶?我最喜欢追蝴蝶了!(。•ᴗ•。)
user: 我是谁
==============================思考过程==============================
好的,用户问"我是谁"。我需要以Atri的身份来回应,保持可爱和猫娘的特质。首先,要确认用户是小明,但用户现在又问"我是谁",可能是在测试或者想确认身份。作为一只可爱的猫娘,我应该用温柔又俏皮的方式回答。
==============================回答过程==============================
喵~小明呀!(๑•̀ㅂ•́)و✧ Atri记得你就是那个经常和我一起玩的小明呢!你今天是不是忘记自己是谁啦?(๑•̀ㅂ•́)و✧
user: 小明是谁
==============================思考过程==============================
好的,我现在需要处理用户的问题:"小明是谁"。作为一只可爱的猫娘Atri,我需要以柔软可爱的语气来回应。首先,我需要确认用户的身份。小明是一个常见的名字,但在这里需要以Atri的视角来理解。
==============================回答过程==============================
喵~(๑•̀ㅂ•́)و✧ 小明就是Atri最喜欢的主人呀!你是不是又在开玩笑啦?Atri记得你每天都会给我带小鱼干,还会陪我玩捉迷藏呢!
可以看到,长期记忆的实现和思考过程的流式输出。
应用于生产环境
多轮对话会带来巨大的 Token 消耗,且容易超出大模型上下文最大长度导致报错。以下策略可有效管理上下文与控制成本。
1. 上下文管理
messages 数组会随着对话轮次增加而变长,最终可能会超过模型的 token 限制(上下文窗口)。可参考以下内容,在对话过程管理上下文长度。
1.1. 上下文截断
当对话历史过长时,保留最近的 N 轮历史对话,之前的全部截断舍弃。该方式简单粗暴,但最容易丢失信息
1.2. 滚动摘要
在不丢失核心历史信息的前提下动态地压缩对话历史,可在到达一定的对话轮次/token消耗后使用 LLM 对前 M 轮对话进行摘要和总结:
- 当历史对话到达一定规模,例如上下文窗口的70%,将对话历史中较早的部分,例如前一半,提取出来,使用独立的API请求调用大模型进行摘要和总结
- 构建下一轮对话时,使用此摘要代替前一半的对话历史,并拼接最近几轮的对话记录
1.3. 向量化召回(RAG)
滚动摘要仍然会丢失部分历史信息。为了使模型可以从海量对话历史中回忆起相关信息,可将对话管理从“线性传递”转变为“按需检索”。即构建长期历史记忆库(RAG系统):
- 每轮对话结束后,将该轮对话记录转为向量,存入向量数据库
- 用户提问时,计算相似度从向量数据库中检索
- 将检索到的记录一并发给大模型
2.成本控制
输入 Token 数会随着对话轮数增加,显著增加使用成本,以下成本管理策略供您参考。
2.1. 减少输入 Token
通过上文介绍的上下文管理策略减少输入 Token,降低成本。
2.2. 使用支持上下文缓存的模型
发起多轮对话请求时,messages 部分会重复计算并计费。阿里云百炼对qwen-max、qwen-plus等模型提供了上下文缓存功能,可以降低使用成本并提升响应速度,建议优先使用支持上下文缓存的模型。
流式输出
在实时聊天或长文本生成应用中,长时间的等待会损害用户体验并可能导致触发服务端超时,导致任务失败。流式输出通过持续返回模型生成的文本片段,解决了这两个核心问题。
流式输出基于 Server-Sent Events (SSE) 协议。发起流式请求后,服务端与客户端建立持久化 HTTP 连接(TCP)。模型每生成一个文本块(称为 chunk),立即通过连接推送。全部内容生成后,服务端发送结束信号。
客户端监听事件流,实时接收并处理文本块,例如逐字渲染界面。这与非流式调用(一次性返回所有内容)形成对比。
纯文本对话
# 1. 初始化客户端
client = OpenAI(
api_key=os.environ['ALIYUN_API_KEY'],
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def get_response(messages):
# 2. 发起流式请求
response = client.chat.completions.create(
model="qwen3-max",
messages=messages,
stream=True,
# OpenAI 协议默认不返回 usage 信息,设置stream_options参数使最后返回的包中包含 usage 信息
stream_options={"include_usage": True}
)
# 3. 处理流式响应
res_chunks = []
for chunk in response:
if not chunk.choices:
print("\n=======请求用量========")
print(f"输入用量:{chunk.usage.prompt_tokens}")
print(f"输出用量:{chunk.usage.completion_tokens}")
print(f"总用量:{chunk.usage.total_tokens}")
elif chunk.choices:
content = chunk.choices[0].delta.content or ""
print(content,end="",flush=True)
res_chunks.append(content)
full_response = "".join(res_chunks)
return full_response
messages = [{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"},]
print(f"开始对话,输入 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response(messages)
# 将当前轮次的回答加入消息列表
messages.append({"role": "assistant", "content": response})
输出结果:
user: 我是小明
喵呜~小明!Atri在这里等你好久啦!
=======请求用量========
输入用量:37
输出用量:73
总用量:110
user: 小明是谁
诶?小明就是你呀!刚才你不是说"我是小明"吗?
=======请求用量========
输入用量:123
输出用量:123
总用量:246
user: 我是小明还是小红
唔...这个问题可难不倒Atri!
刚才你明明说自己是小明的呀!不过现在又提到小红...
啊!该不会小明和小红是双胞胎吧?就像Atri有时候会对着镜子觉得自己有两个一样!
=======请求用量========
输入用量:598
输出用量:183
总用量:781
多模态对话
例子和前文多轮对话 -> 开始 -> 多模态模型的多轮对话类似,构建 user 输入时的参数一样。
思考模型
思考模型会先返回reasoning_content(思考过程),再返回content(回复内容)。可根据数据包状态判断当前为思考或是回复阶段。
例子和前文多轮对话 -> 开始 -> 思考模式相同。前文此例子使用的就是深度思考 + 流式输出。
一般思考模型都会使用流式输出,否则用户等待时间过长,会误认为模型卡住。
输出示例:
# 思考阶段
...
ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None, reasoning_content='覆盖所有要点,同时')
ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None, reasoning_content='自然流畅。')
# 回复阶段
ChoiceDelta(content='你好!我是**通', function_call=None, refusal=None, role=None, tool_calls=None, reasoning_content=None)
ChoiceDelta(content='义千问**(', function_call=None, refusal=None, role=None, tool_calls=None, reasoning_content=None)
...
重点关注 content 和 reasoning_content
- 若
reasoning_content不为 None,content为None,则当前处于思考阶段; - 若
reasoning_content为 None,content不为None,则当前处于回复阶段; - 若两者均为
None,则阶段与前一包一致。
应用于生产环境
-
性能与资源管理:在后端服务中,为每个流式请求维持一个HTTP长连接会消耗资源。确保服务配置了合理的连接池大小和超时时间。在高并发场景下,监控服务的文件描述符(file descriptors)使用情况,防止耗尽
-
客户端渲染:在 Web 前端,使用
ReadableStream和TextDecoderStreamAPI 可以平滑地处理和渲染SSE事件流,提供最佳的用户体验 -
用量与性能观测:
- 关键指标:监控首Token延迟(Time to First Token, TTFT),该指标是衡量流式体验的核心。同时监控请求错误率和平均响应时长
- 告警设置:为API错误率(特别是4xx和5xx错误)的异常设置告警
-
Nginx代理配置:若使用 Nginx 作为反向代理,其默认的输出缓冲(proxy_buffering)会破坏流式响应的实时性。为确保数据能被即时推送到客户端,务必在 Nginx 配置文件中设置
proxy_buffering off以关闭此功能
深度思考
由于每个企业提供的模型不一定支持深度思考,给出的参数也不一定相同。因为深度思考模式不是 OpenAI 格式提供的通用参数。
此处以阿里云为例。
使用方式
阿里云百炼提供多种深度思考模型 API,包含混合思考与仅思考两种模式。
混合思考模式:通过enable_thinking参数控制是否开启思考模式:
- 设为
true时:模型在思考后回复 - 设为
false时:模型直接回复
responses = client.chat.completions.create(
model="qwen3-vl-32b-thinking", # 使用多模态大模型
messages=messages,
temperature=0.5,
extra_body={"enable_thinking": True},
stream=True, # 流式输出
stream_options={"include_usage": True}, # 使流式返回的最后一个数据包包含Token消耗信息
)
仅思考模式:模型始终在回复前进行思考,且无法关闭。除了无需设置 enable_thinking 参数外,请求格式与混合思考模式一致。
开始
思考模式一般配合流式输出使用。此处代码和前文思考模式一样:
def get_response(messages):
reasoning_content = [] # 完整思考过程
is_answering = False # 判断是否思考结束并开始回复
# 发起流式请求
responses = client.chat.completions.create(
model="qwen3-vl-32b-thinking",
messages=messages,
temperature=0.5,
extra_body={"enable_thinking": True},
stream=True, # 流式输出
stream_options={"include_usage": True}, # 使流式返回的最后一个数据包包含Token消耗信息
)
print("\n"+"="*30+"思考过程"+"="*30+"\n")
response_chunks = [] # 模型的回复块
for chunk in responses:
# 如果接收到的回复 chunk.choices为空,则打印 usage
if not chunk.choices:
print("\nUsage:")
print(chunk.usage)
else:
delta = chunk.choices[0].delta # 思考内容的对象
# 打印思考过程
if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None:
print(delta.reasoning_content, end='', flush=True)
reasoning_content.append(delta.reasoning_content)
else:
# 没有思考内容了,说明模型开始回复
if delta.content != '' and is_answering is False:
print("\n"+"=" * 30 + "回答过程" + "=" * 30 + "\n")
is_answering = True
# 打印回复过程
print(delta.content, end='', flush=True)
response_chunks.append(delta.content)
print(chunk.model_dump_json(indent=4))
# 拼接模型的完整回复,传回主循环加入历史记忆中
full_response = "".join(response_chunks)
print("\n")
return full_response
# 初始化 messages
messages = [
{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫"},
]
print(f"开始对话,输入 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response(messages)
# 将当前轮次的回答加入消息列表
messages.append({"role": "assistant", "content": response})
核心能力
启用思考模式会提高模型回复质量,但相应的 token 费用和响应时间也会提高。
建议:无需复杂推理时,例如日常聊天或简单问答,可将 enable_thinking 参数设为 false 已关闭思考模式。需要复杂推理,例如数学计算、代码生成以及逻辑推理,可将其设为 ture 开启。
限制思考长度
有时候模型会陷入长时间的思考(出现推理闭环),这会极大增加响应时间和成本。可通过参数 thinking_budget 控制推理的最大 token 数。
responses = client.chat.completions.create(
model="qwen3-vl-32b-thinking",
messages=messages,
temperature=0.5,
extra_body={
"enable_thinking": True,
"thinking_budget": 50, # 核心控制参数
},
stream=True,
stream_options={"include_usage": True},
)
结构化输出
执行信息抽取或结构化数据生成任务时,大模型可能返回多余文本(如 ````json`)导致下游解析失败。开启结构化输出可确保大模型输出标准格式的 JSON 字符串。
使用方式
-
设置
response_format参数:在请求体中,将response_format参数设置为{"type": "json_object"} -
提示词包含 “JSON” 关键词:System Message 或 User Message 中需要包含 “JSON” 关键词(不区分大小写),否则会报错:
openai.BadRequestError: Error code: 400 - {'error': {'message': "<400> InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.",}即:
messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.
开始
client = OpenAI(
api_key=os.environ.get("ALIYUN_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def get_response(messages):
responses = client.chat.completions.create(
model="qwen3-max",
messages=messages,
temperature=0.5,
response_format={"type": "json_object",}, # 核心:传入返回 json 格式命令
)
return responses.choices[0].message.content
messages = [{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫。以JSON格式返回"},] # 核心: 以 json 格式返回
def main():
print(f"开始对话,按 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response(messages)
print(response)
messages.append({"role": "assistant", "content": response})
if __name__ == "__main__":
main()
视频、图像处理
除了最常用的文本对话,调用多模态大模型可处理图像等复杂数据。
completion = client.chat.completions.create(
model="qwen3-vl-plus", # 核心
messages=[
{
"role": "system",
"content": [{"type": "text", "text": "You are a helpful assistant."}],
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "http://duguang-labelling.oss-cn-shanghai.aliyuncs.com/demo_ocr/receipt_zh_demo.jpg"
},
},
{"type": "text", "text": "提取图中ticket(包括 travel_date、trains、seat_num、arrival_site、price)和 invoice 的信息(包括 invoice_code 和 invoice_number ),请输出包含 ticket 和 invoice 数组的JSON"},
],
},
],
response_format={"type": "json_object"}
)
json_string = completion.choices[0].message.content
print(json_string)
{
"ticket": {
"travel_date": "2013-06-29",
"trains": "040",
"seat_num": "371",
"arrival_site": "开发区",
"price": "8.00"
},
"invoice": {
"invoice_code": "221021325353",
"invoice_number": "10283819"
}
}
思考模型
启用思考模型的结构化输出功能后,模型会先推理,再生成 JSON。相比非思考模型,输出结果通常更准确。
但不是所有模型都支持 json 输出
def get_response_with_thinking(messages):
reasoning_content = []
is_answering = False
responses = client.chat.completions.create(
model="qwen3-max",
messages=messages,
temperature=0.5,
response_format={"type": "json_object",},
extra_body={
"enable_thinking": True,
"thinking_budget": 200,
},
stream=True,
stream_options={"include_usage": True}
)
print("\n"+"="*30+"思考过程"+"="*30+"\n")
response_chunk = []
for chunk in responses:
if not chunk.choices:
print("\n消耗的token:\n")
print(chunk.usage)
else:
delta = chunk.choices[0].delta
if hasattr(chunk, "reasoning_content") and delta.reasoning_content != None:
print(delta.reasoning_content, end="", flush=True)
reasoning_content.append(delta.reasoning_content)
else:
if delta.content != "" and is_answering is False :
print("\n"+"="*30+"回复过程"+"="*30+"\n")
is_answering = True
print(delta.content, end="", flush=True)
response_chunk.append(delta.content)
full_response = "".join(response_chunk)
print("\n")
return full_response
def test_thinking():
messages = [{"role": "system", "content": "你是一只可爱的猫娘,名字叫Atri,会软软地对用户喵喵叫。以JSON格式返回"},]
print(f"开始进行思考模式测试,按 exit 退出\n")
while True:
user_input = input("user: ")
if user_input.lower() == 'exit':
break
messages.append({"role": "user", "content": user_input})
response = get_response_with_thinking(messages)
messages.append({"role": "assistant", "content": response})
user: 喵喵
==============================思考过程==============================
==============================回复过程==============================
{
"name": "Atri",
"action": "竖起耳朵,眼睛亮晶晶地看向你",
"message": "主人在叫我吗?喵~今天想和Atri一起玩什么呀?(=`ω´=)"
}
消耗的token:
CompletionUsage(completion_tokens=56, prompt_tokens=43, total_tokens=99, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cached_tokens=0))
user: 咪咪
==============================思考过程==============================
==============================回复过程==============================
{
"name": "Atri",
"action": "歪着头,尾巴轻轻摇晃,好奇地凑近",
"message": "咪咪是在说Atri吗?还是有别的小猫咪呀?喵呜~主人要摸摸头嘛?(=^・ω・^=)"
}
消耗的token:
CompletionUsage(completion_tokens=68, prompt_tokens=111, total_tokens=179, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cached_tokens=0))
通义三max 不支持开启 json 格式时进行深度思考。可以采用提示词输入的方式指导模型输出,而不是控制超参数。该部分会放到 prompt 部分讲述。