本文内容包括:

  1. 构建简单的请求发送给 LLM
  2. 解析大模型的响应内容
  3. 实现与LLM的多轮对话
  4. 实现流式输出
  5. 实现指导LLM进行深度思考
  6. 指导大模型返回结构化响应(方便后续解析)

请求构建

前提:已经在 .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
        }
    }
}

可以看到,我们一般想要的内容是:

  1. 返回的字典键为 “choices” 的 message 中的 content
  2. 消耗的 token 值(钱)
print(response.choices[0].message.content)
print(response.usage.total_tokens)
喵呜~小明你好呀!(歪着头好奇地看着你,尾巴轻轻摇晃)我是Atri哦,是一只可爱的猫娘!刚刚在窗台上晒太阳的时候就听到你的声音啦。你能来找我玩真是太好啦!(开心地蹭了蹭你的手) 小明今天想和Atri一起做什么呢?我们可以一起晒太阳、玩毛线球,或者去院子里抓蝴蝶哦!
131

多轮对话

实现与LLM的多轮对话

OpenAI 格式的 api 是无状态 stateless 的,不会保存对话历史。要实现多轮对话,需在每次请求中显式传入历史对话消息,并可结合截断、摘要、召回等策略,高效管理上下文,减少 Token 消耗。

工作原理

实现多轮对话的核心是维护一个 messages ,或者是 history 数组。每一轮对话都需要将用户的最新提问和模型的回复追加到此数组中,作为下一轮对话的输入

例如:

  1. 第一轮对话:

    [
        {"role": "user", "content": "推荐一部关于太空探索的科幻电影。"}
    ]
  2. 第二轮对话:

    [
        {"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 轮对话进行摘要和总结:

  1. 当历史对话到达一定规模,例如上下文窗口的70%,将对话历史中较早的部分,例如前一半,提取出来,使用独立的API请求调用大模型进行摘要和总结
  2. 构建下一轮对话时,使用此摘要代替前一半的对话历史,并拼接最近几轮的对话记录

1.3. 向量化召回(RAG)

滚动摘要仍然会丢失部分历史信息。为了使模型可以从海量对话历史中回忆起相关信息,可将对话管理从“线性传递”转变为“按需检索”。即构建长期历史记忆库(RAG系统):

  1. 每轮对话结束后,将该轮对话记录转为向量,存入向量数据库
  2. 用户提问时,计算相似度从向量数据库中检索
  3. 将检索到的记录一并发给大模型

2.成本控制

输入 Token 数会随着对话轮数增加,显著增加使用成本,以下成本管理策略供您参考。

2.1. 减少输入 Token

通过上文介绍的上下文管理策略减少输入 Token,降低成本。

2.2. 使用支持上下文缓存的模型

发起多轮对话请求时,messages 部分会重复计算并计费。阿里云百炼对qwen-maxqwen-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)
...

重点关注 contentreasoning_content

  • reasoning_content不为 None,contentNone,则当前处于思考阶段;
  • reasoning_content为 None,content 不为 None,则当前处于回复阶段;
  • 若两者均为 None,则阶段与前一包一致。

应用于生产环境

  • 性能与资源管理:在后端服务中,为每个流式请求维持一个HTTP长连接会消耗资源。确保服务配置了合理的连接池大小和超时时间。在高并发场景下,监控服务的文件描述符(file descriptors)使用情况,防止耗尽

  • 客户端渲染:在 Web 前端,使用 ReadableStreamTextDecoderStream API 可以平滑地处理和渲染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 字符串。

使用方式

  1. 设置 response_format 参数:在请求体中,将 response_format 参数设置为 {"type": "json_object"}

  2. 提示词包含 “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 部分讲述。

Author

JuyaoHuang

Publish Date

12 - 04 - 2025