Tool Use与Function Calling在Agent中的应用

前言

在大语言模型(LLM)刚刚兴起之时,模型只能”说”而不能”做”——它们能够生成精美的文字,却无法真正操纵外部世界。而Tool Use(工具使用)和Function Calling(函数调用)技术的出现,彻底改变了这一局面。通过让LLM调用外部工具,Agent获得了与现实世界交互的能力,从简单的计算器到复杂的API调用,从文件操作到数据库查询,Agent终于可以”动手”完成任务了。

本文将深入剖析Tool Use与Function Calling的核心原理,探讨如何在LangChain等框架中实现,并提供完整的Python代码示例,帮助你构建真正能够”行动”的AI Agent。

一、Tool Use与Function Calling概述

1.1 什么是Function Calling?

Function Calling(函数调用)是LLM的一种能力,使其能够:

  • 识别需要调用外部函数的场景
  • 解析用户的自然语言请求
  • 提取函数调用所需的参数
  • 返回结构化的函数调用指令
1
2
3
4
5
6
7
8
9
10
// LLM输出的Function Calling示例
{
"function_call": {
"name": "get_weather",
"arguments": {
"location": "北京",
"unit": "celsius"
}
}
}

1.2 Tool Use与Function Calling的关系

特性 Function Calling Tool Use
触发方式 模型主动识别 Agent自主决策
执行方式 同步返回结果 可能异步执行
上下文 单轮交互 多轮协作
典型框架 OpenAI API LangChain, AutoGPT

1.3 为什么Agent需要Tool Use?

  1. 扩展能力边界:访问实时信息、执行计算、操作外部系统
  2. 解决幻觉问题:通过工具验证信息,而非仅依赖模型知识
  3. 实现复杂任务:多步骤工作流、跨系统协作
  4. 增强可靠性:工具执行结果可验证、可追溯

二、Function Calling核心原理

2.1 Function Calling工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
用户请求


┌─────────────┐
│ LLM │ ◄── Function Schemas (工具定义)
│ (决策层) │
└──────┬──────┘
│ 判断需要调用工具

┌─────────────┐
│ 提取参数 │ ◄── 自然语言 → 结构化参数
└──────┬──────┘


┌─────────────┐
│ 工具执行 │ ◄── 调用外部API/函数
└──────┬──────┘


┌─────────────┐
│ 结果整合 │ ◄── 工具返回 → LLM → 用户
└─────────────┘

2.2 OpenAI Function Calling实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import json
from typing import List, Dict, Any, Optional, Callable
from dataclasses import dataclass, field
from enum import Enum
import openai
from datetime import datetime

class ToolType(Enum):
"""工具类型"""
FUNCTION = "function"
RETRIEVER = "retriever"
CODE_INTERPRETER = "code_interpreter"

@dataclass
class FunctionParameter:
"""函数参数定义"""
name: str
type: str # "string", "number", "boolean", "array", "object"
description: str = ""
required: bool = True
enum: List[str] = field(default_factory=list)
default: Any = None

@dataclass
class FunctionSchema:
"""函数模式定义"""
name: str
description: str
parameters: List[FunctionParameter] = field(default_factory=list)

def to_openai_format(self) -> Dict[str, Any]:
"""转换为OpenAI格式"""
properties = {}
required_params = []

for param in self.parameters:
param_dict = {"type": param.type}
if param.description:
param_dict["description"] = param.description
if param.enum:
param_dict["enum"] = param.enum

properties[param.name] = param_dict

if param.required:
required_params.append(param.name)

return {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": properties,
"required": required_params
}
}

class OpenAIFunctionCaller:
"""OpenAI Function Caller实现"""

def __init__(self, api_key: str, model: str = "gpt-4-turbo"):
self.client = openai.OpenAI(api_key=api_key)
self.model = model
self.tools: Dict[str, Callable] = {}
self.tool_schemas: List[FunctionSchema] = []

def register_tool(self, schema: FunctionSchema, handler: Callable):
"""注册工具"""
self.tools[schema.name] = handler
self.tool_schemas.append(schema)

def register_function(self, name: str, description: str,
parameters: List[FunctionParameter]) -> Callable:
"""装饰器:注册函数"""
def decorator(func: Callable) -> Callable:
schema = FunctionSchema(
name=name,
description=description,
parameters=parameters
)
self.register_tool(schema, func)
return func
return decorator

async def chat(self, messages: List[Dict[str, str]],
temperature: float = 0.7,
tools: Optional[List[Dict]] = None) -> Dict[str, Any]:
"""发送聊天请求"""
if tools is None:
tools = [s.to_openai_format() for s in self.tool_schemas]

response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
tools=tools,
tool_choice="auto"
)

return response

async def process_request(self, user_message: str) -> Dict[str, Any]:
"""处理用户请求,自动调用工具"""
messages = [{"role": "user", "content": user_message}]

# 第一轮:获取模型响应
response = await self.chat(messages)
assistant_message = response.choices[0].message

# 检查是否需要调用工具
if assistant_message.tool_calls:
tool_calls = assistant_message.tool_calls
messages.append({
"role": "assistant",
"content": assistant_message.content or "",
"tool_calls": [
{
"id": tc.id,
"type": tc.type,
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments
}
}
for tc in tool_calls
]
})

# 执行工具调用
tool_results = []
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)

if func_name in self.tools:
try:
result = await self.tools[func_name](**func_args)
tool_results.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": func_name,
"content": json.dumps(result, ensure_ascii=False)
})
except Exception as e:
tool_results.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": func_name,
"content": f"Error: {str(e)}"
})
else:
tool_results.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": func_name,
"content": f"Error: Unknown function {func_name}"
})

# 添加工具结果到消息
messages.extend(tool_results)

# 第二轮:生成最终响应
response = await self.chat(messages)

final_message = response.choices[0].message

return {
"content": final_message.content,
"tool_calls": assistant_message.tool_calls if hasattr(assistant_message, 'tool_calls') else None,
"usage": response.usage.model_dump() if response.usage else None
}

2.3 工具注册与使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 创建Function Caller实例
caller = OpenAIFunctionCaller(api_key="your-api-key")

# 定义天气查询工具
weather_schema = FunctionSchema(
name="get_weather",
description="获取指定城市的天气信息",
parameters=[
FunctionParameter(
name="location",
type="string",
description="城市名称,如:北京、上海"
),
FunctionParameter(
name="unit",
type="string",
description="温度单位,celsius或fahrenheit",
enum=["celsius", "fahrenheit"]
)
]
)

# 定义工具处理函数
async def get_weather(location: str, unit: str = "celsius") -> Dict:
"""模拟天气查询"""
# 实际应调用天气API
return {
"location": location,
"temperature": 25 if unit == "celsius" else 77,
"unit": unit,
"condition": "晴朗",
"humidity": 65
}

# 注册工具
caller.register_tool(weather_schema, get_weather)

# 使用装饰器注册
@caller.register_function(
name="search_news",
description="搜索新闻",
parameters=[
FunctionParameter(name="query", type="string", description="搜索关键词"),
FunctionParameter(name="limit", type="number", description="返回数量", required=False, default=5)
]
)
async def search_news(query: str, limit: int = 5) -> Dict:
"""模拟新闻搜索"""
return {
"query": query,
"results": [
{"title": f"关于{query}的新闻{i+1}", "url": f"https://news.example.com/{i}"}
for i in range(limit)
]
}

# 处理请求
async def main():
result = await caller.process_request("北京今天天气怎么样?")
print(result["content"])

三、LangChain Tool Use实战

3.1 LangChain工具定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from langchain.tools import BaseTool, StructuredTool, Tool
from langchain_core.tools import tool
from typing import Optional, Type
from pydantic import BaseModel, Field

# 方式1:使用@tool装饰器(最简洁)
@tool
def get_stock_price(symbol: str) -> str:
"""获取股票当前价格

Args:
symbol: 股票代码,如 AAPL, GOOGL

Returns:
股票价格信息
"""
# 模拟API调用
prices = {"AAPL": 178.50, "GOOGL": 142.30, "MSFT": 378.90}
price = prices.get(symbol, 0)
return f"{symbol} 当前价格: ${price}"

@tool
def calculate(compound: bool, principal: float, rate: float, time: float, frequency: int = 12) -> str:
"""计算投资回报

Args:
compound: 是否复利计算
principal: 本金金额
rate: 年利率(百分比)
time: 投资年数
frequency: 年复利次数

Returns:
计算结果
"""
if compound:
amount = principal * (1 + rate / 100 / frequency) ** (frequency * time)
else:
amount = principal * (1 + rate / 100 * time)

interest = amount - principal
return f"本金: ${principal:.2f}\n最终金额: ${amount:.2f}\n利息: ${interest:.2f}"

# 方式2:使用StructuredTool(支持复杂参数)
class CalendarToolInput(BaseModel):
"""日历工具输入"""
action: str = Field(description="操作类型: create, update, delete, query")
title: str = Field(description="事件标题")
start_time: Optional[str] = Field(None, description="开始时间 ISO格式")
end_time: Optional[str] = Field(None, description="结束时间 ISO格式")
description: Optional[str] = Field(None, description="事件描述")
attendees: Optional[list[str]] = Field(None, description="参与者邮箱列表")

class CalendarTool(BaseTool):
"""日历管理工具"""
name = "calendar"
description = "管理和查询日历事件。适用于创建会议、查询日程、更新时间等。"

args_schema: Type[BaseModel] = CalendarToolInput

def _run(self, action: str, title: str, start_time: Optional[str] = None,
end_time: Optional[str] = None, description: Optional[str] = None,
attendees: Optional[list[str]] = None) -> str:
"""同步执行"""
if action == "create":
return f"已创建事件: {title}\n时间: {start_time} - {end_time}"
elif action == "query":
return f"查询到事件: {title}"
elif action == "update":
return f"已更新事件: {title}"
elif action == "delete":
return f"已删除事件: {title}"
return "未知操作"

async def _arun(self, action: str, title: str, **kwargs) -> str:
"""异步执行"""
return self._run(action, title, **kwargs)

3.2 LangChain Agent实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from langchain.agents import AgentType, initialize_agent
from langchain.agents.agent import AgentExecutor
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.callbacks.base import BaseCallbackHandler
from typing import List, Dict, Any

class AgentCallbackHandler(BaseCallbackHandler):
"""Agent执行回调"""

def __init__(self):
self.steps: List[Dict[str, Any]] = []

def on_agent_action(self, action, **kwargs):
"""Agent执行动作时触发"""
self.steps.append({
"type": "action",
"tool": action.tool,
"input": action.tool_input,
"log": action.log
})
print(f"[Agent Action] 使用工具: {action.tool}")
print(f" 输入: {action.tool_input}")

def on_tool_end(self, output, **kwargs):
"""工具执行完成时触发"""
self.steps.append({
"type": "tool_result",
"output": str(output)[:200]
})
print(f"[Tool Result] 结果: {str(output)[:100]}...")

def on_text(self, text, **kwargs):
"""输出文本时触发"""
print(f"[Agent Text] {text}")

class ToolUsingAgent:
"""使用工具的Agent"""

def __init__(self, api_key: str):
self.llm = ChatOpenAI(
model="gpt-4-turbo",
openai_api_key=api_key,
temperature=0.7
)
self.tools = []
self.memory = ConversationBufferMemory(memory_key="chat_history")
self.callback_handler = AgentCallbackHandler()

def add_tool(self, tool):
"""添加工具"""
self.tools.append(tool)

def add_tools(self, tools: List):
"""批量添加工具"""
self.tools.extend(tools)

def create_agent(self, agent_type: AgentType = AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION):
"""创建Agent"""
return initialize_agent(
self.tools,
self.llm,
agent=agent_type,
memory=self.memory,
verbose=True,
callbacks=[self.callback_handler]
)

async def run(self, task: str) -> str:
"""运行Agent执行任务"""
self.callback_handler.steps.clear()

# 根据工具类型选择合适的Agent
has_structured_tools = any(
isinstance(t, (StructuredTool, CalendarTool))
for t in self.tools
)

if has_structured_tools:
agent = self.create_agent(AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION)
else:
agent = self.create_agent(AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION)

result = await agent.arun(task)

return {
"result": result,
"steps": self.callback_handler.steps
}

def get_conversation_history(self) -> List[Dict]:
"""获取对话历史"""
return self.memory.chat_memory.messages

3.3 完整Tool Use示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
async def demo_langchain_agent():
"""LangChain Agent完整示例"""

# 创建Agent
agent = ToolUsingAgent(api_key="your-openai-key")

# 添加工具
agent.add_tools([
get_stock_price,
calculate,
CalendarTool()
])

# 定义邮件发送工具
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""发送电子邮件

Args:
to: 收件人邮箱
subject: 邮件主题
body: 邮件正文
"""
# 模拟发送
return f"邮件已发送至 {to}\n主题: {subject}\n内容: {body[:50]}..."

agent.add_tool(send_email)

# 定义文件操作工具
@tool
def read_file(path: str, lines: int = 100) -> str:
"""读取文件内容

Args:
path: 文件路径
lines: 读取行数
"""
try:
with open(path, 'r') as f:
content = f.readlines()[:lines]
return "".join(content)
except FileNotFoundError:
return f"文件未找到: {path}"

agent.add_tool(read_file)

# 执行任务
print("=" * 60)
print("任务: 帮我查询苹果股票价格,然后计算10000美元投资5年的回报")
print("=" * 60)

result = await agent.run(
"查询苹果公司(AAPL)的股票价格,然后帮计算如果投资10000美元,"
"年利率5%,复利计算,5年后的最终金额是多少?"
)

print("\n" + "=" * 60)
print("最终结果:")
print(result["result"])
print("=" * 60)

四、自定义工具系统设计

4.1 工具注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from typing import Dict, List, Optional, Any, Callable
from dataclasses import dataclass
import json
import inspect

@dataclass
class ToolDefinition:
"""工具定义"""
name: str
description: str
parameters: Dict[str, Any]
handler: Callable
categories: List[str] = field(default_factory=list)
metadata: Dict[str, Any] = field(default_factory=dict)

class ToolRegistry:
"""工具注册中心 - 管理所有可用工具"""

def __init__(self):
self._tools: Dict[str, ToolDefinition] = {}
self._categories: Dict[str, List[str]] = {} # category -> tool_names

def register(self, name: str, description: str,
parameters: Dict[str, Any], handler: Callable,
categories: List[str] = None, metadata: Dict = None) -> Callable:
"""注册工具"""
def decorator(func: Callable) -> Callable:
tool_def = ToolDefinition(
name=name,
description=description,
parameters=parameters,
handler=func,
categories=categories or [],
metadata=metadata or {}
)
self._tools[name] = tool_def

# 更新分类索引
for cat in tool_def.categories:
if cat not in self._categories:
self._categories[cat] = []
self._categories[cat].append(name)

return func
return decorator

def get_tool(self, name: str) -> Optional[ToolDefinition]:
"""获取工具定义"""
return self._tools.get(name)

def get_all_tools(self) -> List[ToolDefinition]:
"""获取所有工具"""
return list(self._tools.values())

def get_tools_by_category(self, category: str) -> List[ToolDefinition]:
"""按分类获取工具"""
tool_names = self._categories.get(category, [])
return [self._tools[name] for name in tool_names if name in self._tools]

def search_tools(self, query: str) -> List[ToolDefinition]:
"""搜索工具"""
query_lower = query.lower()
results = []

for tool in self._tools.values():
if (query_lower in tool.name.lower() or
query_lower in tool.description.lower()):
results.append(tool)

return results

def generate_schema(self, name: str) -> Optional[Dict]:
"""生成工具的OpenAI格式schema"""
tool = self._tools.get(name)
if not tool:
return None

return {
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters
}

def list_all_schemas(self) -> List[Dict]:
"""列出所有工具的schema"""
return [self.generate_schema(name) for name in self._tools.keys()]


# 全局工具注册中心
global_tool_registry = ToolRegistry()

# 便捷装饰器
def register_tool(name: str, description: str,
parameters: Dict[str, Any],
categories: List[str] = None):
"""注册工具的便捷装饰器"""
return global_tool_registry.register(name, description, parameters, categories)

4.2 工具执行引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import asyncio
from typing import Any, Dict, List
from datetime import datetime
import json

class ToolExecutionEngine:
"""工具执行引擎"""

def __init__(self, registry: ToolRegistry):
self.registry = registry
self.execution_history: List[Dict] = []
self.tool_stats: Dict[str, Dict] = {}

async def execute(self, tool_name: str,
parameters: Dict[str, Any],
timeout: float = 30.0) -> Dict[str, Any]:
"""执行工具"""
tool = self.registry.get_tool(tool_name)
if not tool:
return {
"success": False,
"error": f"Tool not found: {tool_name}",
"available_tools": list(self.registry._tools.keys())
}

# 记录开始时间
start_time = datetime.now()

try:
# 检查是否是异步函数
if asyncio.iscoroutinefunction(tool.handler):
if timeout:
result = await asyncio.wait_for(
tool.handler(**parameters),
timeout=timeout
)
else:
result = await tool.handler(**parameters)
else:
result = tool.handler(**parameters)

execution_time = (datetime.now() - start_time).total_seconds()

# 记录执行历史
self._record_execution(tool_name, parameters, result, execution_time, True)

return {
"success": True,
"result": result,
"execution_time": execution_time,
"tool": tool_name
}

except asyncio.TimeoutError:
self._record_execution(tool_name, parameters, None, timeout, False,
error=f"Execution timeout after {timeout}s")
return {
"success": False,
"error": f"Execution timeout after {timeout}s",
"tool": tool_name
}

except Exception as e:
execution_time = (datetime.now() - start_time).total_seconds()
self._record_execution(tool_name, parameters, None, execution_time, False,
error=str(e))
return {
"success": False,
"error": str(e),
"tool": tool_name
}

async def execute_batch(self, requests: List[Dict[str, Any]],
parallel: bool = True) -> List[Dict[str, Any]]:
"""批量执行工具"""
if parallel:
tasks = [
self.execute(req["tool"], req.get("parameters", {}))
for req in requests
]
return await asyncio.gather(*tasks)
else:
results = []
for req in requests:
result = await self.execute(req["tool"], req.get("parameters", {}))
results.append(result)
return results

def _record_execution(self, tool_name: str, parameters: Dict,
result: Any, execution_time: float,
success: bool, error: str = None):
"""记录执行历史"""
record = {
"timestamp": datetime.now().isoformat(),
"tool": tool_name,
"parameters": parameters,
"result": str(result)[:500] if result else None,
"execution_time": execution_time,
"success": success,
"error": error
}

self.execution_history.append(record)

# 更新统计
if tool_name not in self.tool_stats:
self.tool_stats[tool_name] = {
"total_calls": 0,
"successful_calls": 0,
"failed_calls": 0,
"total_time": 0
}

stats = self.tool_stats[tool_name]
stats["total_calls"] += 1
if success:
stats["successful_calls"] += 1
else:
stats["failed_calls"] += 1
stats["total_time"] += execution_time

def get_stats(self) -> Dict[str, Any]:
"""获取工具使用统计"""
return {
"total_executions": len(self.execution_history),
"tool_stats": self.tool_stats,
"recent_executions": self.execution_history[-10:]
}

def get_tool_schema_for_llm(self) -> List[Dict]:
"""获取所有工具的schema,用于给LLM"""
return self.registry.list_all_schemas()

4.3 工具调用Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class ToolUsingAgent:
"""支持工具调用的Agent"""

def __init__(self, execution_engine: ToolExecutionEngine):
self.engine = execution_engine
self.conversation_history: List[Dict] = []

async def process(self, user_input: str,
llm_client: Any = None) -> Dict[str, Any]:
"""处理用户输入,决定是否调用工具"""

# 记录用户消息
self.conversation_history.append({
"role": "user",
"content": user_input,
"timestamp": datetime.now().isoformat()
})

# 获取可用工具schema
tools = self.engine.get_tool_schema_for_llm()

# 调用LLM判断是否需要工具
if llm_client:
response = await self._llm_decide(llm_client, user_input, tools)
else:
# 简单的规则匹配
response = self._rule_based_decide(user_input)

if response.get("should_use_tool"):
# 执行工具调用
tool_name = response.get("tool_name")
parameters = response.get("parameters", {})

tool_result = await self.engine.execute(tool_name, parameters)

# 记录工具调用
self.conversation_history.append({
"role": "assistant",
"content": None,
"tool_call": {
"name": tool_name,
"parameters": parameters
},
"tool_result": tool_result,
"timestamp": datetime.now().isoformat()
})

return {
"type": "tool_result",
"tool": tool_name,
"result": tool_result,
"needs_llm_response": True # 标记需要LLM生成最终回复
}

# 直接回复
return {
"type": "direct_response",
"content": response.get("content", "我理解您的问题,但无法执行具体操作。")
}

async def _llm_decide(self, llm_client, user_input: str,
tools: List[Dict]) -> Dict[str, Any]:
"""使用LLM决定是否调用工具"""

# 简化实现
prompt = f"""用户输入: {user_input}

可用工具:
{json.dumps(tools, ensure_ascii=False, indent=2)}

判断是否需要调用工具。如果需要,返回JSON格式:
{{"should_use_tool": true, "tool_name": "工具名", "parameters": {{"参数名": "参数值"}}}}。

如果不需要,返回:
{{"should_use_tool": false, "content": "回复内容"}}
"""

response = await llm_client.generate(prompt)

try:
return json.loads(response)
except:
return {"should_use_tool": False, "content": response}

def _rule_based_decide(self, user_input: str) -> Dict[str, Any]:
"""基于规则的决策"""
user_lower = user_input.lower()

# 天气查询
if "天气" in user_input or "weather" in user_lower:
return {
"should_use_tool": True,
"tool_name": "get_weather",
"parameters": {"location": "北京"}
}

# 股票查询
if "股票" in user_input or "price" in user_lower:
if "苹果" in user_input or "apple" in user_lower:
return {
"should_use_tool": True,
"tool_name": "get_stock_price",
"parameters": {"symbol": "AAPL"}
}

# 计算
if "计算" in user_input or "calculate" in user_lower:
return {
"should_use_tool": True,
"tool_name": "calculate",
"parameters": {
"compound": True,
"principal": 10000,
"rate": 5,
"time": 5
}
}

return {
"should_use_tool": False,
"content": "我收到了您的消息。"
}

五、高级工具调用模式

5.1 并行工具调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class ParallelToolCaller:
"""并行工具调用管理器"""

def __init__(self, engine: ToolExecutionEngine):
self.engine = engine

async def call_multiple(self, tool_requests: List[Dict],
strategy: str = "parallel") -> List[Dict]:
"""
并行或顺序调用多个工具

Args:
tool_requests: [{"tool": "tool_name", "parameters": {...}}, ...]
strategy: "parallel" 或 "sequential"
"""
if strategy == "parallel":
return await self._parallel_execute(tool_requests)
else:
return await self._sequential_execute(tool_requests)

async def _parallel_execute(self, requests: List[Dict]) -> List[Dict]:
"""并行执行"""
tasks = [
self.engine.execute(req["tool"], req.get("parameters", {}))
for req in requests
]
return await asyncio.gather(*tasks)

async def _sequential_execute(self, requests: List[Dict]) -> List[Dict]:
"""顺序执行"""
results = []
for req in requests:
result = await self.engine.execute(req["tool"], req.get("parameters", {}))
results.append(result)
return results

async def call_with_dependencies(self,
dependency_graph: Dict[str, List[str]]
) -> Dict[str, Dict]:
"""
根据依赖关系执行工具

dependency_graph: {tool_name: [dependent_tool_names]}
"""
executed = {}
pending = set(dependency_graph.keys())

while pending:
# 找到所有依赖都已执行的工具
ready = []
for tool in pending:
deps = dependency_graph.get(tool, [])
if all(dep in executed for dep in deps):
ready.append(tool)

if not ready:
# 死锁检测
raise RuntimeError(f"Circular dependency detected. Pending: {pending}")

# 并行执行就绪的工具
tasks = []
for tool in ready:
# 获取该工具的参数(从依赖结果中提取)
params = self._extract_params(tool, executed)
tasks.append(self.engine.execute(tool, params))

results = await asyncio.gather(*tasks)

# 更新执行状态
for tool, result in zip(ready, results):
executed[tool] = result
pending.remove(tool)

return executed

def _extract_params(self, tool: str, executed: Dict[str, Any]) -> Dict:
"""从已执行结果中提取参数"""
# 简化实现
return {}

5.2 工具调用重试与降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ResilientToolCaller:
"""带重试和降级的工具调用器"""

def __init__(self, engine: ToolExecutionEngine):
self.engine = engine
self.fallback_handlers: Dict[str, Callable] = {}

def register_fallback(self, tool_name: str, fallback: Callable):
"""注册降级处理器"""
self.fallback_handlers[tool_name] = fallback

async def execute_with_retry(self, tool_name: str,
parameters: Dict,
max_retries: int = 3,
backoff_factor: float = 1.5) -> Dict:
"""带指数退避的重试执行"""
last_error = None

for attempt in range(max_retries):
result = await self.engine.execute(tool_name, parameters)

if result["success"]:
return result

last_error = result.get("error")

# 指数退避
if attempt < max_retries - 1:
wait_time = backoff_factor ** attempt
print(f"工具执行失败,{wait_time}秒后重试... ({attempt + 1}/{max_retries})")
await asyncio.sleep(wait_time)

# 重试失败,尝试降级
if tool_name in self.fallback_handlers:
print(f"尝试使用降级处理: {tool_name}")
try:
fallback_result = await self.fallback_handlers[tool_name](parameters)
return {
"success": True,
"result": fallback_result,
"fallback": True
}
except Exception as e:
return {
"success": False,
"error": f"Fallback failed: {str(e)}"
}

return {
"success": False,
"error": f"All retries failed: {last_error}"
}

5.3 工具调用验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class ToolCallValidator:
"""工具调用验证器"""

def __init__(self, registry: ToolRegistry):
self.registry = registry

def validate_parameters(self, tool_name: str,
parameters: Dict) -> Dict[str, Any]:
"""验证参数"""
tool = self.registry.get_tool(tool_name)
if not tool:
return {
"valid": False,
"error": f"Unknown tool: {tool_name}"
}

# 获取参数定义
param_defs = tool.parameters.get("properties", {})
required = tool.parameters.get("required", [])

errors = []

# 检查必需参数
for req_param in required:
if req_param not in parameters:
errors.append(f"Missing required parameter: {req_param}")

# 检查参数类型
for param_name, param_value in parameters.items():
if param_name in param_defs:
expected_type = param_defs[param_name].get("type")
if not self._check_type(param_value, expected_type):
errors.append(
f"Invalid type for {param_name}: "
f"expected {expected_type}, got {type(param_value).__name__}"
)

# 检查枚举值
for param_name, param_value in parameters.items():
if param_name in param_defs:
enum = param_defs[param_name].get("enum")
if enum and param_value not in enum:
errors.append(
f"Invalid value for {param_name}: "
f"must be one of {enum}"
)

return {
"valid": len(errors) == 0,
"errors": errors
}

def _check_type(self, value: Any, expected_type: str) -> bool:
"""检查类型"""
type_map = {
"string": str,
"number": (int, float),
"integer": int,
"boolean": bool,
"array": list,
"object": dict
}

if expected_type not in type_map:
return True # 未知类型,跳过检查

return isinstance(value, type_map[expected_type])

def validate_tool_chain(self, tool_calls: List[Dict]) -> Dict[str, Any]:
"""验证工具调用链"""
validated = []

for i, call in enumerate(tool_calls):
tool_name = call.get("tool")
parameters = call.get("parameters", {})

validation = self.validate_parameters(tool_name, parameters)

validated.append({
"index": i,
"tool": tool_name,
"parameters": parameters,
"valid": validation["valid"],
"errors": validation.get("errors", [])
})

all_valid = all(v["valid"] for v in validated)

return {
"valid": all_valid,
"calls": validated,
"summary": f"{sum(1 for v in validated if v['valid'])}/{len(validated)} calls valid"
}

六、实际应用案例

6.1 个人助理Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class PersonalAssistantAgent:
"""个人助理Agent - 集成多种工具"""

def __init__(self, api_key: str):
# 初始化工具系统
self.registry = ToolRegistry()
self.engine = ToolExecutionEngine(self.registry)

# 初始化Agent
self.tool_caller = ToolUsingAgent(self.engine)
self.resilient_caller = ResilientToolCaller(self.engine)

# 注册所有工具
self._register_all_tools()

# 初始化LLM
self.llm = ChatOpenAI(model="gpt-4-turbo", api_key=api_key)

def _register_all_tools(self):
"""注册所有工具"""

# 日历工具
@self.registry.register(
name="create_event",
description="创建日历事件",
parameters={
"type": "object",
"properties": {
"title": {"type": "string", "description": "事件标题"},
"start_time": {"type": "string", "description": "开始时间"},
"end_time": {"type": "string", "description": "结束时间"},
"location": {"type": "string", "description": "地点"}
},
"required": ["title", "start_time"]
}
)
def create_calendar_event(title: str, start_time: str,
end_time: str = None, location: str = None) -> str:
return f"已创建日历事件: {title}\n时间: {start_time}" + \
(f" - {end_time}" if end_time else "")

# 邮件工具
@self.registry.register(
name="send_email",
description="发送电子邮件",
parameters={
"type": "object",
"properties": {
"to": {"type": "string", "description": "收件人邮箱"},
"subject": {"type": "string", "description": "邮件主题"},
"body": {"type": "string", "description": "邮件正文"}
},
"required": ["to", "subject", "body"]
}
)
async def send_email(to: str, subject: str, body: str) -> str:
# 模拟发送
return f"邮件已发送至: {to}\n主题: {subject}"

# 搜索工具
@self.registry.register(
name="web_search",
description="搜索网络信息",
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"limit": {"type": "number", "description": "结果数量", "default": 5}
},
"required": ["query"]
}
)
async def web_search(query: str, limit: int = 5) -> str:
return f"搜索 '{query}' 找到 {limit} 条结果"

# 计算器
@self.registry.register(
name="calculate",
description="执行数学计算",
parameters={
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
)
def calculate(expression: str) -> str:
try:
result = eval(expression)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"

# 文件操作
@self.registry.register(
name="read_file",
description="读取文件",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"},
"lines": {"type": "number", "description": "读取行数"}
},
"required": ["path"]
}
)
def read_file(path: str, lines: int = 100) -> str:
try:
with open(path, 'r') as f:
return f.read()[:lines * 100]
except FileNotFoundError:
return f"文件未找到: {path}"

async def chat(self, message: str) -> str:
"""处理用户消息"""
result = await self.tool_caller.process(message, self.llm)

if result["type"] == "tool_result":
# 生成最终回复
tool_result = result["result"]

# 调用LLM生成自然语言回复
response = await self._generate_response(message, tool_result)
return response

return result["content"]

async def _generate_response(self, user_message: str,
tool_result: Dict) -> str:
"""生成基于工具结果的回复"""
prompt = f"""用户消息: {user_message}

工具执行结果:
{json.dumps(tool_result, ensure_ascii=False, indent=2)}

请用自然语言向用户解释工具执行结果。
"""
response = await self.llm.agenerate([{"role": "user", "content": prompt}])
return response.generations[0].text

6.2 代码审查Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class CodeReviewAgent:
"""代码审查Agent"""

def __init__(self, api_key: str):
self.registry = ToolRegistry()
self.engine = ToolExecutionEngine(self.registry)
self._register_tools()

self.llm = ChatOpenAI(model="gpt-4-turbo", api_key=api_key)

def _register_tools(self):
"""注册代码审查相关工具"""

@self.registry.register(
name="run_linter",
description="运行代码lint检查",
parameters={
"type": "object",
"properties": {
"code": {"type": "string", "description": "代码内容"},
"language": {"type": "string", "description": "编程语言"}
},
"required": ["code"]
}
)
def run_linter(code: str, language: str = "python") -> Dict:
# 模拟lint结果
issues = []
if "print" in code and language == "python":
issues.append({"line": 1, "severity": "warning", "message": "避免使用print调试"})
if "TODO" in code:
issues.append({"line": 1, "severity": "info", "message": "存在未完成的TODO"})
return {"issues": issues, "summary": f"发现 {len(issues)} 个问题"}

@self.registry.register(
name="check_security",
description="安全检查",
parameters={
"type": "object",
"properties": {
"code": {"type": "string", "description": "代码内容"}
},
"required": ["code"]
}
)
def check_security(code: str) -> Dict:
issues = []
# 简单的安全检查
if "password" in code.lower() and "=" in code:
issues.append({"severity": "high", "message": "硬编码密码风险"})
if "eval(" in code:
issues.append({"severity": "high", "message": "使用eval存在安全风险"})
if "exec(" in code:
issues.append({"severity": "high", "message": "使用exec存在安全风险"})
return {"issues": issues, "summary": f"安全检查完成,发现 {len(issues)} 个问题"}

@self.registry.register(
name="analyze_complexity",
description="代码复杂度分析",
parameters={
"type": "object",
"properties": {
"code": {"type": "string", "description": "代码内容"}
},
"required": ["code"]
}
)
def analyze_complexity(code: str) -> Dict:
lines = code.split('\n')
return {
"lines_of_code": len([l for l in lines if l.strip() and not l.strip().startswith('#')]),
"cyclomatic_complexity": min(10, len([l for l in lines if 'if' in l or 'for' in l])),
"maintainability": "中等"
}

async def review(self, code: str, language: str = "python") -> Dict:
"""审查代码"""
# 并行运行多个检查
linter_result = await self.engine.execute("run_linter", {"code": code, "language": language})
security_result = await self.engine.execute("check_security", {"code": code})
complexity_result = await self.engine.execute("analyze_complexity", {"code": code})

# 汇总结果
summary = {
"linter": linter_result,
"security": security_result,
"complexity": complexity_result
}

# 生成审查报告
report = await self._generate_report(summary)

return report

async def _generate_report(self, summary: Dict) -> str:
"""生成审查报告"""
prompt = f"""请基于以下代码审查结果生成审查报告:

{json.dumps(summary, ensure_ascii=False, indent=2)}

请提供:
1. 总体评价
2. 发现的问题
3. 改进建议
"""
response = await self.llm.agenerate([{"role": "user", "content": prompt}])
return response.generations[0].text

七、最佳实践与注意事项

7.1 工具设计原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 好的工具设计示例
GOOD_TOOL_EXAMPLE = {
"name": "get_order_status",
"description": "查询订单状态",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单ID,通常以ORD开头"
}
},
"required": ["order_id"]
}
}

# 避免的工具设计
BAD_TOOL_EXAMPLE = {
"name": "query",
"description": "查询",
"parameters": {
"type": "object",
"properties": {
"x": {"type": "string"}
},
"required": ["x"]
}
}

# 原则:
# 1. 工具名称要清晰、具体
# 2. 描述要准确说明工具用途
# 3. 参数名要有意义
# 4. 提供参数示例或格式说明

7.2 错误处理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ToolErrorHandler:
"""工具错误处理"""

ERROR_CATEGORIES = {
"TIMEOUT": {"retry": True, "fallback": True},
"AUTH_FAILURE": {"retry": False, "fallback": False},
"VALIDATION_ERROR": {"retry": False, "fallback": True},
"RATE_LIMIT": {"retry": True, "fallback": False},
"UNKNOWN": {"retry": True, "fallback": True}
}

def categorize_error(self, error: Exception) -> str:
"""分类错误类型"""
error_str = str(error).lower()

if "timeout" in error_str:
return "TIMEOUT"
elif "auth" in error_str or "unauthorized" in error_str:
return "AUTH_FAILURE"
elif "validation" in error_str or "invalid" in error_str:
return "VALIDATION_ERROR"
elif "rate limit" in error_str or "429" in error_str:
return "RATE_LIMIT"
else:
return "UNKNOWN"

def should_retry(self, error: Exception) -> bool:
"""判断是否应该重试"""
category = self.categorize_error(error)
return self.ERROR_CATEGORIES[category].get("retry", False)

def should_fallback(self, error: Exception) -> bool:
"""判断是否应该降级"""
category = self.categorize_error(error)
return self.ERROR_CATEGORIES[category].get("fallback", False)

7.3 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 1. 工具结果缓存
class ToolResultCache:
"""工具结果缓存"""

def __init__(self, max_size: int = 1000, ttl: int = 3600):
self.cache = {}
self.max_size = max_size
self.ttl = ttl

def _make_key(self, tool_name: str, parameters: Dict) -> str:
"""生成缓存键"""
import hashlib
param_str = json.dumps(parameters, sort_keys=True)
key_str = f"{tool_name}:{param_str}"
return hashlib.md5(key_str.encode()).hexdigest()

def get(self, tool_name: str, parameters: Dict) -> Optional[Any]:
"""获取缓存结果"""
key = self._make_key(tool_name, parameters)
if key in self.cache:
value, timestamp = self.cache[key]
if time.time() - timestamp < self.ttl:
return value
del self.cache[key]
return None

def set(self, tool_name: str, parameters: Dict, result: Any):
"""设置缓存"""
if len(self.cache) >= self.max_size:
# 删除最老的条目
oldest = min(self.cache.items(), key=lambda x: x[1][1])
del self.cache[oldest[0]]

key = self._make_key(tool_name, parameters)
self.cache[key] = (result, time.time())

# 2. 工具预热
class ToolPreloader:
"""工具预热 - 提前加载常用工具"""

def __init__(self, engine: ToolExecutionEngine):
self.engine = engine
self.warmed = False

async def warm_up(self, tool_names: List[str]):
"""预热指定工具"""
# 预热不需要实际执行,只是初始化
for name in tool_names:
tool = self.engine.registry.get_tool(name)
if tool:
print(f"预热工具: {name}")

self.warmed = True

八、总结

本文系统性地介绍了Tool Use与Function Calling在Agent中的应用:

  1. 核心原理:Function Calling使LLM能够识别需要调用外部函数的场景,并提取结构化参数
  2. OpenAI实现:完整的OpenAI Function Calling集成方案
  3. LangChain实战:使用LangChain的@tool装饰器和StructuredTool定义工具
  4. 自定义工具系统:工具注册中心、执行引擎、验证器等核心组件
  5. 高级模式:并行调用、重试降级、依赖管理等
  6. 实际案例:个人助理Agent、代码审查Agent的实现

Tool Use与Function Calling是构建真正有用的AI Agent的基础。通过精心设计的工具系统,Agent可以从”只会说话”进化到”能够行动”,在各个领域发挥真正的价值。


本文代码基于Python 3.10+,需要安装openai、langchain等依赖。