第 5 章:工具与函数调用
学习目标: 理解
Tooltrait,创建自定义工具,使用ToolRegistry注册它们,并构建一个能够推理何时使用工具的 ReAct 智能体。
为什么需要工具?
LLM 可以生成文本,但无法执行操作——它们不能计算、搜索网络或读取文件。工具弥补了这一差距,为 LLM 提供了在对话中可以调用的函数。
流程如下:
用户:"347 * 891 等于多少?"
↓
LLM 思考:"我应该使用计算器工具"
↓
LLM 调用:calculator(expression="347 * 891")
↓
工具返回:"309177"
↓
LLM 回复:"347 × 891 = 309,177"
Tool Trait
MoFA 中的每个工具都实现了 mofa-kernel 中的 Tool trait:
#![allow(unused)]
fn main() {
// crates/mofa-kernel/src/agent/components/tool.rs
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> serde_json::Value; // JSON Schema
async fn execute(&self, input: ToolInput, ctx: &AgentContext) -> ToolResult;
// 带默认实现的可选方法:
fn metadata(&self) -> ToolMetadata { ToolMetadata::default() }
fn validate_input(&self, input: &ToolInput) -> AgentResult<()> { Ok(()) }
fn requires_confirmation(&self) -> bool { false }
fn to_llm_tool(&self) -> LLMTool;
}
}
关键方法:
name()— LLM 将使用的函数名(如"calculator")description()— 解释工具的功能(LLM 读取此内容来决定何时使用它)parameters_schema()— 描述预期参数的 JSON Schemaexecute()— 实际运行工具并返回结果
ToolInput 和 ToolResult
#![allow(unused)]
fn main() {
pub struct ToolInput {
pub arguments: serde_json::Value, // 来自 LLM 的 JSON 参数
pub raw_input: Option<String>, // 原始字符串输入(可选)
}
impl ToolInput {
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T>;
pub fn get_str(&self, key: &str) -> Option<&str>;
pub fn get_number(&self, key: &str) -> Option<f64>;
pub fn get_bool(&self, key: &str) -> Option<bool>;
}
pub struct ToolResult {
pub success: bool,
pub output: serde_json::Value,
pub error: Option<String>,
pub metadata: HashMap<String, String>,
}
impl ToolResult {
pub fn success(output: serde_json::Value) -> Self;
pub fn success_text(text: impl Into<String>) -> Self;
pub fn failure(error: impl Into<String>) -> Self;
}
}
构建:计算器和天气工具
让我们创建两个工具并将它们与 LLM 智能体连接。
创建新项目:
cargo new tool_agent
cd tool_agent
编辑 Cargo.toml:
[package]
name = "tool_agent"
version = "0.1.0"
edition = "2024"
[dependencies]
mofa-sdk = { path = "../../crates/mofa-sdk" }
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
serde_json = "1"
编写 src/main.rs:
use async_trait::async_trait;
use mofa_sdk::kernel::{
AgentContext, Tool, ToolInput, ToolResult, ToolMetadata, LLMTool,
};
use std::sync::Arc;
use serde_json::json;
// --- 计算器工具 ---
struct CalculatorTool;
#[async_trait]
impl Tool for CalculatorTool {
fn name(&self) -> &str {
"calculator"
}
fn description(&self) -> &str {
"计算数学表达式。支持 +、-、*、/ 和括号。"
}
fn parameters_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的数学表达式,例如 '2 + 3 * 4'"
}
},
"required": ["expression"]
})
}
async fn execute(&self, input: ToolInput, _ctx: &AgentContext) -> ToolResult {
let expr = match input.get_str("expression") {
Some(e) => e.to_string(),
None => return ToolResult::failure("缺少 'expression' 参数"),
};
// 简单计算(生产环境中请使用专业的数学解析器)
match eval_simple_expr(&expr) {
Ok(result) => ToolResult::success_text(format!("{}", result)),
Err(e) => ToolResult::failure(format!("无法计算 '{}': {}", expr, e)),
}
}
fn to_llm_tool(&self) -> LLMTool {
LLMTool {
name: self.name().to_string(),
description: self.description().to_string(),
parameters: self.parameters_schema(),
}
}
}
fn eval_simple_expr(expr: &str) -> Result<f64, String> {
let expr = expr.trim();
if let Ok(n) = expr.parse::<f64>() {
return Ok(n);
}
for op in ['+', '-', '*', '/'] {
if let Some(pos) = expr.rfind(op) {
if pos > 0 {
let left = eval_simple_expr(&expr[..pos])?;
let right = eval_simple_expr(&expr[pos + 1..])?;
return match op {
'+' => Ok(left + right),
'-' => Ok(left - right),
'*' => Ok(left * right),
'/' => {
if right == 0.0 {
Err("除以零".to_string())
} else {
Ok(left / right)
}
}
_ => unreachable!(),
};
}
}
}
Err(format!("无法解析表达式: {}", expr))
}
// --- 天气工具(模拟) ---
struct WeatherTool;
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"获取城市的当前天气。返回温度和天气状况。"
}
fn parameters_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如 '北京'"
}
},
"required": ["city"]
})
}
async fn execute(&self, input: ToolInput, _ctx: &AgentContext) -> ToolResult {
let city = match input.get_str("city") {
Some(c) => c.to_string(),
None => return ToolResult::failure("缺少 'city' 参数"),
};
// 模拟天气数据(生产环境中调用真实天气 API)
let (temp, condition) = match city.to_lowercase().as_str() {
"san francisco" | "旧金山" => (18, "多雾"),
"new york" | "纽约" => (25, "晴天"),
"london" | "伦敦" => (14, "下雨"),
"tokyo" | "东京" => (28, "潮湿"),
"beijing" | "北京" => (26, "晴天"),
_ => (22, "多云"),
};
ToolResult::success(json!({
"city": city,
"temperature_celsius": temp,
"condition": condition
}))
}
fn to_llm_tool(&self) -> LLMTool {
LLMTool {
name: self.name().to_string(),
description: self.description().to_string(),
parameters: self.parameters_schema(),
}
}
}
// --- 主函数:将工具连接到 LLM 智能体 ---
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 创建工具
let calculator = Arc::new(CalculatorTool) as Arc<dyn Tool>;
let weather = Arc::new(WeatherTool) as Arc<dyn Tool>;
println!("=== 可用工具 ===");
println!(" - {} : {}", calculator.name(), calculator.description());
println!(" - {} : {}", weather.name(), weather.description());
// 直接测试工具
let ctx = AgentContext::new("test-exec");
println!("\n=== 直接工具调用 ===");
let result = calculator
.execute(ToolInput::from_json(json!({"expression": "42 + 58"})), &ctx)
.await;
println!("calculator('42 + 58') = {:?}", result.output);
let result = weather
.execute(ToolInput::from_json(json!({"city": "东京"})), &ctx)
.await;
println!("get_weather('东京') = {}", result.output);
// 显示 LLM 工具定义(发送给 LLM API 的内容)
println!("\n=== LLM 工具定义 ===");
println!("{}", serde_json::to_string_pretty(&calculator.to_llm_tool())?);
Ok(())
}
运行它:
cargo run
ReAct 模式
MoFA 支持 ReAct(Reasoning + Acting)模式,智能体迭代地执行:
- 思考(Think) — 分析情况并规划下一步
- 行动(Act) — 调用工具收集信息或执行操作
- 观察(Observe) — 处理工具的结果
- 重复(Repeat) — 直到任务完成
这通过 MoFA 的 ReAct 模块实现。以下是使用 ReActTool trait 的方式:
#![allow(unused)]
fn main() {
use mofa_sdk::react::{ReActTool, spawn_react_actor};
#[async_trait]
impl ReActTool for CalculatorTool {
fn name(&self) -> &str { "calculator" }
fn description(&self) -> &str { "计算数学表达式" }
fn parameters_schema(&self) -> Option<serde_json::Value> {
Some(json!({
"type": "object",
"properties": {
"expression": { "type": "string" }
},
"required": ["expression"]
}))
}
async fn execute(&self, input: &str) -> Result<String, String> {
eval_simple_expr(input)
.map(|r| r.to_string())
.map_err(|e| e.to_string())
}
}
}
然后与 LLM 智能体一起使用:
#![allow(unused)]
fn main() {
let agent = LLMAgentBuilder::new()
.with_provider(Arc::new(OpenAIProvider::from_env()))
.build();
let tools: Vec<Arc<dyn ReActTool>> = vec![
Arc::new(CalculatorTool),
Arc::new(WeatherTool),
];
// ReAct actor 自动处理 思考 → 行动 → 观察 循环
let result = spawn_react_actor(
agent,
tools,
"东京的天气怎么样?将温度从摄氏度转换为华氏度。"
).await?;
println!("最终答案: {}", result);
}
架构说明: ReAct 模式在
mofa-foundation(crates/mofa-foundation/src/react/)中实现。它使用 Ractor actor 框架管理 Think/Act/Observe 循环。spawn_react_actor函数创建一个 actor 运行循环,直到 LLM 决定有足够的信息给出最终答案。参见examples/react_agent/src/main.rs获取完整示例。
工具注册表
使用 ToolRegistry 管理多个工具:
#![allow(unused)]
fn main() {
use mofa_sdk::kernel::ToolRegistry;
use mofa_sdk::agent::tools::SimpleToolRegistry;
let mut registry = SimpleToolRegistry::new();
registry.register(Arc::new(CalculatorTool))?;
registry.register(Arc::new(WeatherTool))?;
// 列出所有工具
for desc in registry.list() {
println!("{}: {}", desc.name, desc.description);
}
// 按名称执行
let result = registry.execute(
"calculator",
ToolInput::from_json(json!({"expression": "100 / 4"})),
&ctx
).await?;
}
内置工具
MoFA 在 mofa-plugins 中提供了多个内置工具:
#![allow(unused)]
fn main() {
use mofa_sdk::plugins::tools::create_builtin_tool_plugin;
// 创建包含 HTTP、文件系统、Shell、计算器工具的插件
let mut tool_plugin = create_builtin_tool_plugin("my_tools")?;
tool_plugin.init_plugin().await?;
}
包括:
- HTTP 工具:发起网络请求
- 文件系统工具:读写文件
- Shell 工具:执行命令
- 计算器工具:计算表达式
关键要点
- 工具赋予 LLM 超越文本生成的行动能力
Tooltrait 需要:name、description、parameters_schema、executeToolInput提供类型化访问器(get_str、get_number、get_bool)ToolResult::success()/ToolResult::failure()用于返回值- ReAct 模式自动化 思考 → 行动 → 观察 循环
SimpleToolRegistry管理工具集合- 内置工具(HTTP、文件系统、Shell、计算器)在
mofa-plugins中可用
下一章: 第 6 章:多智能体协调 — 编排多个智能体协同工作。
English | 简体中文