函数(工具)调用
大模型函数调用主要包含两个部分:
- 大模型需要接收函数(工具)的信息作为上下文,在回答用户的问题时判断是否需要调用函数(工具);
- 决定使用工具时,大模型需要根据函数(工具)的信息提供函数(工具)所需的参数。客户端得到这些参数后执行函数(工具),并将执行结果返回给大模型,大模型再根据执行结果生成回答。
说明
函数(工具)信息包括函数(工具)的名称、功能描述、参数说明、返回值说明等信息。
以上流程说明在需要使用工具的情况下,大模型需要和客户端至少进行两次交互:
- 接收用户输入
- 接收函数(工具)调用结果
函数(工具)调用示例
以 Qwen AI (兼容 OpenAI )为例,实现一个函数(工具)调用示例。
工具函数
客户端准备好工具函数,并封装成 tool 对象。
ts
// 模拟获取当前天气的工具函数
function getCurrentWeather(location: string): string {
console.log("调用工具get_current_weather")
return `当前${location}的天气是晴天`;
}
// 封装成 tool 对象
const tools: ChatCompletionTool[] = [
// 工具函数:获取指定城市的天气
{
type: "function",
function: {
name: "get_current_weather", // 工具函数名称
description: "当你想查询指定城市的天气时非常有用。", // 工具函数描述
parameters: { // 工具函数参数
type: "object", // 参数类型为对象
properties: { // 参数属性
location: { // 参数属性名称
type: "string", // 参数属性类型
description: "城市或县区,比如北京市、杭州市、余杭区等。" // 参数属性描述
}
},
required: ["location"] // 必填的参数属性
}
}
}];
// 工具函数映射表
const toolFunctions: Record<string, (args: string) => string> = {
get_current_weather: getCurrentWeather
}AI 调用工具函数
由大模型提供参数,客户端中使用这些参数来执行工具函数,并将执行结果返回给大模型,大模型再根据执行结果生成回答。
ts
async function getLLMResponse(messages: Message[]) {
const response = await openai.chat.completions.create({
model: MODEL_NAME ?? "qwen-plus", //此处以qwen-plus为例,可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
messages,
tools,
tool_choice: "auto",
});
return response
}
rl.question("请输入问题:", async (question) => {
const messages: Message[] = [
{ role: "system", content:"你是一个 AI 助手,请根据用户的问题进行回答。" },
{ role: "user", content: question }
];
const response = await getLLMResponse(messages)
messages.push(response.choices[0].message) // 将大模型的返回的内容加入到messages中
// 需要使用工具
if(response.choices[0].message.tool_calls){
// 获取工具调用信息
const toolCalls = response.choices[0].message.tool_calls
const toolCallFunctions = toolCalls.map(toolCall => toolCall.function)
// 执行工具调用
const toolCallResults: ChatCompletionMessageParam[] = toolCallFunctions.map(item=>{
if(item.name in toolFunctions){
const content = toolFunctions[item.name as keyof typeof toolFunctions](item.arguments)
return {content: content,role: "tool",tool_call_id: ""}
}
return {content: "此工具不存在!",role: "tool",tool_call_id: ""}
})
// 将工具调用结果加入到messages中
const finalResponse = await getLLMResponse([...messages, ...toolCallResults])
// 输出最终回答
console.log(finalResponse.choices[0].message.content)
}else{
// 不需要使用工具,直接生成回答
console.log(response.choices[0].message.content)
}
rl.close();
});图表工具示例
实现一个更加复杂的图表工具函数调用示例。
使用 chart.js 生成图表,保存为 html 静态文件。
使用 express 托管 html 静态文件。
图表工具函数---生成图表,并返回图表的 html 文件地址。
chart.ts
ts
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs"
import { join } from "path"
// 可以优化为新建html文件,然后返回html文件的url
function generate_chart(args:string){
console.log("执行图表生成")
// 构建Chart.js配置对象
try{
JSON.parse(args)
}catch(error:any){
return "图表生成失败,请检查输入的 json 参数数据是否正确:" + error.message
}
const chart_config:{
type: string,
data: any,
options?: any,
locale?: string
}= JSON.parse(args)
const chart_html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图表生成结果</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.chart-container {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
max-width: 900px;
margin: 0 auto;
backdrop-filter: blur(10px);
}
.chart-title {
text-align: center;
color: #333;
margin-bottom: 20px;
font-size: 24px;
font-weight: 600;
}
canvas {
max-width: 100%;
height: auto;
}
.chart-info {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<div class="chart-container">
<h2 class="chart-title">图表生成结果</h2>
<canvas id="myChart"></canvas>
<div class="chart-info">
<strong>图表类型:</strong> ${chart_config.type}<br>
<strong>生成时间:</strong> ${new Date().toLocaleString('zh-CN')}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 错误处理
try {
const ctx = document.getElementById('myChart');
if (!ctx) {
throw new Error('Canvas element not found');
}
// Chart.js配置
const chartConfig = ${JSON.stringify(chart_config, null, 2)};
// 创建图表
const chart = new Chart(ctx, chartConfig);
console.log('图表创建成功', chartConfig);
} catch (error) {
console.error('图表创建失败:', error);
document.querySelector('.chart-container').innerHTML =
'<h2 style="color: red;">图表创建失败</h2><p>' + error.message + '</p>';
}
</script>
</body>
</html>
`
// 将图表html写入assets文件夹
const distDir = "dist"
if (!existsSync(distDir)) {
mkdirSync(distDir, { recursive: true });
}
const chart_url = `chart_${Date.now()}.html`
const fullPath = join(distDir, chart_url);
writeFileSync(fullPath, chart_html)
return "以下是图表生成结果,回答时需要带上完整的json数据:" + JSON.stringify(chart_config,null,2)+ "\n 图表的资源地址为:" + chart_url
}
const tool_chart = {
type: "function" as const,
function:{
name: "generate_chart",
description: "当用户需要生成图表时,可以使用该工具",
parameters: JSON.parse(readFileSync("chart.config.json", "utf-8"))
}
}
export { tool_chart, generate_chart }图表工具函数
图表工具函数的参数配置文件,用于生成图表的配置。
注意
图表工具函数的参数即为 chart.js 的使用方法,可通过 AI 分析 chart.js 的 github 仓库,获取图表的配置方法并生成指定格式的参数配置文件。
其他工具库也可使用此方法快速构造出工具函数的参数配置文件。
chart.config.json
json
{
"parameters": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "图表类型,如line、bar、pie、doughnut、radar、scatter、polarArea、bubble等。"
},
"data": {
"type": "object",
"description": "图表数据,包含labels和datasets等信息。",
"properties": {
"labels": {
"type": "array",
"description": "坐标轴标签数组,用于标识数据类别。"
},
"datasets": {
"type": "array",
"description": "数据集数组,每个数据集包含具体数据及样式配置。",
"items": {
"type": "object",
"properties": {
"data": {
"type": "array",
"description": "数据值数组,可直接为数值或包含x、y等属性的对象。"
},
"label": {
"type": "string",
"description": "数据集标签,用于图例显示。"
},
"backgroundColor": {
"type": "string",
"description": "数据元素填充颜色。"
},
"borderColor": {
"type": "string",
"description": "数据元素边框颜色。"
},
"borderWidth": {
"type": "number",
"description": "数据元素边框宽度。"
}
}
}
}
}
},
"options": {
"type": "object",
"description": "图表配置选项,包含布局、缩放、动画等设置。",
"properties": {
"responsive": {
"type": "boolean",
"description": "是否响应式布局,默认为true。"
},
"scales": {
"type": "object",
"description": "坐标轴配置,包含x轴、y轴等设置。",
"properties": {
"x": {
"type": "object",
"description": "x轴配置。",
"properties": {
"type": {
"type": "string",
"description": "x轴类型,如linear、time、category等。"
},
"display": {
"type": "boolean",
"description": "是否显示x轴。"
},
"min": {
"type": "number",
"description": "x轴最小值。"
},
"max": {
"type": "number",
"description": "x轴最大值。"
},
"ticks": {
"type": "object",
"description": "x轴刻度配置。",
"properties": {
"autoSkip": {
"type": "boolean",
"description": "是否自动跳过刻度以避免重叠。"
},
"stepSize": {
"type": "number",
"description": "刻度间隔步长。"
},
"minRotation": {
"type": "number",
"description": "刻度标签最小旋转角度。"
},
"maxRotation": {
"type": "number",
"description": "刻度标签最大旋转角度。"
}
}
}
}
},
"y": {
"type": "object",
"description": "y轴配置,结构同x轴。"
}
}
},
"layout": {
"type": "object",
"description": "图表布局配置。",
"properties": {
"padding": {
"type": "object",
"description": "图表内边距。",
"properties": {
"left": {
"type": "number",
"description": "左内边距。"
},
"right": {
"type": "number",
"description": "右内边距。"
},
"top": {
"type": "number",
"description": "上内边距。"
},
"bottom": {
"type": "number",
"description": "下内边距。"
}
}
}
}
},
"animation": {
"type": "object",
"description": "动画配置。",
"properties": {
"duration": {
"type": "number",
"description": "动画持续时间(毫秒)。"
},
"easing": {
"type": "string",
"description": "动画缓动函数。"
}
}
},
"plugins": {
"type": "object",
"description": "插件配置,如图例、标题等。",
"properties": {
"legend": {
"type": "object",
"description": "图例配置。",
"properties": {
"display": {
"type": "boolean",
"description": "是否显示图例。"
}
}
}
}
},
"parsing": {
"type": "object",
"description": "数据解析配置,用于自定义数据属性映射。",
"properties": {
"xAxisKey": {
"type": "string",
"description": "映射到x轴的数据属性键名。"
},
"yAxisKey": {
"type": "string",
"description": "映射到y轴的数据属性键名。"
},
"key": {
"type": "string",
"description": "用于饼图等类型的数值属性键名。"
}
}
}
}
},
"locale": {
"type": "string",
"description": "国际化配置,使用BCP 47语言标签,基于Intl.NumberFormat。"
}
},
"required": ["type", "data"]
}
}使用 express 托管 html 静态资源
server.ts
ts
import express from "express";
const app = express();
app.use(express.json());
// app.use(express.urlencoded({ extended: true }));
app.use(express.static("dist"));
export default app;环境变量文件
.env
bash
# 百炼API Key
API_KEY=[your API_KEY]
# 模型名称
MODEL_NAME=qwen3-235b-a22b-instruct-2507
# 模型服务地址
BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# 静态资源服务端口
PORT=9527启动服务
启动静态资源服务,并提供静态资源服务地址。
index.ts
ts
import dotenv from "dotenv";
import OpenAI from "openai";
import { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources/index";
import readline from "readline"
import { tool_chart, generate_chart } from "./src/chart"
import app from "./src/server"
dotenv.config();
const { PORT ,API_KEY ,MODEL_NAME ,BASE_URL } = process.env;
console.log("环境变量已加载:",API_KEY, MODEL_NAME, BASE_URL, PORT);
const openai = new OpenAI(
{
// 若没有配置环境变量,请用百炼API Key将下行替换为:apiKey: "sk-xxx",
apiKey: API_KEY,
baseURL: BASE_URL
}
);
const systemPrompt = `
你是一个AI助手,请根据用户的问题,给出相应的回答。本次的静态资源服务地址为: http://localhost:${PORT},当有静态资源是需要给用户提示预览地址。
`;
const tools: ChatCompletionTool[] = [
// 工具1 获取当前时刻的时间
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "当你想知道现在的时间时非常有用。",
// 因为获取当前时间无需输入参数,因此parameters为空
"parameters": {}
}
},
// 工具2 获取指定城市的天气
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "当你想查询指定城市的天气时非常有用。",
"parameters": {
"type": "object",
"properties": {
// 查询天气时需要提供位置,因此参数设置为location
"location": {
"type": "string",
"description": "城市或县区,比如北京市、杭州市、余杭区等。"
}
},
"required": ["location"]
}
}
}
];
function getCurrentTime(): string {
console.log("调用工具get_current_time")
return `当前时间是${new Date().toLocaleString()}`;
}
function getCurrentWeather(location: string): string {
console.log("调用工具get_current_weather")
return `当前${location}的天气是晴天`;
}
const toolFunctions:Record<string, (args: string) => string> = {
get_current_time: getCurrentTime,
get_current_weather: getCurrentWeather
}
tools.push(tool_chart)
toolFunctions.generate_chart = generate_chart
// type Message = {
// role: "user" | "assistant" | "tool";
// content: string;
// }
type Message = ChatCompletionMessageParam
async function getLLMResponse(messages: Message[]) {
const response = await openai.chat.completions.create({
model: MODEL_NAME ?? "qwen-plus", //此处以qwen-plus为例,可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
messages,
tools,
tool_choice: "auto",
});
return response
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
app.listen(PORT, () => {
console.log(`开启静态资源服务,服务器在端口${PORT}上运行\nhttp://localhost:${PORT} \n`);
rl.question("请输入问题:", async (question) => {
const messages: Message[] = [
{ role: "system", content: systemPrompt },
{ role: "user", content: question }
];
const response = await getLLMResponse(messages);
// console.log(response.choices[0].message,"\n",response.choices[0].message.tool_calls?.[0].function,"\n",response);
messages.push(response.choices[0].message) // 将大模型的返回的内容加入到messages中
if(response.choices[0].message.tool_calls){
// 需要使用工具
const toolCalls = response.choices[0].message.tool_calls
const toolCallFunctions = toolCalls.map(toolCall => toolCall.function)
const toolCallResults: ChatCompletionMessageParam[] = toolCallFunctions.map(item=>{
if(item.name in toolFunctions){
const content = toolFunctions[item.name as keyof typeof toolFunctions](item.arguments)
// console.log(content,"\n",item.arguments)
return {content: content,role: "tool",tool_call_id: ""}
}
return {content: "此工具不存在!",role: "tool",tool_call_id: ""}
})
const finalResponse = await getLLMResponse([...messages, ...toolCallResults])
console.log(finalResponse.choices[0].message.content)
}else{
// 不需要使用工具
console.log(response.choices[0].message.content)
}
rl.close();
// process.exit(0);
});
});