Python大模型Function Calling实战:让AI拥有工具使用能力

Python 大模型 Function Calling 实战:让 AI 真正学会"动手干活"

大模型很聪明,但它有个致命缺陷:只能"说",不能"做" 。Function Calling 彻底打破了这道墙。本文将用 Python 从零实现 Function Calling,让大模型不仅能思考,还能调用真实工具完成任务。



一、什么是 Function Calling?为什么它改变了 AI 的游戏规则?

传统大模型的工作方式是这样的:

复制代码
用户提问 → 模型思考 → 返回文本回答

模型只能"纸上谈兵"------它不能查天气、不能发邮件、不能操作数据库、不能调用任何外部系统。

Function Calling 让模型从"军师"变成了"将军":

复制代码
用户提问 → 模型思考 → 决定调用什么工具 → 执行工具 → 获取结果 → 返回最终回答
对比维度 传统大模型 支持 Function Calling
能力边界 只能用训练数据回答 可以调用任意外部工具
实时性 知识截止日期前 可以获取实时数据
准确性 数学计算容易出错 可以调用计算器保证精确
交互性 纯文本对话 操作数据库、发邮件、控制设备
可扩展性 固定能力 无限扩展(写什么工具就能用什么)

一句话总结:Function Calling = 让大模型从"只会聊天"进化为"能干实事"。


二、核心原理:大模型如何"学会"调用工具?

Function Calling 的核心流程并不复杂:

复制代码
┌─────────┐     ┌─────────┐     ┌──────────┐     ┌─────────┐
│  用户    │     │  大模型  │     │  工具函数 │     │  大模型  │
│  提问    │────▶│  判断    │────▶│  执行    │────▶│  总结    │──▶ 最终回答
│         │     │  需要工具 │     │  并返回   │     │  回答    │
└─────────┘     └─────────┘     └──────────┘     └─────────┘

关键步骤:

  1. 定义工具:告诉模型有哪些工具可用,每个工具接收什么参数
  2. 模型决策:模型根据用户输入,判断是否需要调用工具,需要调用哪个
  3. 执行工具:你的代码执行对应函数,拿到结果
  4. 模型总结:把工具结果返回给模型,生成最终回答

注意:模型本身不执行任何代码,它只是输出结构化的 JSON 表示"我想调用这个函数",真正的执行由你的 Python 代码完成。


三、环境准备

bash 复制代码
pip install openai python-dotenv

创建 .env 文件:

env 复制代码
OPENAI_API_KEY=sk-your-key-here
# 也可以用兼容 OpenAI 接口的国内模型
# OPENAI_BASE_URL=https://api.deepseek.com

四、实战 1:天气查询工具 ------ 从最简单的例子开始

4.1 定义工具函数

python 复制代码
# weather_tool.py
import json
from datetime import datetime

# 模拟天气数据(实际项目中替换为真实 API)
WEATHER_DATA = {
    "北京": {"temp": 22, "weather": "晴", "humidity": 45, "wind": "北风3级"},
    "上海": {"temp": 26, "weather": "多云", "humidity": 72, "wind": "东南风2级"},
    "深圳": {"temp": 30, "weather": "阵雨", "humidity": 85, "wind": "南风3级"},
    "成都": {"temp": 20, "weather": "阴", "humidity": 68, "wind": "微风"},
    "杭州": {"temp": 24, "weather": "晴转多云", "humidity": 60, "wind": "东风2级"},
}

def get_weather(city: str) -> str:
    """查询指定城市的天气信息

    Args:
        city: 城市名称,如"北京"、"上海"

    Returns:
        天气信息的 JSON 字符串
    """
    if city in WEATHER_DATA:
        data = WEATHER_DATA[city]
        return json.dumps({
            "city": city,
            "temp": data["temp"],
            "weather": data["weather"],
            "humidity": data["humidity"],
            "wind": data["wind"],
            "update_time": datetime.now().strftime("%Y-%m-%d %H:%M")
        }, ensure_ascii=False)
    return json.dumps({"error": f"未找到城市:{city}"}, ensure_ascii=False)

4.2 注册工具到模型

python 复制代码
# tools定义:告诉模型有哪些工具可用
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的实时天气信息,包括温度、天气状况、湿度、风力等",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "要查询的城市名称,如'北京'、'上海'、'深圳'"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

4.3 核心循环:调用模型 → 解析工具 → 执行 → 再调用

python 复制代码
# main.py
import json
from openai import OpenAI
from weather_tool import get_weather

client = OpenAI()  # 自动读取环境变量

# 工具映射表:函数名 → 实际函数
TOOL_MAP = {
    "get_weather": get_weather,
}

def chat_with_tools(user_message: str) -> str:
    """带工具调用能力的对话"""
    messages = [
        {"role": "system", "content": "你是一个智能助手,可以帮用户查询天气信息。回答时请用简洁自然的语言。"},
        {"role": "user", "content": user_message}
    ]

    # 第一次调用:让模型判断是否需要工具
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto"  # 自动决定是否调用工具
    )

    message = response.choices[0].message

    # 如果模型决定不调用工具,直接返回回答
    if not message.tool_calls:
        return message.content

    # 模型决定调用工具,逐个执行
    messages.append(message)  # 把模型的工具调用记录加入对话

    for tool_call in message.tool_calls:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)

        print(f"  [工具调用] {func_name}({func_args})")

        # 执行对应工具
        if func_name in TOOL_MAP:
            result = TOOL_MAP[func_name](**func_args)
        else:
            result = json.dumps({"error": f"未知工具:{func_name}"})

        # 把工具结果加入对话
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result
        })

    # 第二次调用:让模型根据工具结果生成最终回答
    final_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages
    )

    return final_response.choices[0].message.content


# 测试
if __name__ == "__main__":
    questions = [
        "北京今天天气怎么样?",
        "上海和深圳哪个城市更热?",
        "推荐一个适合出游的城市"
    ]

    for q in questions:
        print(f"\n提问:{q}")
        answer = chat_with_tools(q)
        print(f"回答:{answer}")
        print("-" * 50)

运行效果:

复制代码
提问:北京今天天气怎么样?
  [工具调用] get_weather({'city': '北京'})
回答:北京今天天气晴朗,气温22°C,湿度45%,北风3级。整体来说天气不错,
适合外出活动,建议适当补水,风力稍大注意防护。
--------------------------------------------------
提问:上海和深圳哪个城市更热?
  [工具调用] get_weather({'city': '上海'})
  [工具调用] get_weather({'city': '深圳'})
回答:深圳更热!深圳目前气温30°C,而上海是26°C,深圳比上海高了4度。
不过上海湿度更大(72% vs 85%),体感上深圳更闷热一些。
--------------------------------------------------

注意第二问:模型自主判断需要查两个城市,并行调用了两次工具!


五、实战 2:多工具协作 ------ 打造 AI 助手

真实场景中,我们需要多个工具协同工作。下面打造一个功能更丰富的 AI 助手。

5.1 定义多个工具

python 复制代码
# tools.py
import json
import math
from datetime import datetime

# ========== 工具 1:数学计算器 ==========
def calculator(expression: str) -> str:
    """安全地计算数学表达式

    Args:
        expression: 数学表达式,如 "2 + 3 * 4" 或 "sqrt(16)"
    """
    # 安全白名单:只允许数学函数和数字运算
    ALLOWED_NAMES = {
        "abs": abs, "round": round, "min": min, "max": max,
        "sqrt": math.sqrt, "pow": pow, "pi": math.pi, "e": math.e,
        "sin": math.sin, "cos": math.cos, "tan": math.tan,
        "log": math.log, "log10": math.log10, "ceil": math.ceil,
        "floor": math.floor,
    }
    try:
        result = eval(expression, {"__builtins__": {}}, ALLOWED_NAMES)
        return json.dumps({"expression": expression, "result": result})
    except Exception as e:
        return json.dumps({"error": f"计算错误:{str(e)}"})

# ========== 工具 2:时间查询 ==========
def get_current_time(timezone_offset: int = 8) -> str:
    """获取当前时间

    Args:
        timezone_offset: 时区偏移量,默认8(北京时间 UTC+8)
    """
    from datetime import timezone, timedelta
    tz = timezone(timedelta(hours=timezone_offset))
    now = datetime.now(tz)
    return json.dumps({
        "current_time": now.strftime("%Y-%m-%d %H:%M:%S"),
        "weekday": ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][now.weekday()],
        "timezone": f"UTC+{timezone_offset}"
    }, ensure_ascii=False)

# ========== 工具 3:单位换算 ==========
def unit_convert(value: float, from_unit: str, to_unit: str) -> str:
    """单位换算

    Args:
        value: 原始值
        from_unit: 原始单位(km, mile, kg, lb, celsius, fahrenheit 等)
        to_unit: 目标单位
    """
    conversions = {
        ("km", "mile"): lambda v: v * 0.621371,
        ("mile", "km"): lambda v: v * 1.60934,
        ("kg", "lb"): lambda v: v * 2.20462,
        ("lb", "kg"): lambda v: v * 0.453592,
        ("celsius", "fahrenheit"): lambda v: v * 9/5 + 32,
        ("fahrenheit", "celsius"): lambda v: (v - 32) * 5/9,
        ("meter", "feet"): lambda v: v * 3.28084,
        ("feet", "meter"): lambda v: v * 0.3048,
    }

    key = (from_unit.lower(), to_unit.lower())
    if key in conversions:
        result = conversions[key](value)
        return json.dumps({
            "original": f"{value} {from_unit}",
            "converted": f"{round(result, 4)} {to_unit}"
        }, ensure_ascii=False)
    return json.dumps({"error": f"不支持的转换:{from_unit} → {to_unit}"})

5.2 注册所有工具

python 复制代码
# 工具定义
ALL_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "calculator",
            "description": "计算数学表达式,支持加减乘除、三角函数、对数、幂运算等。当需要精确数值计算时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "要计算的数学表达式,如 '2**10'、'sqrt(144)'、'sin(pi/4)'"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "获取当前的日期和时间",
            "parameters": {
                "type": "object",
                "properties": {
                    "timezone_offset": {
                        "type": "integer",
                        "description": "时区偏移量,默认为8(北京时间)"
                    }
                },
                "required": []
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "unit_convert",
            "description": "在不同度量单位之间进行换算,如长度、重量、温度等",
            "parameters": {
                "type": "object",
                "properties": {
                    "value": {
                        "type": "number",
                        "description": "要换算的数值"
                    },
                    "from_unit": {
                        "type": "string",
                        "description": "原始单位,如 km、mile、kg、lb、celsius、fahrenheit"
                    },
                    "to_unit": {
                        "type": "string",
                        "description": "目标单位"
                    }
                },
                "required": ["value", "from_unit", "to_unit"]
            }
        }
    }
]

# 工具映射
TOOL_MAP = {
    "calculator": calculator,
    "get_current_time": get_current_time,
    "unit_convert": unit_convert,
}

5.3 通用工具调用引擎

python 复制代码
# agent.py
import json
from openai import OpenAI

client = OpenAI()

def agent_chat(user_message: str, system_prompt: str = None, max_rounds: int = 5) -> str:
    """
    通用 Function Calling 引擎

    Args:
        user_message: 用户输入
        system_prompt: 系统提示词
        max_rounds: 最大工具调用轮数(防止死循环)

    Returns:
        模型的最终回答
    """
    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": user_message})

    for round_num in range(max_rounds):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=ALL_TOOLS,
            tool_choice="auto"
        )

        message = response.choices[0].message

        # 没有工具调用,返回最终回答
        if not message.tool_calls:
            return message.content

        # 有工具调用,执行所有工具
        messages.append(message)

        for tool_call in message.tool_calls:
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            print(f"  🔧 [调用] {func_name}({json.dumps(func_args, ensure_ascii=False)})")

            if func_name in TOOL_MAP:
                result = TOOL_MAP[func_name](**func_args)
            else:
                result = json.dumps({"error": f"未知工具:{func_name}"})

            print(f"  📋 [结果] {result}")

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

    return "抱歉,工具调用轮数已达上限。"


# 测试
if __name__ == "__main__":
    system_prompt = """你是一个全能AI助手,具备计算、时间查询和单位换算能力。
回答问题时:
1. 需要精确计算时,使用计算器工具
2. 需要知道当前时间时,使用时间查询工具
3. 需要单位换算时,使用换算工具
4. 用简洁自然的中文回答"""

    test_cases = [
        "帮我算一下 2 的 20 次方是多少",
        "100华氏度等于多少摄氏度?",
        "现在几点了?距离2026年国庆节还有多少天?",
        "一个马拉松42.195公里等于多少英里?跑完大概消耗多少卡路里(按每公里60大卡算)?",
    ]

    for question in test_cases:
        print(f"\n{'='*60}")
        print(f"🙋 提问:{question}")
        print(f"{'='*60}")
        answer = agent_chat(question, system_prompt)
        print(f"\n💬 回答:{answer}")

运行效果:

复制代码
============================================================
🙋 提问:100华氏度等于多少摄氏度?
============================================================
  🔧 [调用] unit_convert({"value": 100, "from_unit": "fahrenheit", "to_unit": "celsius"})
  📋 [结果] {"original": "100 fahrenheit", "converted": "37.7778 celsius"}

💬 回答:100华氏度约等于 **37.78摄氏度**,接近人体正常体温的温度。
============================================================

六、实战 3:工具编排 ------ 让 AI 自主规划执行链

复杂场景下,模型需要链式调用多个工具。看这个例子:

python 复制代码
# chain_agent.py
import json
from openai import OpenAI

client = OpenAI()

def chain_agent(user_message: str) -> str:
    """
    支持多轮工具调用的智能 Agent

    模型可以在一轮中调用多个工具,
    也可以在拿到结果后决定是否继续调用更多工具。
    """
    messages = [
        {
            "role": "system",
            "content": """你是一个智能规划助手。面对复杂问题时:
1. 先分析需要哪些信息
2. 逐步调用工具获取信息
3. 基于工具结果进行推理和回答

你必须主动调用工具获取数据,不要凭空猜测。"""
        },
        {"role": "user", "content": user_message}
    ]

    for round_num in range(10):  # 最多 10 轮工具调用
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=ALL_TOOLS,
            tool_choice="auto"
        )

        message = response.choices[0].message

        if not message.tool_calls:
            return message.content

        messages.append(message)
        print(f"\n--- 第 {round_num + 1} 轮工具调用 ---")

        for tool_call in message.tool_calls:
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            result = TOOL_MAP[func_name](**func_args)
            print(f"  {func_name}({json.dumps(func_args, ensure_ascii=False)}) => {result}")

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

    return "工具调用轮数已达上限"


# 测试复杂链式调用
if __name__ == "__main__":
    result = chain_agent(
        "我想做一个蛋糕。帮我算一下:如果配方需要把350华氏度的烤箱预热,"
        "那是多少摄氏度?另外帮我算一下,2.5磅面粉等于多少公斤?"
    )
    print(f"\n最终回答:\n{result}")

模型会自主规划:先换算温度,再换算重量,最后综合回答。


七、进阶技巧

7.1 强制调用 / 禁止调用工具

python 复制代码
# 强制模型必须调用某个工具
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "get_weather"}}
)

# 禁止调用任何工具(纯对话模式)
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
    tool_choice="none"
)

7.2 并行工具调用

GPT-4o / GPT-4o-mini 支持在一轮中并行调用多个独立工具:

python 复制代码
# 当用户问"北京和上海天气对比"时,
# 模型会在一个 tool_calls 中返回两个调用:
# tool_calls[0]: get_weather({"city": "北京"})
# tool_calls[1]: get_weather({"city": "上海"})

7.3 错误处理与重试

python 复制代码
def safe_tool_call(func, **kwargs, max_retries=2):
    """带重试的安全工具调用"""
    for attempt in range(max_retries + 1):
        try:
            result = func(**kwargs)
            return result
        except Exception as e:
            if attempt == max_retries:
                return json.dumps({"error": f"工具执行失败(已重试{max_retries}次):{str(e)}"})
            print(f"  ⚠️ 工具执行失败,正在重试({attempt + 1}/{max_retries})...")

八、性能与成本分析

项目 数据
单次工具调用延迟 约增加 0.5-1s
Token 消耗 工具定义约 100-300 tokens/个
建议单次对话工具数 3-10 个(太多模型会混乱)
推荐模型 GPT-4o-mini(性价比最高)、GPT-4o(复杂场景)、DeepSeek-V3

成本估算(GPT-4o-mini):

  • 输入:$0.15 / 1M tokens
  • 输出:$0.6 / 1M tokens
  • 一次包含 3 个工具的完整对话:约 0.1-0.3 美分

九、踩坑指南

  1. 工具描述是灵魂description 写得越清晰,模型选错工具的概率越低。不要写"查询信息",要写"查询指定城市的实时天气,包括温度、湿度、风力"
  2. 参数类型要准确integer 就不要写 string,模型会根据类型生成不同格式的参数
  3. 防止注入:工具函数的参数来自模型输出,务必做安全校验
  4. 上下文窗口 :每轮工具调用都会增加消息长度,注意 max_tokens 限制
  5. 幂等性:工具函数尽量设计为幂等的(同一参数多次调用结果一致),因为模型可能重复调用

总结

Function Calling 的工作模式:

复制代码
定义工具 → 注册给模型 → 模型自主决策 → 代码执行工具 → 结果返回模型 → 生成最终回答
能力 传统大模型 + Function Calling
数学计算 容易出错 精确无误
实时信息 无法获取 随时查询
外部操作 无能为力 无限扩展
多步推理 靠谱度一般 工具链保障

Function Calling 是 AI Agent 的基石。掌握了它,你就掌握了让大模型真正"干活"的能力。下一步可以结合 LangChain、CrewAI 等框架,构建更复杂的多 Agent 协作系统。

如果觉得有用,点赞收藏不迷路!下期我们将深入探讨 RAG 检索增强生成,让 AI 拥有你的私有知识库。

相关推荐
无心水1 小时前
【Hermes:MCP 与工具实战】31、多 Agent 编排:delegate_task 并行机制与安全设计 —— 让智能体组团作战,效率翻倍
人工智能·ai·mcp协议·openclaw·养龙虾·hermes·honcho
xyq20241 小时前
Vue.js 实例
开发语言
Data_Journal1 小时前
Puppeteer指纹识别指南:循序渐进,简单易学!
服务器·前端·人工智能·物联网·媒体
源码之家1 小时前
计算机毕业设计:Python中药材数据可视化与智能分析平台 Django框架 中药数据分析 医药数据分析数据分析 可视化 爬虫 (建议收藏)✅
python·深度学习·信息可视化·数据分析·django·课程设计
q_35488851531 小时前
计算机毕业设计:Python中药材天地网数据挖掘与可视化系统 Django框架 中药数据分析 医药数据分析数据分析 可视化 爬虫 (建议收藏)✅
python·数据挖掘·数据分析·django·flask·课程设计
敲代码的瓦龙1 小时前
Android?碎片!!!
java·开发语言·android-studio
froginwe111 小时前
SVG 滤镜:全面解析与高效应用
开发语言
不知名的老吴1 小时前
深度剖析NLP模型的实现步骤(二)
人工智能·自然语言处理
余俊晖1 小时前
图文混合文档的轻量级多模态listwise重排框架:Rank-Nexus
人工智能·算法·机器学习