Skip to main content

One post tagged with "AI Agent"

View All Tags

目录

  1. 引言
  2. 大模型工具调用基础原理
  3. 工具调用流程详解
  4. Rust 实现案例分析
  5. 代码架构设计
  6. 最佳实践与优化
  7. 总结

引言

随着大语言模型(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 从纯文本生成器转变为能够与现实世界交互的智能代理。

核心优势

  1. 实时性:获取最新的数据和信息
  2. 准确性:避免模型幻觉,提供可靠的事实信息
  3. 扩展性:通过工具扩展模型能力边界
  4. 可控性:明确的函数调用过程,便于调试和监控

工具调用流程详解

完整流程图

---
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: "你是一个专业的天气顾问,请根据获取到的天气数据给出详细的穿衣建议".to_string(),
})
})
});
tools.insert("get_weather".to_string(), weather_handler);

// 注册搜索工具
let search_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 query = args["query"].as_str().unwrap();

let search_results = serper::search(query).await?;
let formatted_results = serper::format_results(&search_results, 3);

Ok(ToolResult {
content: formatted_results,
system_prompt: "你是一个专业的信息分析师,请根据搜索结果给出准确、简洁的回答".to_string(),
})
})
});
tools.insert("search".to_string(), search_handler);

Self { tools }
}
}

// 统一的工具调用处理函数
pub async fn handle_tool_call(call: &Value) -> Result<ToolResult, Box<dyn std::error::Error>> {
let tool_name = call["function"]["name"].as_str().ok_or("无效的工具名称")?;
let registry = get_registry();

match registry.get_handler(tool_name) {
Some(handler) => handler(call).await,
None => Err(format!("未知的工具调用: {}", tool_name).into()),
}
}

3. 天气查询工具实现

pub async fn get_weather(location: &str, api_key: &str) -> Result<WeatherResponse, reqwest::Error> {
// 第一步:获取城市行政编码
let district_url = format!(
"https://restapi.amap.com/v3/config/district?key={}&keywords={}&subdistrict=0&extensions=all",
api_key, location
);

let district_resp: DistrictResponse = reqwest::get(&district_url).await?.json().await?;

if district_resp.status != "1" || district_resp.districts.is_empty() {
return Err(reqwest::get("http://error").await.unwrap_err());
}

let adcode = &district_resp.districts[0].adcode;

// 第二步:获取天气数据
let weather_url = format!(
"https://restapi.amap.com/v3/weather/weatherInfo?key={}&city={}&extensions=all&output=json",
api_key, adcode
);

let weather_resp: WeatherResponse = reqwest::get(&weather_url).await?.json().await?;

if weather_resp.status != "1" {
return Err(reqwest::get("http://error").await.unwrap_err());
}

Ok(weather_resp)
}

4. 搜索工具实现

pub async fn search(query: &str) -> Result<Vec<SearchResult>, reqwest::Error> {
let api_key = env::var("SERPER_API_KEY").expect("SERPER_API_KEY must be set");
let client = reqwest::Client::new();

let response = client
.post("https://google.serper.dev/search")
.header("X-API-KEY", api_key)
.header("Content-Type", "application/json")
.json(&serde_json::json!({
"q": query,
"type": "search"
}))
.send()
.await?
.json::<SearchResponse>()
.await?;

let results = response
.organic
.into_iter()
.map(|result| SearchResult {
title: result.title,
link: result.link,
snippet: result.snippet,
})
.collect();

Ok(results)
}

pub fn format_results(results: &[SearchResult], max_results: usize) -> String {
let results = results
.iter()
.take(max_results)
.enumerate()
.map(|(i, result)| {
format!(
"{}. {}\n 链接: {}\n 摘要: {}\n",
i + 1,
result.title,
result.link,
result.snippet
)
})
.collect::<Vec<_>>()
.join("\n");

if results.is_empty() {
"没有找到相关结果。".to_string()
} else {
results
}
}

代码架构设计

设计原则

我们的实现遵循以下软件设计原则:

  1. 高内聚:相关功能集中在同一模块中
  2. 低耦合:模块间依赖关系最小化
  3. 模块化:清晰的模块边界和职责分离
  4. 可扩展性:易于添加新的工具和功能

模块职责划分

  • main.rs:负责主程序逻辑和 LLM 交互
  • tools/mod.rs:工具注册表和统一接口
  • tools/amap.rs:天气查询功能实现
  • tools/serper.rs:搜索功能实现

异步编程模式

项目大量使用 Rust 的异步编程特性:

// 使用 tokio 运行时
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 异步函数调用
chat(user_query).await?;
Ok(())
}

// 异步工具处理函数
type ToolHandler = Box<dyn Fn(&Value) -> Pin<Box<dyn Future<Output = Result<ToolResult, Box<dyn std::error::Error>>> + Send + Sync>> + Send + Sync>;

最佳实践与优化

1. 错误处理

使用 Rust 的 Result 类型进行优雅的错误处理:

pub async fn handle_tool_call(call: &Value) -> Result<ToolResult, Box<dyn std::error::Error>> {
let tool_name = call["function"]["name"].as_str().ok_or("无效的工具名称")?;
// ... 其他逻辑
}

2. 环境变量管理

使用 dotenv 管理敏感配置:

use dotenv::dotenv;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok();
let api_key = env::var("DEEPSEEK_API_KEY").unwrap();
// ...
}

3. 日志记录

使用 tracing 进行结构化日志记录:

use tracing::{Level, info};

info!("用户查询: {}", user_query);
info!("工具调用参数: {}", serde_json::to_string_pretty(&args).unwrap());

4. 性能优化

  • 使用连接池复用 HTTP 连接
  • 实现请求缓存机制
  • 并发处理多个工具调用

5. 安全考虑

  • API 密钥安全存储
  • 输入参数验证
  • 请求频率限制

扩展功能建议

1. 添加更多工具

// 数据库查询工具
let db_handler: ToolHandler = Box::new(|call: &Value| {
// 数据库查询逻辑
});

// 文件操作工具
let file_handler: ToolHandler = Box::new(|call: &Value| {
// 文件操作逻辑
});

2. 实现工具链

支持多个工具的串联调用,实现复杂的业务流程。

3. 添加缓存机制

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

struct CacheManager {
cache: Arc<Mutex<HashMap<String, String>>>,
}

4. 监控和指标

添加工具调用的性能监控和成功率统计。

总结

大模型工具调用是构建智能 AI 应用的关键技术。通过本文的详细分析,我们了解了:

  1. 工具调用的基本原理:从用户请求到最终回答的完整流程
  2. 实现架构设计:模块化、可扩展的代码组织方式
  3. 具体代码实现:使用 Rust 构建高性能的工具调用系统
  4. 最佳实践:错误处理、日志记录、性能优化等方面的建议

这种架构不仅提供了良好的代码组织结构,还为后续功能扩展奠定了坚实基础。随着大模型技术的不断发展,工具调用将成为构建复杂 AI 应用的标准模式。

相关资源


本文展示了如何使用 Rust 构建支持工具调用的大模型应用。如果您对实现细节有疑问,欢迎查看完整的项目源码或提出 Issue 讨论。

鱼雪