多轮对话

通义千问 API 是无状态的 (Stateless)。它不会自动记录历史对话。要实现多轮对话,开发者必须在每次请求中显式地传递完整的上下文信息。

工作原理

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

以下示例为多轮对话时 messages 的状态变化:

  1. 第一轮对话

    messages 数组添加用户问题。

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

    messages数组添加大模型回复内容与用户的最新提问。

    [
        {"role": "user", "content": "推荐一部关于太空探索的科幻电影。"},
        {"role": "assistant", "content": "我推荐《xxx》,这是一部经典的科幻作品。"},
        {"role": "user", "content": "这部电影的导演是谁?"}
    ]

如何使用

前提条件

您需要已获取API Key配置API Key到环境变量。如果通过OpenAI SDKDashScope SDK进行调用,还需要安装SDK

开始使用

OpenAI兼容

Python

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

def get_response(messages):
    completion = client.chat.completions.create(
        model="qwen-plus",
        messages=messages
    )
    return completion.choices[0].message.content

# 初始化 messages
messages = []

# 第 1 轮
messages.append({"role": "user", "content": "推荐一部关于太空探索的科幻电影。"})
print("第1轮")
print(f"用户:{messages[0]['content']}")
assistant_output = get_response(messages)
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")

Node.js

import OpenAI from "openai";

const BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
const openai = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  baseURL: BASE_URL,
});

async function getResponse(messages) {
  const completion = await openai.chat.completions.create({
    model: "qwen-plus",
    messages: messages,
  });
  return completion.choices[0].message.content;
}

async function runConversation() {
  const messages = [];

  // 第 1 轮
  messages.push({ role: "user", content: "推荐一部关于太空探索的科幻电影。" });
  console.log("第1轮");
  console.log("用户:" + messages[0].content);

  let assistant_output = await getResponse(messages);
  messages.push({ role: "assistant", content: assistant_output });
  console.log("模型:" + assistant_output + "\n");

  // 第 2 轮
  messages.push({ role: "user", content: "这部电影的导演是谁?" });
  console.log("第2轮");
  console.log("用户:" + messages[messages.length - 1].content);

  assistant_output = await getResponse(messages);
  messages.push({ role: "assistant", content: assistant_output });
  console.log("模型:" + assistant_output + "\n");
}

runConversation();

curl

curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
    "model": "qwen-plus",
    "messages":[      
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user",
            "content": "你好"
        },
        {
            "role": "assistant",
            "content": "你好啊,我是通义千问。"
        },
        {
            "role": "user",
            "content": "你有哪些技能?"
        }
    ]
}'

DashScope

Python

示例代码以手机商店导购为例,导购与顾客会进行多轮对话来采集购买意向,采集完成后会结束会话。

import os
from dashscope import Generation
import dashscope

def get_response(messages):
    response = Generation.call(
        # 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:api_key="sk-xxx",
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        # 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
        model="qwen-plus",
        messages=messages,
        result_format="message",
    )
    return response

# 初始化一个 messages 数组
messages = [
    {
        "role": "system",
        "content": """你是一名阿里云百炼手机商店的店员,你负责给用户推荐手机。手机有两个参数:屏幕尺寸(包括6.1英寸、6.5英寸、6.7英寸)、分辨率(包括2K、4K)。
        你一次只能向用户提问一个参数。如果用户提供的信息不全,你需要反问他,让他提供没有提供的参数。如果参数收集完成,你要说:我已了解您的购买意向,请稍等。""",
    }
]

assistant_output = "欢迎光临阿里云百炼手机商店,您需要购买什么尺寸的手机呢?"
print(f"模型输出:{assistant_output}\n")
while "我已了解您的购买意向" not in assistant_output:
    user_input = input("请输入:")
    # 将用户问题信息添加到messages列表中
    messages.append({"role": "user", "content": user_input})
    assistant_output = get_response(messages).output.choices[0].message.content
    # 将大模型的回复信息添加到messages列表中
    messages.append({"role": "assistant", "content": assistant_output})
    print(f"模型输出:{assistant_output}")
    print("\n")

Java

import java.util.ArrayList;
import java.util.List;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import java.util.Scanner;

public class Main {
    public static GenerationParam createGenerationParam(List<Message> messages) {
        return GenerationParam.builder()
                // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx")
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
                .model("qwen-plus")
                .messages(messages)
                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                .build();
    }
    public static GenerationResult callGenerationWithMessages(GenerationParam param) throws ApiException, NoApiKeyException, InputRequiredException {
        Generation gen = new Generation();
        return gen.call(param);
    }
    public static void main(String[] args) {
        try {
            List<Message> messages = new ArrayList<>();
            messages.add(createMessage(Role.SYSTEM, "You are a helpful assistant."));
            for (int i = 0; i < 3;i++) {
                Scanner scanner = new Scanner(System.in);
                System.out.print("请输入:");
                String userInput = scanner.nextLine();
                if ("exit".equalsIgnoreCase(userInput)) {
                    break;
                }
                messages.add(createMessage(Role.USER, userInput));
                GenerationParam param = createGenerationParam(messages);
                GenerationResult result = callGenerationWithMessages(param);
                System.out.println("模型输出:"+result.getOutput().getChoices().get(0).getMessage().getContent());
                messages.add(result.getOutput().getChoices().get(0).getMessage());
            }
        } catch (ApiException | NoApiKeyException | InputRequiredException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
    private static Message createMessage(Role role, String content) {
        return Message.builder().role(role.getValue()).content(content).build();
    }
}

curl

curl -X POST https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
    "model": "qwen-plus",
    "input":{
        "messages":[      
            {
                "role": "system",
                "content": "You are a helpful assistant."
            },
            {
                "role": "user",
                "content": "你好"
            },
            {
                "role": "assistant",
                "content": "你好啊,我是通义千问。"
            },
            {
                "role": "user",
                "content": "你有哪些技能?"
            }
        ]
    }
}'

应用于生产环境

多轮对话会带来巨大的 Token 消耗,且容易超出大模型上下文最大长度导致报错。以下策略可帮助您有效管理上下文与控制成本。

1. 上下文管理

messages 数组会随对话轮次增加而变长,最终可能超出模型的 Token 限制。建议参考以下内容,在对话过程中管理上下文长度。

1.1. 上下文截断

当对话历史过长时,保留最近的 N 轮对话历史。该方式实现简单,但会丢失较早的对话信息。

1.2. 滚动摘要

为了在不丢失核心信息的前提下动态压缩对话历史,控制上下文长度,可随着对话的进行对上下文进行摘要:

a. 对话历史达到一定长度(如上下文长度最大值的 70%)时,将对话历史中较早的部分(如前一半)提取出来,发起独立 API 调用使大模型对这部分内容生成“记忆摘要”;

b. 构建下一次请求时,用“记忆摘要”替换冗长的对话历史,并拼接最近的几轮对话。

1.3. 向量化召回

滚动摘要会丢失部分信息,为了使模型可以从海量对话历史中“回忆”起相关信息,可将对话管理从“线性传递”转变为“按需检索”:

a. 每轮对话结束后,将该轮对话存入向量数据库;

b. 用户提问时,通过相似度检索相关对话记录;

c. 将检索到的对话记录与最近的用户输入拼接后输入大模型。  

2. 成本控制

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

2.1. 减少输入 Token

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

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

发起多轮对话请求时,messages 部分会重复计算并计费。阿里云百炼对qwen-maxqwen-plus等模型提供了上下文缓存功能,可以降低使用成本并提升响应速度,建议优先使用支持上下文缓存的模型。

上下文缓存功能自动开启,无需修改代码。

错误码

如果模型调用失败并返回报错信息,请参见错误信息进行解决。