RAG 让 LLM 能「读」,Function Calling 让 LLM 能「做」。这篇带你从零实现一个带工具调用能力的聊天机器人——它能查天气、搜新闻、做算术,还能把结果整理成漂亮的格式返回。
📑 目录
项目目标
最终效果:
用户:「北京今天天气怎么样?顺便帮我算下 23 * 47」
机器人:
🌤 北京今天:晴,气温 12~22°C,适合出行
🧮 23 × 47 = 1081
用户:「搜一下最新的 AI 新闻」
机器人:[调用搜索工具] 🔍 找到以下最新资讯:
1. OpenAI 发布 GPT-5 ...
2. Google DeepMind 宣布 ...
关键能力:
✅ 自动判断是否需要调用工具
✅ 正确提取函数参数
✅ 执行工具并将结果整合进回答
✅ 多轮工具调用(一个问题可能需要调多个工具)
定义工具集
# bot/tools.py
import json
import random
from datetime import datetime
from typing import Callable
# 工具注册表:名称 → (函数, 描述, 参数Schema)
TOOLS_REGISTRY: dict[str, dict] = {}
def tool(name: str, description: str, params_schema: dict):
"""装饰器:注册一个工具函数"""
def decorator(func: Callable):
TOOLS_REGISTRY[name] = {
"function": func,
"description": description,
"parameters": params_schema,
}
return func
return decorator
@tool(
name="get_weather",
description="获取指定城市的当前天气",
params_schema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"},
},
"required": ["city"]
}
)
def get_weather(city: str) -> str:
"""模拟天气 API(实际替换为真实 API)"""
weathers = ["晴", "多云", "阴", "小雨"]
temp_low = random.randint(-5, 20)
temp_high = random.randint(temp_low + 5, temp_low + 18)
return json.dumps({"city": city, "weather": random.choice(weathers),
"temp_low": temp_low, "temp_high": temp_high,
"updated_at": datetime.now().isoformat()}, ensure_ascii=False)
@tool(
name="calculate",
description="执行数学计算表达式",
params_schema={
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,如 '23*47'"},
},
"required": ["expression"]
}
)
def calculate(expression: str) -> str:
try:
# 安全起见只允许数字和基本运算符
allowed = set("0123456789+-*/().% ")
if not all(c in allowed for c in expression):
return json.dumps({"error": "不允许的表达式字符"})
result = eval(expression) # 生产环境用 ast.literal_eval 更安全
return json.dumps({"expression": expression, "result": result})
except Exception as e:
return json.dumps({"error": str(e)})
@tool(
name="search_news",
description="搜索最新新闻(按关键词)",
params_schema={
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词"},
"limit": {"type": "integer", "description": "返回条数(默认5)"},
},
"required": ["keyword"]
}
)
def search_news(keyword: str, limit: int = 5) -> str:
"""模拟搜索(实际接入搜索 API 或 Tavily)"""
fake_news = [
f"{keyword} 相关重大进展报道",
f"专家解读{keyword}领域新趋势",
f"{month}月{keyword}行业数据发布",
]
return json.dumps([{"title": n, "source": "模拟源"} for n in fake_news[:limit]], ensure_ascii=False)
print(f"已注册 {len(TOOLS_REGISTRY)} 个工具: {list(TOOLS_REGISTRY.keys())}")
核心循环 & 完整实现
# bot/main.py
from openai import OpenAI
import json
from tools import TOOLS_REGISTRY
client = OpenAI()
def build_tools_definition():
"""转换为 OpenAI Function Calling 格式"""
return [{
"type": "function",
"function": {
"name": name,
"description": info["description"],
"parameters": info["parameters"]
}
} for name, info in TOOLS_REGISTRY.items()]
def execute_tool(name: str, args: dict):
"""安全执行工具并返回结果"""
if name not in TOOLS_REGISTRY:
return json.dumps({"error": f"未知工具: {name}"})
try:
func = TOOLS_REGISTRY[name]["function"]
result = func(**args)
return result
except Exception as e:
return json.dumps({"error": f"工具执行错误: {e}"})
def chat_with_tools(user_message: str, max_turns=5):
"""带 Function Calling 的多轮对话循环"""
messages = [{"role": "system", "content": "你是一个有帮助的助手。使用可用工具来获取实时信息和执行操作。"}]
messages.append({"role": "user", "content": user_message})
tools_def = build_tools_definition()
for turn in range(max_turns):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools_def,
temperature=0.2,
)
msg = response.choices[0].message
# 情况A:直接回答
if not msg.tool_calls:
return msg.content
# 情况B:调用工具
messages.append(msg) # 记录 LLM 的决策
for tc in msg.tool_calls:
func_name = tc.function.name
func_args = json.loads(tc.function.arguments)
print(f" 🔧 调用工具: {func_name}({func_args})")
tool_result = execute_tool(func_name, func_args)
print(f" 📋 结果: {tool_result[:100]}...")
# 结果喂回 LLM
messages.append({"role": "tool", "tool_call_id": tc.id,
"content": tool_result})
return "达到最大轮次限制"
# CLI 交互
if __name__ == "__main__":
print("🤖 函数调用机器人已启动!(输入 quit 退出)\n")
while True:
query = input("你: ")
if query.lower() == "quit": break
answer = chat_with_tools(query)
print(f"🤖: {answer}\n")
# 运行
python -m bot.main
❌ 常见误区
- ❌ 工具描述写得越详细越好 — 太长的描述反而干扰 LLM 决策,简洁精准最好
- ❌ 所有工具一次性注册 — 按场景分组加载,减少选择困难
- ❌ 忽略工具的错误处理 — 工具可能失败,必须有 fallback 和异常捕获

12万+

被折叠的 条评论
为什么被折叠?



