AI Agent 入门(三):Tool Use 入门 —— Function Calling 原理与实战

本文是 AI Agent 入门系列的第 3 篇。前两篇我们学会了调 LLM API 和结构化输出,现在进入 Agent 最核心的技术:Tool Use(函数调用)

学完本篇,你将给 LLM 装上"手"------让它能调用工具来回答问题。


本课目标

  • 理解 Function Calling / Tool Use 的原理
  • 学会定义工具并绑定到 LLM
  • 掌握工具调用的完整流程

本课产出

  • 新建文件lesson03_tool_calling.py,粘贴下文完整代码
  • 运行python lesson03_tool_calling.py
  • 效果:LLM 能根据问题自主决定调用计算器或天气工具,拿到结果后给出最终回答

一、什么是 Tool Use?

Tool Use(函数调用)是 Agent 最核心的技术------它让 LLM 从"只能说话"变成"能做事"。

核心流程

复制代码
用户: "2.5 * 3.7 + 1.8 等于多少?"

LLM 思考:这是一个数学计算,需要调用计算器工具
    ↓
LLM 输出:{"function": "calculator", "args": {"expr": "2.5 * 3.7 + 1.8"}}
    ↑ LLM 只输出调用请求,不执行计算
    ↓
你的代码执行 calculator("2.5 * 3.7 + 1.8")
    ↓
得到结果:11.05
    ↓
把结果返回给 LLM,LLM 生成最终回答:"结果是 11.05"

关键理解

LLM 不执行代码,它只发出"调用请求"。执行由你写代码完成,结果再喂回给 LLM。

这样做的好处:

  • LLM 不需要知道工具的实现细节
  • 你可以绑定任何工具(搜索、计算、发邮件、操作文件......)
  • 每个工具可以用最合适的语言/库实现

二、工具的定义方式

每个工具定义包含三部分:

字段 作用 关键
name 工具名称 唯一标识,Python 函数名保持一致
description 工具描述 最重要 ------ 决定 LLM 什么时候选这个工具
parameters 参数定义 JSON Schema 格式,告诉 LLM 需要传什么参数

工具描述的艺术

python 复制代码
# ❌ 差 ------ 太简略
"description": "计算"

# ✅ 好 ------ 说明什么时候用、参数怎么传
"description": "执行数学表达式计算,支持 + - * / ** 等运算符。"
               "当用户需要计算数值时使用此工具。"

三、完整调用流程

复制代码
┌──────┐   ① 提问    ┌──────┐   ② 请求调用工具   ┌──────┐
│ 用户 │ ──────────→ │ LLM  │ ────────────────→ │ 代码 │
│      │ ←────────── │      │ ←──────────────── │      │
└──────┘   ⑤ 回答    └──────┘   ④ 返回结果      └──────┘
                                                     │
                                                     │ ③ 执行
                                                     ↓
                                                 ┌──────┐
                                                 │ 工具 │
                                                 └──────┘

关于 tool_choice

默认情况下 LLM 自主决定是否调用工具。你也可以控制:

tool_choice 行为 适用场景
"auto"(默认) LLM 自主决定 不确定是否需要工具时
"required" 强制每次都调用 调试、确定性演示

多工具调用

LLM 可能一次请求调用多个工具 (如"北京和上海的天气"可能同时触发两次查询)。

代码中可以用循环处理:

python 复制代码
for tool_call in message.tool_calls:
    result = call_tool(tool_call)

四、完整代码

💡 环境准备:本课使用与前两课相同的环境。如尚未配置,请先完成:

  1. pip install openai python-dotenv
  2. 进入 code/ 目录,复制 .env.example.env,填入你的 API Key(详见第 1 课)
    ⚠️ 安全说明 :本课计算器使用 eval() 仅用于教学演示。

eval() 有安全风险,生产环境请使用 numexprast.literal_eval

新建 lesson03_tool_calling.py,粘贴以下代码:

python 复制代码
"""
第 3 课:Tool Use 入门 ------ 函数调用完整实现
"""
import json, os
from dotenv import load_dotenv
load_dotenv()

from openai import OpenAI
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)
MODEL = "deepseek-chat"

if not client.api_key:
    print("❌ 未检测到 API Key!")
    exit(1)

# ===========================================
# 工具函数实现
# ===========================================

def calculator(expr: str) -> str:
    """执行数学表达式计算(教学演示用 eval,生产环境请用 ast)"""
    try:
        # 安全白名单:只允许数字和基本运算符
        allowed = set("0123456789+-*/(). ")
        if not all(c in allowed for c in expr):
            return "错误:表达式包含不允许的字符"
        result = eval(expr)  # 仅用于教学演示
        return f"{expr} = {result}"
    except Exception as e:
        return f"计算错误:{e}"

def get_weather(city: str) -> str:
    """查询天气(mock 数据,仅用于演示)"""
    mock = {
        "北京": "晴,25°C,湿度 40%,北风 3 级",
        "上海": "多云,28°C,湿度 65%,东南风 2 级",
        "深圳": "阵雨,30°C,湿度 80%,南风 4 级",
        "广州": "阴天,29°C,湿度 75%,西南风 3 级",
        "成都": "多云,26°C,湿度 60%,东北风 2 级",
        "杭州": "晴,27°C,湿度 55%,东风 3 级",
    }
    data = mock.get(city)
    if data:
        return f"{city}天气:{data}"
    return f"暂无 {city} 的数据。支持:{', '.join(mock.keys())}"

# ===========================================
# 工具定义(API 格式)
# ===========================================

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "calculator",
            "description": "执行数学表达式计算。支持 + - * / ** 等运算符。"
                           "当用户需要计算数值时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "expr": {
                        "type": "string",
                        "description": "数学表达式,如 '2.5 * 3.7 + 1.8'"
                    }
                },
                "required": ["expr"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的实时天气。参数 city 用中文城市名。",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名,如 '北京'、'上海'"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

TOOL_FUNCTIONS = {"calculator": calculator, "get_weather": get_weather}

# ===========================================
# Agent 主循环
# ===========================================

def call_tool(tool_call):
    func_name = tool_call.function.name
    func_args = json.loads(tool_call.function.arguments)
    func = TOOL_FUNCTIONS.get(func_name)
    if not func:
        return f"未知工具 {func_name}"
    print(f"  → 调用 {func_name}({json.dumps(func_args, ensure_ascii=False)})")
    result = func(**func_args)
    print(f"  ← 结果: {result}")
    return result

def run_agent(user_input: str, max_turns: int = 5):
    messages = [
        {"role": "system", "content": "你是 AI 助手,可使用工具回答问题。"},
        {"role": "user", "content": user_input},
    ]
    for turn in range(1, max_turns + 1):
        print(f"\n--- 第 {turn} 轮 ---")
        try:
            response = client.chat.completions.create(
                model=MODEL, messages=messages, tools=TOOLS,
            )
        except Exception as e:
            print(f"API 调用失败:{e}")
            return

        msg = response.choices[0].message
        if msg.tool_calls:
            messages.append(msg)
            for tc in msg.tool_calls:
                messages.append({
                    "role": "tool", "tool_call_id": tc.id,
                    "content": call_tool(tc),
                })
        else:
            print(f"\n最终回答:{msg.content}")
            return

    print(f"达到最大轮数 {max_turns},结束。")

if __name__ == "__main__":
    print("=" * 50)
    print("测试 1:数学计算")
    print("=" * 50)
    run_agent("23 * 45 + 12 等于多少?")

    print("\n\n" + "=" * 50)
    print("测试 2:天气查询")
    print("=" * 50)
    run_agent("北京和上海的天气怎么样?")

    print("\n\n" + "=" * 50)
    print("测试 3:混合问题")
    print("=" * 50)
    run_agent("你好!顺便算一下 3.14 * 2.71 等于多少?")

运行结果示例

text 复制代码
==================================================
测试 1:数学计算
==================================================

--- 第 1 轮 ---
  → 调用 calculator({"expr": "23 * 45 + 12"})
  ← 结果: 23 * 45 + 12 = 1047

最终回答:23 × 45 + 12 = 1047

==================================================
测试 2:天气查询
==================================================

--- 第 1 轮 ---
  → 调用 get_weather({"city": "北京"})
  ← 结果: 北京天气:晴,25°C,湿度 40%,北风 3 级
  → 调用 get_weather({"city": "上海"})
  ← 结果: 上海天气:多云,28°C,湿度 65%,东南风 2 级

最终回答:北京今天晴,25°C;上海多云,28°C。

常见报错

问题 原因 解决
LLM 直接回答而不是调用工具 模型或 prompt 不支持 tools 检查模型是否支持 Function Calling,或加 tool_choice="required"
tool_calls 为空但 finish_reason 不是 stop 模型不支持工具调用 更换为 DeepSeek 或 GPT-4o 等支持工具调用的模型
计算器返回"表达式包含不允许的字符" 表达式中含有字母 纯数字计算不会有此问题,确保只传入数学表达式

动手练习

  1. 添加第三个工具:get_current_time(),返回当前时间
  2. 修改天气 mock 数据,让它支持更多城市
  3. 故意把工具描述写得很差(如 calculator 的描述写成"处理文本"),观察 LLM 的选择是否出错

思考题

  1. 如果 LLM 连续请求调用同一个工具(比如不断调用计算器),可能是什么原因?怎么处理?
  2. 工具的描述写得不好会有什么后果?
  3. 如果两个工具功能相似(比如两个搜索工具),LLM 怎么决定用哪个?

📦 完整代码

本课程所有代码已托管在 GitCode:

复制代码
git clone git@gitcode.com:gcw_A202cbBm/ai-agent.git
cd ai-agent/code

也可直接访问:https://gitcode.com/gcw_A202cbBm/ai-agent


下一篇预告:AI Agent 入门(四):手写 Agent 循环 ------ ReAct 模式完整实现

我们将用不到 100 行代码,从零实现一个完整的 Agent 主循环,支持多工具注册、异常处理、调用日志追踪。


如果您觉得有用,欢迎 点赞、转发、评论、关注

相关推荐
anOnion1 小时前
Agentic 前端开发之 实时显示 AI Agent 终端输出
前端·javascript·人工智能
AI 大模型学习不踩坑1 小时前
OpenClaw 完整教程:从安装到使用(官方脚本版)
java·人工智能·神经网络·机器学习·计算机视觉·自然语言处理·openclaw
不爱记笔记1 小时前
ClaudeCode接入DeepSeek教程!防封号!
人工智能·ai·deepseek·claudecode
随风一样自由1 小时前
【前端领域】2026最新前端领域全梳理(框架/工具/AI/跨端/底层标准/就业趋势)
前端·人工智能·前端框架
新知图书1 小时前
RAG之生成技术
人工智能·agent·ai agent·智能体·langgraph
漫步人生走在路上1 小时前
外贸GEO vs 传统SEO:区别有多大?
人工智能·搜索引擎·chatgpt·facebook·twitter
武子康2 小时前
调查研究-211 AgentBound 深度解析:AI Agent 不只要“有权限”,还要有可验证的行为治理
人工智能·llm·agent
Gp7HH6hrE2 小时前
OpenAI 与 Anthropic 开放公共学习平台
人工智能·学习·chatgpt
Mark0802032 小时前
不同AI工具在盯盘、财报整理与复盘记录中的适用场景分析
大数据·人工智能