Appearance
工具调用与Function Calling
工具调用是Agent从"能说"到"能做"的关键桥梁
什么是Function Calling?
Function Calling(函数调用)是大模型的一项核心能力,允许模型在生成文本的同时,请求执行外部函数或工具。这使得AI能够获取实时信息、执行实际操作、与外部系统交互。
没有Function Calling:
────────────────────────────
用户:北京今天天气怎么样?
AI:根据我的训练数据,北京通常...(可能过时/不准确)
有Function Calling:
────────────────────────────
用户:北京今天天气怎么样?
AI:[调用get_weather("北京")]
→ 获取实时天气数据
→ 北京今天晴天,气温20-28℃,适合户外活动核心价值
| 价值 | 说明 | 示例 |
|---|---|---|
| 实时数据 | 获取最新信息 | 天气、股价、新闻 |
| 外部操作 | 执行实际动作 | 发送邮件、创建订单 |
| 专业计算 | 处理复杂计算 | 数学运算、数据分析 |
| 系统集成 | 连接外部服务 | 数据库查询、API调用 |
Function Calling工作原理
工作流程
Function Calling完整流程:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. 用户请求 │
│ "帮我查一下北京明天的天气" │
│ ↓ │
│ 2. 模型分析 │
│ LLM分析用户意图,判断需要调用工具 │
│ ↓ │
│ 3. 生成函数调用 │
│ { │
│ "name": "get_weather", │
│ "arguments": {"city": "北京", "date": "明天"} │
│ } │
│ ↓ │
│ 4. 执行函数 │
│ 客户端执行函数,获取结果 │
│ ↓ │
│ 5. 返回结果 │
│ {"temperature": "15-25℃", "weather": "晴天"} │
│ ↓ │
│ 6. 生成最终回复 │
│ "北京明天天气晴朗,气温15-25℃..." │
│ │
└─────────────────────────────────────────────────────────────┘关键概念
Tool Definition(工具定义)
────────────────────────────
告诉模型有哪些工具可用,以及如何使用
Tool Call(工具调用)
────────────────────────────
模型决定调用哪个工具,以及传递什么参数
Tool Result(工具结果)
────────────────────────────
执行工具后返回的结果
Tool Response(工具响应)
────────────────────────────
将结果返回给模型,模型生成最终回复工具定义规范
OpenAI格式
OpenAI定义了业界最广泛使用的Function Calling格式:
php
<?php
// 定义工具列表
$tools = [
[
// 工具类型为函数
'type' => 'function',
'function' => [
// 工具名称
'name' => 'get_weather',
// 工具描述
'description' => '获取指定城市的天气信息',
// 参数定义
'parameters' => [
'type' => 'object',
'properties' => [
// 城市参数
'city' => [
'type' => 'string',
'description' => '城市名称,如:北京、上海'
],
// 温度单位参数
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
'description' => '温度单位,默认摄氏度'
]
],
// 必填参数列表
'required' => ['city']
]
]
]
];参数类型详解
JSON Schema参数类型:
┌─────────────────────────────────────────┐
│ 类型 │ 说明 │ 示例 │
├─────────────────────────────────────────┤
│ string │ 字符串 │ "北京" │
│ number │ 数字 │ 25 │
│ integer │ 整数 │ 100 │
│ boolean │ 布尔值 │ true │
│ array │ 数组 │ [1, 2, 3] │
│ object │ 对象 │ {"a": 1} │
│ null │ 空值 │ null │
└─────────────────────────────────────────┘
特殊约束:
────────────────────────────
• enum:限定可选值
• minimum/maximum:数值范围
• minLength/maxLength:字符串长度
• pattern:正则表达式匹配
• default:默认值复杂参数定义
php
<?php
// 定义复杂工具配置
$complexTool = [
// 工具类型
'type' => 'function',
'function' => [
// 工具名称
'name' => 'search_products',
// 工具描述
'description' => '搜索商品信息',
// 参数定义
'parameters' => [
'type' => 'object',
'properties' => [
// 搜索关键词参数
'query' => [
'type' => 'string',
'description' => '搜索关键词'
],
// 过滤器参数
'filters' => [
'type' => 'object',
'properties' => [
// 商品分类
'category' => [
'type' => 'string',
'enum' => ['electronics', 'clothing', 'food']
],
// 价格范围
'price_range' => [
'type' => 'object',
'properties' => [
// 最低价格
'min' => ['type' => 'number'],
// 最高价格
'max' => ['type' => 'number']
]
],
// 品牌列表
'brands' => [
'type' => 'array',
'items' => ['type' => 'string']
]
]
],
// 排序方式
'sort' => [
'type' => 'string',
'enum' => ['price_asc', 'price_desc', 'relevance'],
'default' => 'relevance'
],
// 返回数量限制
'limit' => [
'type' => 'integer',
'minimum' => 1,
'maximum' => 100,
'default' => 10
]
],
// 必填参数
'required' => ['query']
]
]
];实现Function Calling
基础实现
php
<?php
// 引入自动加载文件
require_once __DIR__ . '/vendor/autoload.php';
use OpenAI\Client;
/**
* 天气Agent类
* 实现天气查询功能,支持Function Calling
*/
class WeatherAgent
{
// OpenAI客户端实例
private Client $client;
/**
* 构造函数
* @param Client $client OpenAI客户端实例
*/
public function __construct(Client $client)
{
// 初始化客户端
$this->client = $client;
}
/**
* 获取天气信息
* @param string $city 城市名称
* @param string $unit 温度单位
* @return string 天气信息JSON
*/
private function getWeather(string $city, string $unit = 'celsius'): string
{
// 模拟天气数据
$weatherData = [
'北京' => ['temp' => '20-28', 'condition' => '晴天'],
'上海' => ['temp' => '22-30', 'condition' => '多云'],
];
// 获取城市天气,不存在则返回未知
$data = $weatherData[$city] ?? ['temp' => '未知', 'condition' => '未知'];
// 返回JSON格式天气信息
return json_encode([
'city' => $city,
'temperature' => $data['temp'],
'condition' => $data['condition'],
'unit' => $unit
], JSON_UNESCAPED_UNICODE);
}
/**
* 获取工具定义列表
* @return array 工具定义数组
*/
private function getTools(): array
{
return [
[
// 工具类型
'type' => 'function',
'function' => [
// 工具名称
'name' => 'get_weather',
// 工具描述
'description' => '获取指定城市的天气信息',
// 参数定义
'parameters' => [
'type' => 'object',
'properties' => [
// 城市参数
'city' => [
'type' => 'string',
'description' => '城市名称'
],
// 温度单位参数
'unit' => [
'type' => 'string',
'enum' => ['celsius', 'fahrenheit'],
'description' => '温度单位'
]
],
// 必填参数
'required' => ['city']
]
]
]
];
}
/**
* 运行Agent
* @param string $userInput 用户输入
* @return string Agent响应
*/
public function run(string $userInput): string
{
// 初始化消息列表
$messages = [
['role' => 'user', 'content' => $userInput]
];
// 调用OpenAI API
$response = $this->client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages,
'tools' => $this->getTools(),
'tool_choice' => 'auto'
]);
// 获取响应消息
$message = $response->choices[0]->message;
// 检查是否有工具调用
if (isset($message->toolCalls) && count($message->toolCalls) > 0) {
// 获取第一个工具调用
$toolCall = $message->toolCalls[0];
// 获取函数名称
$functionName = $toolCall->function->name;
// 解析函数参数
$functionArgs = json_decode($toolCall->function->arguments, true);
// 判断是否为天气查询函数
if ($functionName === 'get_weather') {
// 执行天气查询
$functionResponse = $this->getWeather(
$functionArgs['city'],
$functionArgs['unit'] ?? 'celsius'
);
}
$messages[] = $message->toArray();
$messages[] = [
'tool_call_id' => $toolCall->id,
'role' => 'tool',
'name' => $functionName,
'content' => $functionResponse
];
$secondResponse = $this->client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages
]);
return $secondResponse->choices[0]->message->content;
}
return $message->content;
}
}
$client = OpenAI::factory()
->withApiKey($_ENV['OPENAI_API_KEY'])
->make();
$agent = new WeatherAgent($client);
echo $agent->run("北京今天天气怎么样?");多工具调用处理
php
<?php
class MultiToolAgent
{
private Client $client;
private array $tools;
public function __construct(Client $client, array $tools)
{
$this->client = $client;
$this->tools = $tools;
}
public function run(string $userInput): string
{
$messages = [
['role' => 'user', 'content' => $userInput]
];
while (true) {
$response = $this->client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages,
'tools' => $this->tools,
'tool_choice' => 'auto'
]);
$message = $response->choices[0]->message;
if (!isset($message->toolCalls) || count($message->toolCalls) === 0) {
return $message->content;
}
$messages[] = $message->toArray();
foreach ($message->toolCalls as $toolCall) {
$functionName = $toolCall->function->name;
$functionArgs = json_decode($toolCall->function->arguments, true);
$result = $this->executeTool($functionName, $functionArgs);
$messages[] = [
'tool_call_id' => $toolCall->id,
'role' => 'tool',
'name' => $functionName,
'content' => $result
];
}
}
}
private function executeTool(string $name, array $args): string
{
$toolFunctions = [
'get_weather' => [$this, 'getWeather'],
'search_web' => [$this, 'searchWeb'],
'calculate' => [$this, 'calculate'],
];
if (isset($toolFunctions[$name])) {
return json_encode(call_user_func($toolFunctions[$name], $args), JSON_UNESCAPED_UNICODE);
}
return json_encode(['error' => "Unknown tool: {$name}"]);
}
private function getWeather(array $args): array
{
return ['city' => $args['city'], 'temp' => '20-28'];
}
private function searchWeb(array $args): array
{
return ['query' => $args['query'], 'results' => []];
}
private function calculate(array $args): array
{
return ['expression' => $args['expression'], 'result' => 0];
}
}并行工具调用
php
<?php
use React\Promise\Promise;
class ParallelToolExecutor
{
public function executeToolsParallel(array $toolCalls): array
{
$promises = [];
foreach ($toolCalls as $toolCall) {
$promises[] = $this->executeSingleAsync($toolCall);
}
$results = [];
foreach ($promises as $promise) {
$results[] = $promise->wait();
}
return $results;
}
private function executeSingleAsync(object $toolCall): Promise
{
return new Promise(function ($resolve) use ($toolCall) {
$functionName = $toolCall->function->name;
$functionArgs = json_decode($toolCall->function->arguments, true);
$result = $this->asyncExecuteTool($functionName, $functionArgs);
$resolve([
'tool_call_id' => $toolCall->id,
'role' => 'tool',
'name' => $functionName,
'content' => $result
]);
});
}
private function asyncExecuteTool(string $name, array $args): string
{
return match ($name) {
'get_weather' => $this->asyncGetWeather($args),
'search_web' => $this->asyncSearchWeb($args),
default => json_encode(['error' => 'Unknown tool'])
};
}
}工具选择策略
tool_choice参数
php
<?php
$toolChoiceOptions = [
'auto' => '模型自动决定是否调用工具',
'none' => '模型不调用任何工具',
'required' => '模型必须调用至少一个工具',
];
$response = $client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages,
'tools' => $tools,
'tool_choice' => 'auto'
]);
$response = $client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages,
'tools' => $tools,
'tool_choice' => [
'type' => 'function',
'function' => ['name' => 'get_weather']
]
]);动态工具选择
php
<?php
class DynamicToolSelector
{
private array $allTools;
private array $toolEmbeddings;
public function __construct(array $allTools)
{
$this->allTools = $allTools;
$this->toolEmbeddings = $this->computeEmbeddings();
}
public function selectRelevantTools(string $query, int $topK = 5): array
{
$queryEmbedding = $this->embed($query);
$similarities = $this->computeSimilarities($queryEmbedding);
arsort($similarities);
$topIndices = array_slice(array_keys($similarities), 0, $topK, true);
return array_map(fn($i) => $this->allTools[$i], $topIndices);
}
private function computeEmbeddings(): array
{
$embeddings = [];
foreach ($this->allTools as $tool) {
$text = "{$tool['function']['name']}: {$tool['function']['description']}";
$embeddings[] = $this->embed($text);
}
return $embeddings;
}
}不同平台的Function Calling
OpenAI
php
<?php
$response = $client->chat()->create([
'model' => 'gpt-4',
'messages' => $messages,
'tools' => $tools,
'tool_choice' => 'auto',
'parallel_tool_calls' => true
]);Anthropic Claude
php
<?php
use Anthropic\Client;
$client = new Client();
$response = $client->messages()->create([
'model' => 'claude-3-opus-20240229',
'max_tokens' => 1024,
'tools' => [
[
'name' => 'get_weather',
'description' => '获取天气信息',
'input_schema' => [
'type' => 'object',
'properties' => [
'city' => ['type' => 'string']
],
'required' => ['city']
]
]
],
'messages' => [
['role' => 'user', 'content' => '北京天气怎么样?']
]
]);
foreach ($response->content as $block) {
if ($block['type'] === 'tool_use') {
$toolName = $block['name'];
$toolInput = $block['input'];
}
}国内大模型
php
<?php
use ZhipuAI\Client;
$client = new Client();
$response = $client->chat()->create([
'model' => 'glm-4',
'messages' => $messages,
'tools' => $tools,
'tool_choice' => 'auto'
]);
use Dashscope\Client;
$client = new Client();
$response = $client->generation()->call([
'model' => 'qwen-max',
'messages' => $messages,
'tools' => $tools,
'result_format' => 'message'
]);高级技巧
工具结果验证
php
<?php
class ToolResultValidator
{
private array $validators = [];
public function __construct()
{
$this->validators = [
'get_weather' => [$this, 'validateWeatherResult'],
'search_web' => [$this, 'validateSearchResult'],
];
}
public function validate(string $toolName, string $result): array
{
try {
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
return ['valid' => false, 'error' => 'Invalid JSON: ' . $e->getMessage()];
}
if (isset($this->validators[$toolName])) {
return call_user_func($this->validators[$toolName], $data);
}
return ['valid' => true, 'data' => $data];
}
private function validateWeatherResult(array $data): array
{
$requiredFields = ['city', 'temperature', 'condition'];
$missing = array_diff($requiredFields, array_keys($data));
if (!empty($missing)) {
return [
'valid' => false,
'error' => 'Missing fields: ' . implode(', ', $missing)
];
}
return ['valid' => true, 'data' => $data];
}
private function validateSearchResult(array $data): array
{
if (!isset($data['results']) || !is_array($data['results'])) {
return ['valid' => false, 'error' => 'Missing or invalid results array'];
}
return ['valid' => true, 'data' => $data];
}
}错误处理与重试
php
<?php
class RobustToolExecutor
{
private int $maxRetries = 3;
public function executeWithRetry(object $toolCall): string
{
for ($attempt = 0; $attempt < $this->maxRetries; $attempt++) {
try {
$result = $this->executeTool($toolCall);
$validation = (new ToolResultValidator())->validate(
$toolCall->function->name,
$result
);
if ($validation['valid']) {
return $result;
}
if ($attempt < $this->maxRetries - 1) {
$correctedArgs = $this->correctArgs(
$toolCall->function->arguments,
$validation['error']
);
$toolCall->function->arguments = $correctedArgs;
}
} catch (\Exception $e) {
if ($attempt === $this->maxRetries - 1) {
return json_encode([
'error' => $e->getMessage(),
'retries' => $attempt + 1
], JSON_UNESCAPED_UNICODE);
}
sleep(2 ** $attempt);
}
}
return json_encode(['error' => 'Max retries exceeded']);
}
}工具链式调用
php
<?php
class ToolChainExecutor
{
public function executeToolChain(array $toolCalls): string
{
$results = [];
$context = [];
foreach ($toolCalls as $toolCall) {
$args = json_decode($toolCall->function->arguments, true);
foreach ($context as $key => $value) {
if (isset($args[$key]) && $args[$key] === '$previous_result') {
$args[$key] = $value;
}
}
$result = $this->executeTool($toolCall->function->name, $args);
$results[] = $result;
$context['previous_result'] = $result;
$context["{$toolCall->function->name}_result"] = $result;
}
return end($results);
}
}最佳实践
工具定义原则
1. 描述清晰
────────────────────────────
❌ 差的描述:
"description": "获取信息"
✅ 好的描述:
"description": "获取指定城市的实时天气信息,包括温度、湿度、风速等"
2. 参数完整
────────────────────────────
• 必填参数标记为required
• 可选参数提供default值
• 使用enum限定可选值
• 提供参数示例
3. 单一职责
────────────────────────────
每个工具只做一件事:
• get_weather - 获取天气
• search_web - 搜索网页
• 不要设计get_weather_and_news这样的工具
4. 合理粒度
────────────────────────────
• 太粗:execute_task - 太泛化
• 太细:add_number - 太细碎
• 适中:calculate_expression - 合适性能优化
1. 减少工具数量
────────────────────────────
• 只提供相关工具
• 动态加载工具
• 避免工具过多导致选择困难
2. 并行调用
────────────────────────────
• 独立的工具调用并行执行
• 减少总等待时间
3. 结果缓存
────────────────────────────
• 缓存频繁调用的结果
• 设置合理的TTL
4. 流式输出
────────────────────────────
• 使用streaming模式
• 提前展示中间结果安全考虑
安全注意事项
工具调用涉及外部系统,需要特别注意安全:
python
class SecureToolExecutor:
ALLOWED_DOMAINS = ["api.example.com", "api.weather.com"]
MAX_RESULT_SIZE = 1024 * 1024
def execute_tool(self, name: str, args: dict) -> str:
if not self.is_tool_allowed(name):
raise PermissionError(f"Tool {name} is not allowed")
if not self.validate_args(name, args):
raise ValueError("Invalid arguments")
result = self.do_execute(name, args)
if len(result) > self.MAX_RESULT_SIZE:
result = result[:self.MAX_RESULT_SIZE]
self.log_tool_call(name, args, result)
return result
def is_tool_allowed(self, name: str) -> bool:
return name in self.allowed_tools
def validate_args(self, name: str, args: dict) -> bool:
if "url" in args:
return self.is_url_allowed(args["url"])
return True
def is_url_allowed(self, url: str) -> bool:
from urllib.parse import urlparse
domain = urlparse(url).netloc
return domain in self.ALLOWED_DOMAINS实战案例
智能客服工具集
python
customer_service_tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "查询订单状态",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单号"
}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "create_ticket",
"description": "创建工单",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
},
"required": ["title", "description"]
}
}
},
{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "搜索知识库",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"category": {
"type": "string",
"enum": ["product", "shipping", "returns", "payment"]
}
},
"required": ["query"]
}
}
}
]数据分析工具集
python
data_analysis_tools = [
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行SQL查询",
"parameters": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL查询语句(只允许SELECT)"
},
"database": {
"type": "string",
"description": "数据库名称"
}
},
"required": ["sql"]
}
}
},
{
"type": "function",
"function": {
"name": "generate_chart",
"description": "生成数据图表",
"parameters": {
"type": "object",
"properties": {
"data": {"type": "array"},
"chart_type": {
"type": "string",
"enum": ["bar", "line", "pie", "scatter"]
},
"title": {"type": "string"},
"x_axis": {"type": "string"},
"y_axis": {"type": "string"}
},
"required": ["data", "chart_type"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate_statistics",
"description": "计算统计指标",
"parameters": {
"type": "object",
"properties": {
"data": {"type": "array"},
"metrics": {
"type": "array",
"items": {
"type": "string",
"enum": ["mean", "median", "std", "min", "max"]
}
}
},
"required": ["data", "metrics"]
}
}
}
]关键术语速查
| 术语 | 英文 | 解释 |
|---|---|---|
| Function Calling | Function Calling | 大模型调用外部函数的能力 |
| Tool Definition | Tool Definition | 工具的定义,包括名称、描述、参数 |
| Tool Call | Tool Call | 模型发起的工具调用请求 |
| Tool Result | Tool Result | 工具执行后返回的结果 |
| JSON Schema | JSON Schema | 定义JSON数据结构的规范 |
| Parallel Tool Calls | Parallel Tool Calls | 并行调用多个工具 |
学习检验
概念理解
- Function Calling的完整工作流程是什么?
- 如何定义一个复杂的工具参数?
- tool_choice参数有哪些选项?各有什么作用?
实践任务
- 定义一个发送邮件的工具
- 实现一个支持多工具调用的对话系统
- 添加工具结果验证和错误处理
下一步学习
💡 记住:工具调用是Agent能力的核心,好的工具设计让Agent更强大、更可靠。
