工具调用 API
工具调用(也称为函数调用)允许模型在生成过程中调用外部函数,实现与外部 API、数据库和自定义逻辑的集成。
概述
vLLM Client 支持 OpenAI 兼容的工具调用:
#![allow(unused)] fn main() { use vllm_client::{VllmClient, json}; let client = VllmClient::new("http://localhost:8000/v1"); let response = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!([ {"role": "user", "content": "东京的天气怎么样?"} ])) .tools(tools) .send() .await?; }
定义工具
基础工具定义
工具使用遵循 OpenAI 规范的 JSON 格式定义:
#![allow(unused)] fn main() { let tools = json!([ { "type": "function", "function": { "name": "get_weather", "description": "获取指定地点的当前天气", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名称,如东京" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位" } }, "required": ["location"] } } } ]); }
多个工具
#![allow(unused)] fn main() { let tools = json!([ { "type": "function", "function": { "name": "get_weather", "description": "获取天气信息", "parameters": { "type": "object", "properties": { "location": {"type": "string"} }, "required": ["location"] } } }, { "type": "function", "function": { "name": "search_web", "description": "搜索网页信息", "parameters": { "type": "object", "properties": { "query": {"type": "string"}, "limit": {"type": "integer"} }, "required": ["query"] } } } ]); }
工具选择
控制模型如何选择工具:
#![allow(unused)] fn main() { // 让模型自行决定(默认) .tool_choice(json!("auto")) // 禁止使用工具 .tool_choice(json!("none")) // 强制使用工具 .tool_choice(json!("required")) // 强制使用特定工具 .tool_choice(json!({ "type": "function", "function": {"name": "get_weather"} })) }
处理工具调用
检查工具调用
#![allow(unused)] fn main() { use vllm_client::{VllmClient, json, VllmError}; let response = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!([ {"role": "user", "content": "东京的天气怎么样?"} ])) .tools(tools) .send() .await?; // 检查响应是否包含工具调用 if response.has_tool_calls() { if let Some(tool_calls) = &response.tool_calls { for tool_call in tool_calls { println!("函数: {}", tool_call.name); println!("参数: {}", tool_call.arguments); } } } }
ToolCall 结构
#![allow(unused)] fn main() { pub struct ToolCall { pub id: String, // 调用的唯一标识 pub name: String, // 函数名称 pub arguments: String, // 参数的 JSON 字符串 } }
解析参数
将参数字符串解析为类型化数据:
#![allow(unused)] fn main() { use serde::Deserialize; #[derive(Deserialize)] struct WeatherArgs { location: String, unit: Option<String>, } if let Some(tool_call) = response.first_tool_call() { // 解析为特定类型 match tool_call.parse_args_as::<WeatherArgs>() { Ok(args) => { println!("地点: {}", args.location); if let Some(unit) = args.unit { println!("单位: {}", unit); } } Err(e) => { eprintln!("解析参数失败: {}", e); } } // 或解析为通用 JSON let args: Value = tool_call.parse_args()?; } }
工具结果方法
创建工具结果消息:
#![allow(unused)] fn main() { // 创建工具结果消息 let tool_result = tool_call.result(json!({ "temperature": 25, "condition": "sunny", "humidity": 60 })); // 返回一个可直接加入消息的 JSON 对象 // { // "role": "tool", // "tool_call_id": "...", // "content": "{\"temperature\": 25, ...}" // } }
完整工具调用流程
#![allow(unused)] fn main() { use vllm_client::{VllmClient, json, ToolCall}; use serde::{Deserialize, Serialize}; #[derive(Deserialize)] struct WeatherArgs { location: String, } #[derive(Serialize)] struct WeatherResult { temperature: f32, condition: String, } // 模拟天气 API fn get_weather(location: &str) -> WeatherResult { WeatherResult { temperature: 25.0, condition: "sunny".to_string(), } } async fn chat_with_tools(client: &VllmClient, user_message: &str) -> Result<String, Box<dyn std::error::Error>> { let tools = json!([ { "type": "function", "function": { "name": "get_weather", "description": "获取当前天气", "parameters": { "type": "object", "properties": { "location": {"type": "string"} }, "required": ["location"] } } } ]); // 第一次请求 let response = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!([ {"role": "user", "content": user_message} ])) .tools(tools.clone()) .send() .await?; // 检查模型是否要调用工具 if response.has_tool_calls() { let mut messages = vec![ json!({"role": "user", "content": user_message}) ]; // 将助手的工具调用加入消息 if let Some(tool_calls) = &response.tool_calls { let assistant_msg = response.assistant_message(); messages.push(assistant_msg); // 执行每个工具并加入结果 for tool_call in tool_calls { if tool_call.name == "get_weather" { let args: WeatherArgs = tool_call.parse_args_as()?; let result = get_weather(&args.location); messages.push(tool_call.result(json!(result))); } } } // 带工具结果继续对话 let final_response = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!(messages)) .tools(tools) .send() .await?; return Ok(final_response.content.unwrap_or_default()); } Ok(response.content.unwrap_or_default()) } }
流式工具调用
流式响应中,工具调用会增量推送:
#![allow(unused)] fn main() { use vllm_client::{VllmClient, json, StreamEvent}; use futures::StreamExt; let mut stream = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!([ {"role": "user", "content": "东京和巴黎的天气怎么样?"} ])) .tools(tools) .stream(true) .send_stream() .await?; let mut tool_calls: Vec<ToolCall> = Vec::new(); let mut content = String::new(); while let Some(event) = stream.next().await { match event { StreamEvent::Content(delta) => { content.push_str(&delta); print!("{}", delta); } StreamEvent::ToolCallDelta { index, id, name, arguments } => { println!("[工具增量 {}] {}({})", index, name, arguments); } StreamEvent::ToolCallComplete(tool_call) => { println!("[工具完成] {}({})", tool_call.name, tool_call.arguments); tool_calls.push(tool_call); } StreamEvent::Done => break, _ => {} } } // 执行所有收集到的工具调用 for tool_call in tool_calls { // 执行并返回结果... } }
多轮工具调用
#![allow(unused)] fn main() { async fn multi_round_tool_calling( client: &VllmClient, user_message: &str, max_rounds: usize, ) -> Result<String, Box<dyn std::error::Error>> { let mut messages = vec![ json!({"role": "user", "content": user_message}) ]; for _ in 0..max_rounds { let response = client.chat.completions().create() .model("Qwen/Qwen2.5-72B-Instruct") .messages(json!(&messages)) .tools(tools.clone()) .send() .await?; if response.has_tool_calls() { // 加入带工具调用的助手消息 messages.push(response.assistant_message()); // 执行工具并加入结果 if let Some(tool_calls) = &response.tool_calls { for tool_call in tool_calls { let result = execute_tool(&tool_call.name, &tool_call.arguments); messages.push(tool_call.result(result)); } } } else { // 没有更多工具调用,返回内容 return Ok(response.content.unwrap_or_default()); } } Err("超过最大轮数".into()) } }
最佳实践
清晰的工具描述
写清楚、详细的描述:
#![allow(unused)] fn main() { // 推荐 "description": "获取指定城市的当前天气状况。返回温度、湿度和天气状况。" // 避免 "description": "获取天气" }
精确的参数 Schema
定义准确的 JSON Schema:
#![allow(unused)] fn main() { "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市名称或坐标" }, "days": { "type": "integer", "minimum": 1, "maximum": 7, "description": "预报天数" } }, "required": ["location"] } }
错误处理
优雅地处理工具执行错误:
#![allow(unused)] fn main() { let tool_result = match execute_tool(&tool_call.name, &tool_call.arguments) { Ok(result) => json!({"success": true, "data": result}), Err(e) => json!({"success": false, "error": e.to_string()}), }; messages.push(tool_call.result(tool_result)); }