🛠️Function Calling | 工具注册 + 智能选择 + 结果整合 | LangChain Tools实战 | 完整项目代码
📖 为什么需要工具调用?
LLM的局限性
# 纯LLM - 只能基于训练数据回答 llm = ChatOpenAI(model="gpt-4") response = llm.invoke("今天北京的天气怎么样?") # 输出: "抱歉,我无法获取实时天气信息" ❌ response = llm.invoke("计算 12345 * 67890") # 输出: 可能计算错误 ❌ response = llm.invoke("查询张三的数据库记录") # 输出: "我无法访问你的数据库" ❌问题:
- ❌ 无法获取实时信息
- ❌ 数学计算可能出错
- ❌ 无法访问外部系统
- ❌ 无法执行实际操作
工具调用的优势
# Agent + 工具 - 可以调用外部能力 agent = create_agent(llm, tools=[ weather_tool, # 天气查询 calculator_tool, # 精确计算 database_tool # 数据库访问 ]) response = agent.run("今天北京的天气怎么样?") # 自动调用weather_tool → "北京今天晴,25°C" ✅ response = agent.run("计算 12345 * 67890") # 自动调用calculator_tool → "838102050" ✅ response = agent.run("查询张三的信息") # 自动调用database_tool → "张三,30岁,工程师" ✅优势:
- ✅ 获取实时数据
- ✅ 精确计算
- ✅ 访问外部系统
- ✅ 执行实际操作
🏗️ 工具调用架构
核心流程
用户输入 │ ▼ ┌──────────────┐ │ LLM分析 │ ← 理解用户需求,判断是否需要工具 └──────┬───────┘ │ ├─ 需要工具 ──→ ┌──────────────┐ │ │ 工具选择 │ ← 从可用工具中选择 │ └──────┬───────┘ │ │ │ ▼ │ ┌──────────────┐ │ │ 工具执行 │ ← 调用工具函数 │ └──────┬───────┘ │ │ │ ▼ │ ┌──────────────┐ │ │ 结果整合 │ ← LLM整合工具结果 │ └──────┬───────┘ │ │ └─ 不需要工具 ────────┐│ ▼▼ ┌──────────────┐ │ 最终回复 │ └──────────────┘工具调用模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 单次调用 | 调用一个工具后直接回复 | 简单查询 |
| 多次调用 | 连续调用多个工具 | 复杂任务 |
| 并行调用 | 同时调用多个独立工具 | 提高效率 |
| 条件调用 | 根据前一个结果决定下一步 | 决策流程 |
🛠️ 核心技术实现
方案1:LangChain Tools基础用法
安装依赖
pip install langchain langchain-openai1. 自定义工具
from langchain.tools import tool @tool def calculate(expression: str) -> str: """计算数学表达式 Args: expression: 数学表达式,如 "2 + 2" 或 "12345 * 67890" Returns: 计算结果 """ try: # 安全计算(只允许基本运算) result = eval(expression, {"__builtins__": {}}, {}) return f"计算结果: {result}" except Exception as e: return f"计算错误: {str(e)}" @tool def get_weather(city: str) -> str: """查询城市天气 Args: city: 城市名称,如 "北京" 或 "上海" Returns: 天气信息 """ # 模拟天气API weather_data = { "北京": "晴,25°C,空气质量优", "上海": "多云,28°C,湿度65%", "广州": "小雨,30°C,湿度80%" } return weather_data.get(city, f"未找到{city}的天气信息") @tool def search_database(query: str) -> str: """搜索数据库记录 Args: query: 搜索关键词 Returns: 搜索结果 """ # 模拟数据库 database = { "张三": {"age": 30, "occupation": "工程师", "city": "北京"}, "李四": {"age": 25, "occupation": "设计师", "city": "上海"}, "王五": {"age": 35, "occupation": "产品经理", "city": "广州"} } if query in database: info = database[query] return f"{query}的信息:年龄{info['age']}岁,职业{info['occupation']},所在城市{info['city']}" else: return f"未找到'{query}'的记录" # 创建工具列表 tools = [calculate, get_weather, search_database]2. 创建Agent
from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate # 创建LLM llm = ChatOpenAI(model="gpt-4", temperature=0) # 创建提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", """你是一个有用的助手,可以使用各种工具来帮助用户。 可用的工具: - calculate: 计算数学表达式 - get_weather: 查询城市天气 - search_database: 搜索数据库记录 当用户的问题需要这些信息时,请使用相应的工具。"""), ("human", "{input}"), ("placeholder", "{agent_scratchpad}") ]) # 创建Agent agent = create_tool_calling_agent(llm, tools, prompt) # 创建执行器 agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, # 显示详细过程 handle_parsing_errors=True ) # 使用Agent result = agent_executor.invoke({ "input": "今天北京的天气怎么样?" }) print(result["output"]) # 输出: 北京今天晴,25°C,空气质量优 result = agent_executor.invoke({ "input": "计算 12345 乘以 67890" }) print(result["output"]) # 输出: 计算结果: 838102050 result = agent_executor.invoke({ "input": "帮我查一下张三的信息" }) print(result["output"]) # 输出: 张三的信息:年龄30岁,职业工程师,所在城市北京方案2:内置工具集
常用工具
from langchain_community.tools import ( DuckDuckGoSearchRun, # 网络搜索 WikipediaQueryRun, # 维基百科 ArxivQueryRun, # 学术论文 ShellTool, # 命令行执行 PythonREPLTool, # Python代码执行 ) # 网络搜索工具 search_tool = DuckDuckGoSearchRun() result = search_tool.run("Python最新版本") print(result) # 维基百科工具 wiki_tool = WikipediaQueryRun() result = wiki_tool.run("人工智能") print(result) # Python执行工具 python_tool = PythonREPLTool() result = python_tool.run("print(2 + 2)") print(result) # 输出: 4组合使用
# 创建强大的Agent tools = [ DuckDuckGoSearchRun(), WikipediaQueryRun(), PythonREPLTool(), calculate, get_weather ] agent = create_tool_calling_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # 复杂查询 result = agent_executor.invoke({ "input": "搜索Python的最新版本,然后计算版本号中的数字之和" })方案3:自定义工具类
高级工具实现
from langchain.tools import BaseTool from typing import Type from pydantic import BaseModel, Field class WeatherInput(BaseModel): """天气查询输入""" city: str = Field(description="城市名称,如'北京'、'上海'") date: str = Field(description="日期,格式YYYY-MM-DD,默认为今天", default="today") class WeatherTool(BaseTool): """天气查询工具""" name: str = "get_weather" description: str = "查询指定城市的天气信息" args_schema: Type[BaseModel] = WeatherInput def _run(self, city: str, date: str = "today") -> str: """同步执行""" # 这里可以调用真实的天气API weather_data = { "北京": {"temp": 25, "condition": "晴", "humidity": 40}, "上海": {"temp": 28, "condition": "多云", "humidity": 65}, "广州": {"temp": 30, "condition": "小雨", "humidity": 80} } if city in weather_data: data = weather_data[city] return f"{city}{date}天气:{data['condition']},温度{data['temp']}°C,湿度{data['humidity']}%" else: return f"未找到{city}的天气信息" async def _arun(self, city: str, date: str = "today") -> str: """异步执行""" return self._run(city, date) class DatabaseTool(BaseTool): """数据库查询工具""" name: str = "query_database" description: str = "查询数据库中的用户信息" def _run(self, user_name: str) -> str: """查询用户信息""" # 模拟数据库连接 database = { "张三": {"age": 30, "occupation": "工程师", "salary": 20000}, "李四": {"age": 25, "occupation": "设计师", "salary": 15000}, } if user_name in database: info = database[user_name] return f"用户{user_name}:年龄{info['age']},职业{info['occupation']},薪资{info['salary']}元" else: return f"未找到用户'{user_name}'" async def _arun(self, user_name: str) -> str: return self._run(user_name) # 使用自定义工具 tools = [WeatherTool(), DatabaseTool()]方案4:工具路由与优化
智能工具选择
from typing import List, Dict class ToolRouter: """工具路由器 - 智能选择最合适的工具""" def __init__(self, tools: List[BaseTool]): self.tools = tools self.tool_index = {tool.name: tool for tool in tools} # 工具关键词索引(用于快速匹配) self.keyword_index = {} for tool in tools: keywords = self._extract_keywords(tool.description) for keyword in keywords: if keyword not in self.keyword_index: self.keyword_index[keyword] = [] self.keyword_index[keyword].append(tool.name) def select_tools(self, query: str, top_k: int = 3) -> List[str]: """根据查询选择相关工具 Args: query: 用户查询 top_k: 返回工具数量 Returns: 工具名称列表 """ # 提取查询关键词 query_keywords = self._extract_keywords(query) # 计算工具相关性得分 tool_scores = {} for keyword in query_keywords: if keyword in self.keyword_index: for tool_name in self.keyword_index[keyword]: tool_scores[tool_name] = tool_scores.get(tool_name, 0) + 1 # 按得分排序 sorted_tools = sorted(tool_scores.items(), key=lambda x: x[1], reverse=True) return [tool_name for tool_name, score in sorted_tools[:top_k]] def _extract_keywords(self, text: str) -> List[str]: """提取关键词(简化版)""" # 实际应用中可以使用NLP技术 keywords = ["天气", "计算", "搜索", "查询", "数据库", "用户"] return [kw for kw in keywords if kw in text] # 使用示例 router = ToolRouter([WeatherTool(), DatabaseTool(), calculate]) relevant_tools = router.select_tools("北京今天的天气怎么样?") print(relevant_tools) # 输出: ['get_weather']工具缓存
from functools import lru_cache import time class CachedTool(BaseTool): """带缓存的工具""" def __init__(self, base_tool: BaseTool, cache_ttl: int = 3600): self.base_tool = base_tool self.cache_ttl = cache_ttl self.cache = {} self.cache_timestamps = {} def _run(self, *args, **kwargs) -> str: """执行工具(带缓存)""" # 生成缓存键 cache_key = str((args, kwargs)) # 检查缓存 if cache_key in self.cache: timestamp = self.cache_timestamps[cache_key] if time.time() - timestamp < self.cache_ttl: print(f"✅ 使用缓存结果") return self.cache[cache_key] # 执行工具 result = self.base_tool._run(*args, **kwargs) # 更新缓存 self.cache[cache_key] = result self.cache_timestamps[cache_key] = time.time() return result🎯 实战案例:智能研究助手
系统架构
用户提问 │ ▼ ┌──────────────┐ │ 意图识别 │ ← 判断需要什么类型的信息 └──────┬───────┘ │ ├─ 事实查询 ──→ 搜索引擎 ├─ 学术问题 ──→ 论文数据库 ├─ 计算问题 ──→ 计算器 ├─ 代码问题 ──→ Python执行器 └─ 其他 ─────→ LLM直接回答 │ ▼ ┌──────────────┐ │ 结果整合 │ ← 综合多个来源 └──────┬───────┘ │ ▼ 最终答案完整实现
from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate from langchain_community.tools import DuckDuckGoSearchRun from langchain.tools import tool # ==================== 定义工具 ==================== @tool def calculate_expression(expression: str) -> str: """计算数学表达式""" try: result = eval(expression, {"__builtins__": {}}, {}) return str(result) except: return "计算错误" @tool def search_web(query: str) -> str: """搜索网络信息""" search = DuckDuckGoSearchRun() return search.run(query) @tool def summarize_text(text: str, max_length: int = 200) -> str: """总结文本内容""" # 简单总结(实际应用可以用LLM) words = text.split() if len(words) > max_length: return " ".join(words[:max_length]) + "..." return text # ==================== 创建Agent ==================== def create_research_assistant(): """创建智能研究助手""" llm = ChatOpenAI(model="gpt-4", temperature=0) tools = [ calculate_expression, search_web, summarize_text ] prompt = ChatPromptTemplate.from_messages([ ("system", """你是一个智能研究助手,可以帮助用户: 1. 搜索网络信息(使用search_web) 2. 进行数学计算(使用calculate_expression) 3. 总结长文本(使用summarize_text) 根据用户的需求选择合适的工具。如果一个问题需要多个步骤, 可以依次调用多个工具。 回答要准确、简洁、有用。"""), ("human", "{input}"), ("placeholder", "{agent_scratchpad}") ]) agent = create_tool_calling_agent(llm, tools, prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, max_iterations=5, # 最大工具调用次数 handle_parsing_errors=True ) return agent_executor # ==================== 使用示例 ==================== def example_research_assistant(): """研究助手使用示例""" print("="*60) print("智能研究助手演示") print("="*60) assistant = create_research_assistant() # 测试用例 test_cases = [ "计算 2的10次方是多少?", "搜索Python的最新版本信息", "帮我总结一下:人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器。人工智能的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。" ] for i, query in enumerate(test_cases, 1): print(f"\n[问题 {i}]") print(f"👤 用户: {query}") print("-" * 60) result = assistant.invoke({"input": query}) print(f"🤖 AI: {result['output']}") print("="*60) if __name__ == "__main__": example_research_assistant()📊 性能优化技巧
1. 并行工具调用
import asyncio async def parallel_tool_calls(agent_executor, queries: List[str]): """并行执行多个查询""" tasks = [ agent_executor.ainvoke({"input": query}) for query in queries ] results = await asyncio.gather(*tasks) return results # 使用 queries = [ "北京的天气", "上海的天气", "广州的天气" ] results = asyncio.run(parallel_tool_calls(agent_executor, queries))2. 工具预筛选
def prefilter_tools(query: str, all_tools: List[BaseTool]) -> List[BaseTool]: """预筛选相关工具,减少LLM负担""" # 基于关键词快速过滤 relevant_keywords = { "天气": ["get_weather"], "计算": ["calculate"], "搜索": ["search_web"], "代码": ["python_repl"] } selected_tools = set() for keyword, tool_names in relevant_keywords.items(): if keyword in query: selected_tools.update(tool_names) # 返回匹配的工具 return [t for t in all_tools if t.name in selected_tools]3. 结果缓存
class ToolCache: """工具结果缓存""" def __init__(self, max_size: int = 1000): self.cache = {} self.max_size = max_size def get(self, tool_name: str, args: tuple) -> Optional[str]: """获取缓存""" key = (tool_name, args) return self.cache.get(key) def set(self, tool_name: str, args: tuple, result: str): """设置缓存""" key = (tool_name, args) # LRU策略 if len(self.cache) >= self.max_size: oldest_key = next(iter(self.cache)) del self.cache[oldest_key] self.cache[key] = result💡 最佳实践总结
1. 工具设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 单一职责 | 每个工具只做一件事 | 天气工具只查天气 |
| 清晰描述 | description要详细说明用途 | "查询城市天气,返回温度和状况" |
| 类型安全 | 使用Pydantic定义输入 | args_schema = WeatherInput |
| 错误处理 | 优雅处理异常情况 | 返回友好错误信息 |
| 幂等性 | 相同输入产生相同输出 | 避免副作用 |
2. 工具选择策略
# 好的工具描述 @tool def get_stock_price(stock_symbol: str) -> str: """获取股票实时价格。 参数: stock_symbol: 股票代码,如'AAPL'、'GOOGL' 返回: 股票的当前价格和涨跌幅 """ pass # 不好的工具描述 @tool def stock(x): """股票""" pass3. 调试技巧
# 启用verbose查看详细过程 agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True # 显示每一步 ) # 查看中间步骤 result = agent_executor.invoke({"input": query}) print(result["intermediate_steps"]) # 工具调用历史🔍 常见问题解答
Q1: 如何防止工具被滥用?
A:
# 1. 限制调用次数 agent_executor = AgentExecutor( agent=agent, tools=tools, max_iterations=3 # 最多调用3次 ) # 2. 权限控制 def safe_execute(tool, args): if not check_permission(tool.name): raise PermissionError("无权使用该工具") return tool.run(args) # 3. 输入验证 def validate_input(tool_name, args): if tool_name == "shell": dangerous_commands = ["rm", "sudo", "dd"] if any(cmd in args for cmd in dangerous_commands): raise ValueError("禁止执行危险命令")Q2: 如何处理工具调用失败?
A:
# 1. 重试机制 for attempt in range(3): try: result = tool.run(args) break except Exception as e: if attempt == 2: result = f"工具调用失败: {str(e)}" # 2. 降级策略 try: result = premium_api.query(args) except: result = basic_api.query(args) # 降级到基础API # 3. 错误传递给LLM agent_executor = AgentExecutor( handle_parsing_errors=True # 自动处理解析错误 )Q3: 如何评估工具调用的效果?
A:
# 关键指标 metrics = { "tool_selection_accuracy": 0.92, # 工具选择准确率 "success_rate": 0.95, # 调用成功率 "avg_response_time": 2.5, # 平均响应时间(秒) "cost_per_query": 0.02 # 每次查询成本(美元) } # A/B测试 variant_a = AgentExecutor(tools=basic_tools) variant_b = AgentExecutor(tools=enhanced_tools) compare_performance(variant_a, variant_b)🔗 相关资源
- LangChain Tools文档
- Function Calling指南
- 配套项目代码
📝 总结
Agent工具调用是让AI拥有"超能力"的关键技术,通过:
✅工具注册- 定义清晰的工具接口
✅智能选择- LLM自动选择合适工具
✅结果整合- 将工具结果融入对话
✅错误处理- 优雅的异常处理机制
掌握了工具调用技术,你就能打造出真正实用的AI Agent,让它能够:
- 获取实时信息
- 执行复杂计算
- 访问外部系统
- 完成实际任务
下一步:动手实践,从简单的计算器工具开始,逐步构建功能丰富的Agent系统。
专栏:AI Agent实战专栏
日期:2026年5月10日
系列:AI Agent高级进阶系列第3篇