1. 内容介绍
本文主要介绍如何通过阿里云百炼,快速入门通义千问系列大语言模型。
通义千问是由阿里云自主研发的大语言模型,用于理解和分析用户输入的自然语言,在不同领域和任务为用户提供服务和帮助。您可以通过提供尽可能清晰详细的指令,来获取符合您预期的结果。
目前阿里云百炼的通义千问大语言模型主要包括qwen-turbo、qwen-plus、qwen-max和qwen-max-longcontext,具体参数说明请参考下面的表格,开发者可以根据不同的业务场景选择不同的模型。
qwen-turbo |
通义千问超大规模语言模型,支持中文、英文等不同语言输入。 |
模型支持8k tokens上下文,为了保证正常的使用和输出,API限定用户输入为6k tokens。 |
qwen-plus |
通义千问超大规模语言模型增强版,支持中文、英文等不同语言输入。 |
模型支持32k tokens上下文,为了保证正常的使用和输出,API限定用户输入为30k tokens。 |
qwen-max |
通义千问千亿级别超大规模语言模型,支持中文、英文等不同语言输入。随着模型的升级,qwen-max将滚动更新升级。如果希望使用固定版本,请使用历史快照版本。当前qwen-max模型与qwen-max-0428快照版本等价,均为最新版本的qwen-max模型,也是当前通义千问2.5产品版本背后的API模型。 |
模型支持8k tokens上下文,为了保证正常的使用和输出,API限定用户输入为6k tokens。 |
qwen-max-longcontext |
通义千问千亿级别超大规模语言模型,支持中文、英文等不同语言输入。 |
模型支持30k tokens上下文,为了保证正常的使用和输出,API限定用户输入为28k tokens。 |
模型计费和限流规则,请参考官方文档: 通义千问大语言模型计费与限流。
2. 模型体验
下边我们通过阿里云百炼的官网页面来体验一下通义千问的大语言模型。
首先,访问阿里云百炼官网,并参考文档开通阿里云百炼大模型服务产品, 开通阿里云百炼的云产品服务。
然后,点击左侧“模型体验”->“模型测试”后,按照如下步骤进行模型体验测试:
- 选择模型,如通义千问-Max。
- 配置模型参数,如system prompt,temperature等。
- 输入user message,如“请你扮演大诗人李白,......”。
- 点击“执行”按钮后,即可在右侧看到模型的输出结果。
3. 模型调用
下边介绍如何通过API或者SDK调用通义千问大语言模型。
如果需要通过SDK调用模型,需要参考文档安装阿里云百炼SDK,安装或引用SDK。目前阿里云百炼的SDK提供Java和Python语言,其他语言可以参考API定义进行开发。
3.1 快速调用
1)首先,进入阿里云百炼官网,点击“模型广场”,并选择需要调用的模型,如“通义千问-Max”,然后点击“API调用示例”。
2) 然后,点击“查看我的API-KEY” -> “创建API-KEY”,并点击“查看”获取到API-KEY。如果已创建过API-KEY,直接点击“查看”获取API-KEY即可。
3) 选择需要调用的模型,如“通义千问-Max”,然后点击“API调用示例”。选择调用的语言,如“Python”,然后点击“复制代码”按钮,即可复制代码到IDE直接运行。
在调用模型前,需要通过环境变量或者参数设置API-KEY。
# 在执行代码前,通过环境变量设置API-KEY export DASHSCOPE_API_KEY=YOUR_API_KEY # 或者通过代码设置API-KEY response = Generation.call(api_key="YOUR_API_KEY", message=messages)
Python代码的调用通义千问-Max模型的示例如下:
from http import HTTPStatus from dashscope import Generation def call_with_stream(): messages = [ {'role': 'user', 'content': '如何做西红柿炖牛腩?'}] responses = Generation.call("qwen-max", messages=messages, result_format='message', # 设置输出为'message'格式 stream=True, # 设置输出方式为流式输出 incremental_output=True # 增量式流式输出 ) for response in responses: if response.status_code == HTTPStatus.OK: print(response.output.choices[0]['message']['content'],end='') else: print('Request id: %s, Status code: %s, error code: %s, error message: %s' % ( response.request_id, response.status_code, response.code, response.message )) if __name__ == '__main__': call_with_stream()
3.2 多轮对话
我们在与模型进行多轮对话的时候,模型本身并不能够提供记忆的功能。这时,就需要我们在调用模型的时候,将之前的对话记录也传给大模型,这样模型在推理的时候可以根据之前的对话记录和当前问题进行回答。下面是一个多轮对话的例子:
from http import HTTPStatus import dashscope def send_message(user_message: str) -> str: global messages messages.append({'role': 'user', 'content': user_message}) response = dashscope.Generation.call(model="qwen-turbo", messages=messages, # 将输出设置为"message"格式 result_format='message') if response.status_code == HTTPStatus.OK: # 将assistant的回复添加到messages列表中 messages.append({'role': response.output.choices[0]['message']['role'], 'content': response.output.choices[0]['message']['content']}) return response.output.choices[0]['message']['content'] else: # 如果响应失败,将最后一条user message从messages列表里删除,确保user/assistant消息交替出现 messages = messages[:-1] return response.message if __name__ == '__main__': messages = [ {'role': 'system', 'content': '你是一个旅游助手.'} ] # 第一轮对话 result = send_message("我想去新疆") print("第一轮结果: \n%s" % result) # 第二轮对话 result = send_message("哪里有什么特色美食") print("第二轮结果: \n%s" % result)
执行结果如下:
第一轮结果: 新疆是一个充满魅力的旅游目的地,拥有壮丽的自然风光、丰富的历史文化以及多元的民族风情。以下是一些推荐的旅行景点和注意事项: 1. **乌鲁木齐**:作为新疆的首府,你可以参观大巴扎(国际大巴扎),体验维吾尔族的市集文化;游览红山公园和天山天池。 2. **喀纳斯湖**:这是中国最美的湖泊之一,有“人间仙境”的美誉,还有神秘的月亮湾和神仙湾。 3. **天山南北**:包括天山天池、天山大峡谷、那拉提草原等,是欣赏雪山、草原、森林的好地方。 4. **吐鲁番**:可以参观葡萄沟,品尝新疆特产葡萄,还可以看看坎儿井。 5. **伊犁**:这里有霍尔果斯口岸,可以体验边境贸易的特色,还有薰衣草田和美丽的伊犁河谷。 6. **禾木村**:位于阿尔泰山脉,是拍摄日落和星空的理想地,也是小众的摄影爱好者的天堂。 7. **南疆地区**:库车大峡谷、阿克苏地区的库尔勒等地,可以看到独特的地貌和文化遗产。 8. **美食**:别忘了尝试新疆的羊肉串、大盘鸡、手抓饭、葡萄干等特色美食。 注意事项: - 新疆气候干燥,早晚温差大,记得带好防晒和保暖用品。 - 尊重当地风俗习惯,尤其是少数民族地区的礼仪。 - 在景区游玩时,请遵守规定,保护环境。 - 如果自驾游,确保车辆状况良好,并了解路线和天气情况。 在计划前往新疆之前,最好提前查看最新的旅行指南和当地政策,确保行程顺利。祝你旅途愉快! 第二轮结果: 新疆美食以其丰富的口味和独特的烹饪方式闻名,以下是新疆的一些特色美食: 1. **羊肉串(Kebab)**:新疆最有名的街头小吃之一,选用优质羊肉,串在铁签上,烤至外焦里嫩,香气四溢。 2. **大盘鸡(Dapanji)**:鸡肉块与土豆、洋葱、青椒等一起炖煮,汤汁浓郁,味道鲜美。 3. **手抓饭(Qingfa fan)**:用羊肉或牛肉、胡萝卜、洋葱等烹制的米饭,口感软糯,肉香浓郁。 4. **抓饭(Chawarma)**:类似羊肉卷饼,将羊肉、蔬菜和面团一起烤制,营养丰富,便于携带。 5. **烤全羊(Roast lamb)**:整只羊经过特殊腌制后炭火慢烤,皮脆肉嫩,是新疆传统宴席上的佳肴。 6. **拌面(La mian)**:新疆特色的面食,如牛肉面、羊肉面,配以各种蔬菜和酱料,口感爽滑。 7. **纳仁(Naer)**:一种甜点,由葡萄干、杏仁、核桃、葡萄等干果制成,甜而不腻。 8. **奶茶(Kumis)**:新疆的传统饮品,奶茶醇厚,搭配各种糕点十分美味。 9. **酸奶(Yogurt)**:新疆酸奶口感酸甜,营养价值高,是新疆人的日常饮品。 10. **葡萄干(Guanggan)**:新疆的葡萄品种丰富,晒制的葡萄干甜度极高,是新疆特产。 这些只是新疆美食的一部分,每个地区可能还有其独特的美食,不妨在当地餐馆或市场尝试更多地道的风味。
在这个例子中,我们将用户的输入和模型返回的结果都存入了一个全局变量messages 的list结构中,但在实际的生产环境中,我们通常会将上下文的历史记录存储在redis缓存,mysql或者nosql等数据库中进行持久化。
3.3 函数调用
大语言模型由于是一个预训练的模型,通常在实时性问题和私域知识问题的回答效果欠佳。这时,我们就需要通过模型的函数调用功能,解决大模型的实时性和私域数据知识空白的问题。下边是一个让大模型回答实时天气的例子:
import json import random from dashscope import Generation # 定义工具列表,模型在选择使用哪个工具时会参考工具的name和description tools = [ # 工具1 获取指定城市的天气 { "type": "function", "function": { "name": "get_current_weather", "description": "当你想查询指定城市的天气时非常有用。", "parameters": { # 查询天气时需要提供位置,因此参数设置为location "type": "object", "properties": { "location": { "type": "string", "description": "城市或县区,比如北京市、杭州市、余杭区等。" } } }, "required": [ "location" ] } } ] # 模拟天气查询工具。返回结果示例:“北京今天是晴天。” def get_current_weather(location): return f"{location}今天是晴天。 " # 封装模型响应函数 def get_response(messages): response = Generation.call( model='qwen-max', messages=messages, tools=tools, seed=random.randint(1, 10000), # 设置随机数种子seed,如果没有设置,则随机数种子默认为1234 result_format='message' # 将输出设置为message形式 ) return response def call_with_messages(message: str): print('\n') messages = [ { "content": message, "role": "user" } ] # 模型的第一轮调用 first_response = get_response(messages) assistant_output = first_response.output.choices[0].message # print(f"\n大模型第一轮输出信息:{first_response}\n") messages.append(assistant_output) if 'tool_calls' not in assistant_output: # 如果模型判断无需调用工具,则将assistant的回复直接打印出来,无需进行模型的第二轮调用 print(f"最终答案:{assistant_output.content}") # 此处直接返回模型的回复,您可以根据您的业务,选择当无需调用工具时最终回复的内容 return # 如果模型选择的工具是get_current_weather elif assistant_output.tool_calls[0]['function']['name'] == 'get_current_weather': tool_info = {"name": "get_current_weather", "role": "tool"} location = json.loads(assistant_output.tool_calls[0]['function']['arguments'])['properties']['location'] tool_info['content'] = get_current_weather(location) print(f"工具输出信息:{tool_info['content']}\n") messages.append(tool_info) # 模型的第二轮调用,对工具的输出进行总结 second_response = get_response(messages) # print(f"大模型第二轮输出信息:{second_response}\n") print(f"最终答案:{second_response.output.choices[0].message['content']}") if __name__ == '__main__': call_with_messages("今天北京的天气如何?")
输出结果如下:
工具输出信息:北京市今天是晴天。 最终答案:今天北京市是晴朗的天气。
在这里,get_current_weather方法直接返回了结果,在实际的情况下,通常是调用三方查询天气的API来实时地返回结果。
有不少朋友在使用函数调用时,经常会出现一下几类问题,通常通过以下方法能够解决这类问题:
- 我问了相关的问题,但是模型并没有规划到我期望的函数,而是模型直接用通用知识直接回答了。
解决方法:在定义function tool的时候,对description需要有清晰明确的任务和用途描述,比如: “这是个天气查询工具,当你想查询指定城市的天气时,必须使用此工具。”。这样,模型在规划时,会根据用户的问题,和tool的描述,来判定是否需要使用这个function。
- 在进行参数的提取过程中,经常提取不到对应的参数,或者参数格式不对。
解决方法: 在参数的描述中,需要清晰的定义参数的信息,格式以及示例。比如我要查询某天的天气时,需要用户指定日期,那么可以这样进行定义:
"properties": { "date": { "type": "string", "description": "查询天气的日期,格式为年-月-日,比如2024-06-01。" } }
4. 开发实践
在实际的开发过程中,有不少的朋友也问到,为什么调用模型进行问答响应很慢,以及如何实现类似通义千问应用的打字机效果。接下来我们也会提供分别提供Python后端、Java后端以及前端的代码示例。
通常情况下,不建议开发者直接通过前端调用阿里云百炼的API,这样api-key在前端暴露很容易造成安全性问题。因此都会建议开发者通过前端在调用后端服务,然后后端服务再调用阿里云百炼的API。
4.1 Python后端代码示例
Python后端代码基于FastAPI,并提供流式的方式将大模型生成的内容增量返回给前端。在运行代码前需要安装如下组件:
pip install fastapi pip install uvicorn
基于fastapi的python后端代码如下:
import json import logging import traceback from http import HTTPStatus from typing import Generator from dashscope import Generation from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse from pydantic import BaseModel, Field logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def create_app(): """Create the FastAPI app and include the router.""" fa_app = FastAPI() origins = [ "*", ] fa_app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) return fa_app # 创建fastapi app app = create_app() # 定义后端api的输入输出参数结构 class ChatRequest(BaseModel): message: str = Field(default=None, alias='message') class ChatResponse(BaseModel): content: str = Field(default=None, alias='content') messages = [ {'role': 'system', 'content': '你是一个旅游助手.'} ] def sse_data(data: str) -> str: return f"data: {data}\n\n" def chat_generator(request: ChatRequest) -> Generator[str, None, None]: try: global messages message = request.message messages.append({'role': 'user', 'content': message}) logging.info("send message to llm: %s" % (json.dumps(messages, ensure_ascii=False))) responses = Generation.call("qwen-max", messages=messages, result_format='message', stream=True, incremental_output=True ) full_message = "" for response in responses: if response.status_code == HTTPStatus.OK: data = response.output.choices[0]['message']['content'] full_message += data else: logging.error('Request id: %s, Status code: %s, error code: %s, error message: %s' % ( response.request_id, response.status_code, response.code, response.message )) data = response.message result = ChatResponse(content=data) yield sse_data(json.dumps(result.dict(), ensure_ascii=False)) if full_message is not None and full_message: messages.append({'role': 'assistant', 'content': full_message}) except Exception as e: logging.error("failed to do stream chat, request: %s, error: %s" % (request, traceback.format_exc())) yield sse_data(str(e)) post("/v1/chat", summary="chat api") .async def chat(request: ChatRequest): logger.info("start chat, request: %s" % json.dumps(request.dict(), ensure_ascii=False)) return StreamingResponse(chat_generator(request=request), media_type="text/event-stream") if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8080)
4.2 Java后端代码
Java后端代码基于Spring boot webflux实现,并提供流式的方式将大模型生成的内容增量返回给前端。
pom依赖的参考内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.alibaba</groupId> <artifactId>bailian-backend</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>2.7.18</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <version>2.15.1</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </project>
spring boot的controller参考代码如下:
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 io.reactivex.Flowable; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; "/v1") ( origins = "*") (public class ChatController { private static final List<Message> messages = new ArrayList<>(); public void init() { messages.add(Message.builder().role(Role.SYSTEM.getValue()).content("你是一个旅游助手.").build()); } value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) ( public Flux<ChatResponse> generation( ChatRequest request) { try { messages.add(Message.builder() .role(Role.USER.getValue()) .content(request.getMessage()) .build()); log.info("send messages to llm: {}", messages); GenerationParam param = GenerationParam.builder() .model("qwen-max").messages(messages) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .topP(0.8) .incrementalOutput(true) .build(); Generation gen = new Generation(); Flowable<GenerationResult> generationResult = gen.streamCall(param); List<String> chunks = new ArrayList<>(); return Flux.from(generationResult).map(data -> handGenerationResult(data, chunks)) .onErrorResume(this::handleGenerationError) .doOnComplete(this::handleGenerationComplete); } catch (ApiException | NoApiKeyException | InputRequiredException e) { log.error("failed to generate message", e); if (!messages.isEmpty()) { messages.remove(messages.size() - 1); } ChatResponse response = ChatResponse.builder().content(e.getMessage()).build(); return Flux.just(response); } } private ChatResponse handGenerationResult(GenerationResult result, List<String> chunks) { if (result == null) { return null; } String content = result.getOutput().getChoices().get(0).getMessage().getContent(); String finishReason = result.getOutput().getChoices().get(0).getFinishReason(); ChatResponse response = ChatResponse.builder() .content(content) .build(); chunks.add(content); //缓存上下文内容 if (Objects.equals(finishReason, "stop")) { messages.add(Message.builder() .role(Role.ASSISTANT.getValue()) .content(String.join("", chunks)) .build()); } return response; } private Mono<ChatResponse> handleGenerationError(Throwable t) { ChatResponse response = ChatResponse.builder() .content(t.getMessage()) .build(); return Mono.just(response); } private void handleGenerationComplete() { log.info("handle Generation Complete!"); } } class ChatRequest implements Serializable { private String message; } class ChatResponse implements Serializable { private String content; }
上述代码中,使用了spring boot的webflux将内容通过http sse的方式流式返回。如果读者对webflux和reactor了解较少,可以先了解一下webflux和reactor的相关知识。
4.3 前端代码
前端代码基于html, javascript的方式实现,并通过js处理sse的流式消息,实现大模型问答的打字机效果。
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>阿里云百炼-大模型应用前端</title> <style> #output { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background: #f9f9f9; height: 200px; overflow-y: auto; } </style> </head> <body> <div style="width: 50%;margin-left: auto; margin-right: auto"> <div id="output" style="height: 500px"></div> <p></p> <label for="message"></label><textarea id="message" placeholder="输入内容" cols="80" rows="6"></textarea> <button onclick="sendMessage()">发送消息</button> </div> <script> let outputElement = document.getElementById('output'); let currentIndex = 0; let currentText = ''; async function sendMessage() { // outputElement.textContent = "正在思考中......"; const message = document.getElementById('message').value; // 发送POST请求 try { await fetch("http://localhost:8080/v1/chat", { // 替换为实际的POST接口地址 method: "POST", headers: { "Content-Type": "application/json", "Accept": "text/event-stream", }, body: JSON.stringify({message: message}) }).then(async response => { if (!response.ok) { console.error("POST请求失败"); return; } document.getElementById('message').value = ""; outputElement.textContent += "\n\n我: " + message + "\n\nAI:"; // const reader = response.body.getReader(); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader() let buffer = ""; while (true) { const {value, done} = await reader.read(); if (done) { break; } // console.log("value:[" + value + "]") buffer += value; let index; while ((index = buffer.indexOf('\n\n')) >= 0) { const rawMessage = buffer.slice(0, index); buffer = buffer.slice(index + 2); const message = parseSSEMessage(rawMessage); console.log("Parsed message: [" + message.data + "]"); const resp = JSON.parse(message.data) currentText += resp.content; typeEffect(); } } }).catch(error => { console.error("sse请求发生错误:", error); }); } catch (error) { console.error("请求发生错误:", error); } } function parseSSEMessage(rawMessage) { const lines = rawMessage.split('\n'); const message = {}; for (const line of lines) { if (line.trim() === '') continue; const [field, rest] = line.split(':'); const value = rest.join(':').trim(); if (field in message) { message[field] += '\n' + value; } else { message[field] = value; } } return message; } function typeEffect() { if (currentIndex < currentText.length) { outputElement.textContent += currentText.charAt(currentIndex); currentIndex++; setTimeout(typeEffect, 50); // 设置每个字符的打字速度,这里是50ms } } </script> </body> </html>
5. 写在最后
如果在使用阿里云百炼过程中,有碰到任何问题以及建议,欢迎大家留言讨论或是加入我们的官方支持群(群号:77600022533)进行交流反馈!