大模型'手脚'延伸:深入解析AI工具调用与智能体进化之路
鱼雪的AI博客-来自NotebookLM
0:000:00
目录
引言
随着大语言模型(LLM)技术的快速发展,单纯的文本生成已经无法满足复杂应用场景的需求。**工具调用(Tool Calling)**作为一种重要的扩展机制,让大模型能够与外部系统交互,获取实时数据,执行特定任务,从而构建更加智能和实用的 AI 应用。
本文将深入探讨大模型工具调用的核心原理,并通过一个完整的 Rust 项目案例,详细讲解如何实现一个支持天气查询和网络搜索的智能助手,使用原生的HTTP调用方式,不使用任何第三方LLM服务的客户端,比如Python的openai库,这样便于对原理有更清晰的理解,也便于根据任何编程语言开发自己工具,而不局限于特定库。
大模型工具调用算是LLM中一种动态获取,或说实时获取与用户提问相关数据的一种方式,为用户的问题提供可靠的上下文。另外一种方式基于RAG的方式,这种是基于知识库,更像是一种静态知识。之后会用别的文章再来讨论,敬请期待。
关键词
- 大语言模型 (Large Language Model, LLM)
- 工具调用 (Tool Calling)
- Function Calling
- AI Agent
- Rust 异步编程
- DeepSeek API
大模型工具调用基础原理
什么是工具调用?
LLM 工具调用是指大语言模型在生成回答时,能够识别用户需求并主动调用预定义的外部函数或 API 来获取信息或执行操作的能力。这种机制让 LLM 从纯文本生成器转变为能够与现实世界交互的智能代理。
核心优势
- 实时性:获取最新的数据和信息
- 准确性:避免模型幻觉,提供可靠的事实信息
- 扩展性:通过工具扩展模型能力边界
- 可控性:明确的函数调用过程,便于调试和监控
工具调用流程详解
完整流程图
---
id: f662e516-56f0-46a1-a622-1438ab60949c
---
sequenceDiagram
participant User as 用户
participant App as 应用程序
participant LLM as 大语言模型
participant Tool as 外部工具/API
User->>App: 发送查询请求
App->>LLM: 用户请求 + 工具函数描述(JSON对象)
LLM->>App: 选 择的工具函数 + 工具调用参数
App->>Tool: 匹配实际工具函数填充参数并调用
Tool->>App: 获取实际工具函数执行结果
App->>LLM: 用户请求 + 工具函数执行结果
LLM->>App: 生成最终回答
App->>User: 返回完整回答
详细步骤分析
1. 初始请求阶段
用户向系统发送查询请求,应用程序将请求连同预定义的函数工具描述一起发送给大模型。
{
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "你是一个专业的助手,可以提供天气信息和搜索功能"
},
{
"role": "user",
"content": "今天上海的天气怎么样?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气预报信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称"
}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}
2. 模型决策阶段
大模型分析用户请求,识别(根据工具的描述文档,然后自行决定调用需要调用哪些工具)出需要调用天气查询工具,并返回结构化的工具 调用指令(函数名 + 与之匹配的参数):
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"上海\"}"
}
}
]
}
}
]
}
3. 工具执行阶段
应用程序解析工具调用指令,执行 实际的 API 调用:
// 解析工具调用参数
let args: Value = serde_json::from_str(
call["function"]["arguments"].as_str().unwrap()
)?;
let location = args["location"].as_str().unwrap();
// 调用实际的天气 API
let weather_info = amap::get_weather(location, &amap_key).await?;
4. 结果整合阶段
将工具执行结果反馈给大模型,生成最终的用户友好回答:
{
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "你是一个专业的天气顾问,请根据天气数据给出详细建议"
},
{
"role": "assistant",
"content": null,
"tool_calls": [...]
},
{
"role": "tool",
"content": "天气数据JSON",
"tool_call_id": "call_123"
}
]
}
Rust 实现案例分析
项目架构概览
我们的项目采用模块化设计,主要包含以下组件:
src/
├── main.rs # 主程序入口和聊天逻辑
├── tools/ # 工具模块
│ ├── mod.rs # 工具注册和统一接口
│ ├── amap.rs # 高德天气 API 工具
│ └── serper.rs # Google 搜索 API 工具
核心代码实现
1. 主聊天逻辑
async fn chat(user_query: &str) -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("DEEPSEEK_API_KEY").unwrap();
let endpoint = env::var("DEEPSEEK_API_URL").unwrap();
// 构建包含工具定义的请求
let body = json!({
"model": env::var("MODEL_NAME").unwrap(),
"messages": [
{
"role": "system",
"content": "你是一个专业的助手,可以提供天气信息和搜索功能"
},
{"role": "user", "content": user_query}
],
"tools": get_tools_definition(),
"tool_choice": "auto", // 使用`auto`让大模型自行决定使用哪些工具
});
// 发送初始请求
let response = reqwest::Client::new()
.post(&endpoint)
.header("Authorization", format!("Bearer {}", api_key))
.json(&body)
.send()
.await?
.json::<Value>()
.await?;
// 处理工具调用
if let Some(tool_calls) = response["choices"][0]["message"]["tool_calls"].as_array() {
for call in tool_calls {
let tool_result = handle_tool_call(call).await?;
// 将结果反馈给 模型生成最终回答
let final_response = generate_final_response(&api_key, &endpoint, call, &tool_result).await?;
info!("最终回答: {}", final_response["choices"][0]["message"]["content"]);
}
}
Ok(())
}
2. 工具注册系统
为了实现高内聚、低耦合的设计,我们创建了一个统一的工具注册系统:
// 工具处理结果结构
pub struct ToolResult {
pub content: String,
pub system_prompt: String,
}
// 工具处理函数类型
type ToolHandler = Box<dyn Fn(&Value) -> Pin<Box<dyn Future<Output = Result<ToolResult, Box<dyn std::error::Error>>> + Send + Sync>> + Send + Sync>;
// 工具注册表
struct ToolRegistry {
tools: HashMap<String, ToolHandler>,
}
impl ToolRegistry {
fn new() -> Self {
let mut tools = HashMap::new();
// 注册天气查询工具
let weather_handler: ToolHandler = Box::new(|call: &Value| {
let call = call.clone();
Box::pin(async move {
let args: Value = serde_json::from_str(call["function"]["arguments"].as_str().unwrap())?;
let location = args["location"].as_str().unwrap();
let amap_key = env::var("AMAP_API_KEY").unwrap();
let weather_info = amap::get_weather(location, &amap_key).await?;
Ok(ToolResult {
content: serde_json::to_string(&weather_info)?,
system_prompt: "