作者:刘天蒙,阿里云技术内容工程师,负责百炼产品-通义千问 API 使用相关的文档,并参与部分百炼实践教程文档开发,以及大模型ACA、ACP课程的开发。由于通义千问具有强大的自然语言处理能力,许多开发者都会选择阿里云百炼提供的API 来将其集成到业务中。作为通义千问 API 文档的作者,需要面对不同的背景的开发者。在将多种不同功能、不同语言的示例代码展示在官方文档的过程中,我充分使用了通义灵码强大的代码理解与生成能力,减少了我对不熟悉编程语言的学习成本。我将为大家分享通义灵码是如何对我的工作进行提效与赋能。01
我的使用场景
场景1:翻译代码
背景:对于许多大模型的使用者来说,最熟悉的语言可能是 Python,或者 Curl 命令,我也是其中一个。但是也有许多使用其他语言的开发者(比如 Java、JavaScript、Go、C++ 等)想要了解如何使用通义千问 API,如果文档中只有 Python 代码,对这些开发者其实是劝退的。因此我会使用通义灵码插件,将 Curl 命令 “翻译”为这些语言,极大节省我对于新语言的学习成本。
真实需求
假设我现在有了一份 Curl 的命令,现在有添加 Node.js 示例代码的需求,但是我之前没有接触过 Node.js。
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": "你是谁?" } ]}'
我们前往通义灵码来完成这个需求。在安装最新版通义灵码插件后,打开通义灵码新推出的AI程序员 Tab 页,输入以下提示词。
提示:我有这样一个curl的命令,请帮我转化为可以通过node qwen_test.mjs命令可以运行的javascript代码。请注意,我需要看到json化后的结果,并且把代码写进qwen_test.mjs
这个需求对通义灵码有一些细节上的考察。
1. 环境变量语句的理解
Authorization: Bearer $DASHSCOPE_API_KEY中的 是环境变量,通义灵码生成的代码很好地理解了这个变量的含义,并通过下方代码来从环境变量中获取 API Key。$DASHSCOPE_API_KEY
const apiKey = process.env.DASHSCOPE_API_KEY;
2. Node.js环境的模块系统Node.js的模块系统包括 CommonJS、ES modules等,如果想要直接通过命令运行,依赖就需要通过 import 而不是 require 方法导入,通义灵码也很好地实现了这点。node test.mjs
通义灵码的AI程序员帮助我们建立了一个qwen_test.mjs文件,并且把生成的代码放了进去。我们通过在terminal中运行命令,打印出了我们预期的结果。整个过程无需我们手动建立mjs文件,也不用写代码,非常的方便(通义千问API文档中许多代码都是我通过通义灵码辅助完成的)。node qwen_test.mjs
{ "output": { "choices": [ { "finish_reason": "stop", "message": { "role": "assistant", "content": "我是来自阿里云的大规模语言模型,我叫通义千问。" } } ] }, "usage": { "prompt_tokens_details": { "cached_tokens": 0 }, "total_tokens": 38, "output_tokens": 16, "input_tokens": 22 }, "request_id": "51d5c5bb-be19-9ad0-b7b5-030735b3d206"}
场景2:解释代码
背景:为了加深对工具的理解程度,开发者经常会去阅读源码。然而较长的源码有时会对理解带来困难。Ragas 是专门用于评测 RAG 应用的框架(现在也支持了评测 NL2SQL、Agent等应用),它会从定义好的维度去给 RAG 应用打分,对于使用者来说,只需传入 RAG 应用的数据(问题、答案、召回的文本段、问题对应的标准答案),便可以从检索、生成、整体等多个维度得到分数。
在开发大模型 ACP 认证课程时,我参与编写了使用 Ragas 框架评测 RAG 应用的课程,对于课程开发者来说,不仅需要教学员如何使用 Ragas 评测指标,也需要介绍指标的含义与计算过程,这样才能帮助学员更好地理解指标背后的原理和应用场景。
真实需求
Ragas 其中一个指标是 Context Precision,这个指标的计算过程比较复杂,我最初通过 Ragas 的官方文档也没法很清楚地理解。
遇事不决找源码。我找到了本地 Ragas SDK 的源码,将它直接丢给了通义灵码,让它给我解释一下这个指标是怎么算出来的,并且给我举例子帮助我理解。
给我介绍一下这个指标是怎么算的,最后配一个示例给我介绍下计算过程。
通义灵码非常完美地解读了指标的计算方法,在我几次追问后,我彻底明白了这个指标的计算过程。
如果verdict_list是[1,0,0]呢,分数应该是多少?
如果verdict_list是[0,1,1]呢,分数应该是多少?
为什么[0,1,1]有效的context数量比[1,0,0]多,分数却比它低?
于是我将计算过程写在了大模型 ACP 课程上。
大家如果对阿里云大模型 ACP 课程(免费学习)感兴趣,可以在阿里云认证的页面中看到。
场景3:代码专业性校验与改写
背景:由于我写的代码是直接面对开发者的,而通义千问又是许多开发者关注的重点,因此流量比较大。官网示例代码既需要保证代码的可运行性,又要保证代码的专业性、简洁性,比如命名是否规范,缩进是否正确,是否有错别字等都是我需要时刻关注的角度。
真实需求
这是我之前写的一份通过 DashScope Java SDK 完成的 Function call代码(大家感兴趣的话可以前往百炼-文本生成 文档中查看)。由于我本人对 Java 了解不多,因此这份代码中有许多不规范的地方。比如:
1. 天气Weather写成了Whether;
2. Java 变量命名规范更多遵循驼峰命名法,但是我由于不了解,写成了fd_whether这样的不规范命名变量。
// Copyright (c) Alibaba, Inc. and its affiliates.// version >= 2.12.0import com.alibaba.dashscope.aigc.conversation.ConversationParam.ResultFormat;import com.alibaba.dashscope.aigc.generation.Generation;import com.alibaba.dashscope.aigc.generation.GenerationOutput.Choice;import com.alibaba.dashscope.aigc.generation.GenerationParam;import com.alibaba.dashscope.aigc.generation.GenerationResult;import com.alibaba.dashscope.common.Message;import com.alibaba.dashscope.exception.ApiException;import com.alibaba.dashscope.exception.InputRequiredException;import com.alibaba.dashscope.exception.NoApiKeyException;import com.alibaba.dashscope.tools.FunctionDefinition;import com.alibaba.dashscope.tools.ToolCallBase;import com.alibaba.dashscope.tools.ToolCallFunction;import com.alibaba.dashscope.tools.ToolFunction;import com.alibaba.dashscope.utils.JsonUtils;import com.fasterxml.jackson.databind.node.ObjectNode;import com.github.victools.jsonschema.generator.Option;import com.github.victools.jsonschema.generator.OptionPreset;import com.github.victools.jsonschema.generator.SchemaGenerator;import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;import com.github.victools.jsonschema.generator.SchemaVersion;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import com.alibaba.dashscope.common.Role;import java.util.Scanner; public class Main { public class GetWhetherTool { private String location; public GetWhetherTool(String location) { this.location = location; } public String call() { return location+"今天是晴天"; } } public class GetTimeTool { public GetTimeTool() { } public String call() { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String currentTime = "当前时间:" + now.format(formatter) + "。"; return currentTime; } } public static void SelectTool() throws NoApiKeyException, ApiException, InputRequiredException { SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON); SchemaGeneratorConfig config = configBuilder.with(Option.EXTRA_OPEN_API_FORMAT_VALUES) .without(Option.FLATTENED_ENUMS_FROM_TOSTRING).build(); SchemaGenerator generator = new SchemaGenerator(config); ObjectNode jsonSchema_whether = generator.generateSchema(GetWhetherTool.class); ObjectNode jsonSchema_time = generator.generateSchema(GetTimeTool.class); FunctionDefinition fd_whether = FunctionDefinition.builder().name("get_current_whether").description("获取指定地区的天气") .parameters(JsonUtils.parseString(jsonSchema_whether.toString()).getAsJsonObject()).build(); FunctionDefinition fd_time = FunctionDefinition.builder().name("get_current_time").description("获取当前时刻的时间") .parameters(JsonUtils.parseString(jsonSchema_time.toString()).getAsJsonObject()).build(); Message systemMsg = Message.builder().role(Role.SYSTEM.getValue()) .content("You are a helpful assistant. When asked a question, use tools wherever possible.") .build(); Scanner scanner = new Scanner(System.in); System.out.print("\n请输入:"); String userInput = scanner.nextLine(); Message userMsg = Message.builder().role(Role.USER.getValue()).content(userInput).build(); List<Message> messages = new ArrayList<>(); messages.addAll(Arrays.asList(systemMsg, userMsg)); GenerationParam param = GenerationParam.builder() .model("qwen-plus") // 若没有配置环境变量,请用百炼API Key将下行替换为:.apiKey("sk-xxx") .apiKey(System.getenv("DASHSCOPE_API_KEY")) .messages(messages).resultFormat(ResultFormat.MESSAGE) .tools(Arrays.asList(ToolFunction.builder().function(fd_whether).build(),ToolFunction.builder().function(fd_time).build())).build(); // 大模型的第一轮调用 Generation gen = new Generation(); GenerationResult result = gen.call(param); System.out.println("\n大模型第一轮输出信息:"+JsonUtils.toJson(result)); for (Choice choice : result.getOutput().getChoices()) { messages.add(choice.getMessage()); // 如果需要调用工具 if (result.getOutput().getChoices().get(0).getMessage().getToolCalls() != null) { for (ToolCallBase toolCall : result.getOutput().getChoices().get(0).getMessage() .getToolCalls()) { if (toolCall.getType().equals("function")) { // 获取工具函数名称和入参 String functionName = ((ToolCallFunction) toolCall).getFunction().getName(); String functionArgument = ((ToolCallFunction) toolCall).getFunction().getArguments(); // 大模型判断调用天气查询工具的情况 if (functionName.equals("get_current_whether")) { GetWhetherTool GetWhetherFunction = JsonUtils.fromJson(functionArgument, GetWhetherTool.class); String whether = GetWhetherFunction.call(); Message toolResultMessage = Message.builder().role("tool") .content(String.valueOf(whether)).toolCallId(toolCall.getId()).build(); messages.add(toolResultMessage); System.out.println("\n工具输出信息:"+whether); } // 大模型判断调用时间查询工具的情况 else if (functionName.equals("get_current_time")) { GetTimeTool GetTimeFunction = JsonUtils.fromJson(functionArgument, GetTimeTool.class); String time = GetTimeFunction.call(); Message toolResultMessage = Message.builder().role("tool") .content(String.valueOf(time)).toolCallId(toolCall.getId()).build(); messages.add(toolResultMessage); System.out.println("\n工具输出信息:"+time); } } } } // 如果无需调用工具,直接输出大模型的回复 else { System.out.println("\n最终答案:"+result.getOutput().getChoices().get(0).getMessage().getContent()); return; } } // 大模型的第二轮调用 包含工具输出信息 param.setMessages(messages); result = gen.call(param); System.out.println("\n大模型第二轮输出信息:"+JsonUtils.toJson(result)); System.out.println(("\n最终答案:"+result.getOutput().getChoices().get(0).getMessage().getContent())); } public static void main(String[] args) { try { SelectTool(); } catch (ApiException | NoApiKeyException | InputRequiredException e) { System.out.println(String.format("Exception %s", e.getMessage())); } System.exit(0); }}
我使用通义灵码的 AI程序员功能,让它帮我仔细检查一下。
这是一份需要上官网文档的示例代码,所以有比较高的要求,请你帮我从变量命名规范、是否有错别字等各方面仔细检查一下
通义灵码通过自己的知识,完美地找出了这份代码的不规范与不合理之处。这让我的代码变得更加简洁和规范,同时也让这份文档能够更好地支撑 Java 开发者的使用。
此时的系统还只能回答使用一个工具的问题,在面对“四个直辖市的天气”这样的问题时它无法给出正确答案,关键在于代码中只有一次对toolcall属性的判断,如果需要多个工具,可以使用while循环来判断返回参数中是否存在toolcall信息。我们对通义灵码给出如下提示词:
我想修改一下代码,对toolcall属性是否存在的判断需要不止一次,写一个while循环,直到不输出toolcall信息后跳出循环,并输出最终结果
场景4:激发灵码的潜力
由于训练数据时效性,通义灵码可能无法准确知晓一些最新的API用法, 我们可以给通义灵码一些代码参考(手动 RAG ),这样可以提高通义灵码代码的通过率。
假设有一个需求。产品需要对客户发起一次产品调研,任务是在一天之内统计出用户认为产品哪些方面有待改进,比如:价格过高、售后支持不足、产品使用体验不佳等。现在收集到了1万多份主观反馈,存放在data_analysis.xlsx文件中。在这样的情况下,借助大语言模型 API 可以大大简化和加速文本处理的任务。于是我们向通义灵码传入提示词:
为了便于演示,我们这里使用了8条数据来模拟业务场景。
请从当前文件夹中的data_analysis.xlsx 文件中逐行读取数据(xlsx文件的“反馈内容”列记录着用户的反馈明细),并调用大模型分析用户的反馈原因分类。分类包括:价格过高、售后支持不足、产品使用体验不佳、其他。回答格式为:分类结果:xxx。你需要通过openai兼容的方法调用qwen-plus模型,使用python语言。
可以看到,通义灵码生成的代码此时是不可用的,这是因为 OpenAI 兼容调用 qwen-plus 模型的方法是近一段时间才出来的,通义灵码的模型可能还没有更新到这个知识点。为了解决这个问题,我们可以把首次调用通义千问API中的示例代码粘过来,给通义灵码参考信息(手动RAG)。就像我们作为开发者,查找接口使用方法也是去参考官方文档一样。于是提示词修改成了这样:
请从当前文件夹中的data_analysis.xlsx 文件中逐行读取数据(xlsx文件的“反馈内容”列记录着用户的反馈明细),并调用大模型分析用户的反馈原因分类,统计出一共用了多长时间。分类包括:价格过高、售后支持不足、产品使用体验不佳、其他。你可以参考下面代码调用同通义千问API来进行分析(请注意,我已经配置好了环境变量DASHSCOPE_API_KEY,直接os.getenv就行)。import osfrom openai import OpenAI try: client = OpenAI( # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx", api_key="sk-xxxxxxxxx", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", ) completion = client.chat.completions.create( model="qwen-plus", # 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models messages=[ {'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'user', 'content': '你是谁?'} ] ) print(completion.choices[0].message.content)except Exception as e: print(f"错误信息:{e}") print("请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code"
现在再运行代码是可以得到返回结果的。但是通义灵码生成的提示词并没有强制要求qwen-plus模型按照“分类结果:xxx”的格式进行回答,于是我们再次向通义灵码提出要求:
帮我修改一下代码,我需要让大模型强制输出“分类结果:xxx”格式的回答,一定在提示词里强调不能输出其它内容,并且要打印出问题与分类结果的对应关系。
现在通义千问返回的结果已经完全符合我们的预期了。但是我们可以发现,现阶段的文本分类过程是一句一句处理的,属于大模型 API 的同步调用,如果使用异步调用的方法,那将可以大大提升处理速度。我们可以将通义千问异步调用的代码粘过来提供给通义灵码参考,并且要求通义灵码加入计时机制,能让我们直观看到异步调用带来的速度提升。
这段代码很棒。我现在想用异步调用的方法提高处理速度,给你参考代码:import os import asyncio from openai import AsyncOpenAI import platform 创建异步客户端实例client = AsyncOpenAI( # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) 定义异步任务列表async def task(question): print(f"Sending question: {question}") response = await client.chat.completions.create( messages=[ {"role": "user", "content": question} ], model="qwen-plus", ) print(f"Received answer: {response.choices[0].message.content}") 主异步函数async def main(): questions = ["你是谁?", "你会什么?", "天气怎么样?"] tasks = [task(q) for q in questions] await asyncio.gather(*tasks) if name == 'main': # 设置事件循环策略 if platform.system() == 'Windows': asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # 运行主协程 asyncio.run(main(), debug=False) 请帮我统计出来异步与同步调用的时间差异
通义灵码非常完美地帮我们改好了代码,并在代码中移植进了计时功能,异步调用比同步调用快了好几倍!02
墙裂推荐
通义灵码在我的工作中提供了非常多的帮助,希望大家可能通过多多使用通义灵码。近期通义灵码上线了超级多新能力,欢迎大家体验反馈。
嘿嘿,秀一下通义灵码给我的奖杯