AI Agent 入门实战:用 Function Calling 让大模型学会调用工具

一、引子:大模型缺的那条腿

你有没有遇到过这种情况------你问 ChatGPT "今天北京的天气怎么样",它说"我无法实时获取天气信息"?

这不是模型不够聪明,而是它天生缺了一条腿:无法和外部世界交互。大模型的知识截止于训练数据的那一刻,它不知道你的数据库里有什么,也没法直接调用外部 API。

2024 年 OpenAI 推出了 Function Calling(函数调用)能力,让模型不仅能"说话",还能"动手"------调用 API、查数据库、发邮件、搜网页。这就是 Agent 架构最核心的基石。

今天我们就从零手写一个能自动调用工具的 Agent,你不需要任何框架,只需要 Python 和一点耐心。

二、Agent 核心逻辑:三步循环

别管任何花哨的名词,Agent 的本质就是一个简单的循环:

步骤 做什么 谁来干
① 思考 理解用户问题,决定是否需要调用工具 LLM
② 调用 输出结构化参数(JSON),程序执行对应函数 你的代码
③ 反馈 把工具执行结果送回 LLM,让它生成最终回答 LLM + 代码

这个三步循环跑一次叫"单轮工具调用",跑多次叫"多轮 Agent"。

很多人把 Agent 和 RAG 搞混,它们的区别其实很清晰:

能力维度 RAG Agent(Function Calling)
检索知识库 ✅ 向量搜索 ✅ 可调用搜索 API
操作数据库 ✅ 执行 SQL 或 CRUD
调用外部 API ✅ GET/POST 任意接口
获取实时数据 ✅ 天气/股票/新闻
自主决策 ✅ 判断何时调、调哪个工具
多步推理 ✅ 链式思考与中间结果复用

一句话总结:RAG 让 AI"知道更多",Agent 让 AI"能做更多"。

三、Function Calling 到底怎么工作的?

OpenAI 的 Chat Completions API 提供了一个 tools 参数,你可以把"函数定义"以 JSON Schema 的格式传给模型:

python 复制代码
import json
from openai import OpenAI

client = OpenAI()

# 定义工具(JSON Schema 格式)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如 北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# 第一次调用:模型决定是否要调工具
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
    tools=tools
)

msg = response.choices[0].message
if msg.tool_calls:
    print(f"模型选择了工具:{msg.tool_calls[0].function.name}")
    print(f"参数:{msg.tool_calls[0].function.arguments}")
    # 输出:模型选择了工具:get_weather
    # 输出:参数:{"city": "北京"}

模型返回的 tool_calls 里包含了工具名称参数,你只需要解析它、执行对应的函数、把结果送回模型------Agent 循环就完成了。

关键点在于:模型只负责"决定调哪个工具、传什么参数",不负责"执行"。执行工具拿到结果后,再送一次请求让模型生成人类可读的回答。

四、实战:80 行代码写一个能搜索和计算的 Agent

下面是一个完整的 Agent 实现,它有两个工具:一个模拟搜索引擎,一个做数学计算。

python 复制代码
import json
from openai import OpenAI

client = OpenAI()

# --- 工具函数 ---
def search_web(query: str) -> str:
    """模拟搜索引擎,实际项目可替换为 Bing/SerpAPI 等"""
    results = {
        "北京天气": "北京今天晴,气温 25-32°C,湿度 40%",
        "北京人口": "北京市常住人口约 2188 万(2024年)",
    }
    return results.get(query, f"未找到「{query}」的相关信息")

def calculator(expr: str) -> str:
    """安全计算器,只允许数字和四则运算"""
    try:
        allowed = set("0123456789+-*/.()")
        if not all(c in allowed for c in expr):
            return "表达式包含非法字符"
        return str(eval(expr))
    except Exception as e:
        return f"计算错误:{str(e)}"

# --- 工具注册表(映射 name → 函数) ---
FUNCTIONS = {
    "search_web": search_web,
    "calculator": calculator,
}

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "搜索实时信息,适合查天气、新闻、百科",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "搜索关键词"}
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculator",
            "description": "执行数学计算,支持加减乘除",
            "parameters": {
                "type": "object",
                "properties": {
                    "expr": {"type": "string", "description": "数学表达式"}
                },
                "required": ["expr"]
            }
        }
    }
]

# --- Agent 主循环 ---
def agent_loop(user_input: str, max_turns: int = 5) -> str:
    messages = [{"role": "user", "content": user_input}]

    for turn in range(max_turns):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=TOOLS
        )
        msg = response.choices[0].message

        # 模型不再需要工具 → 返回最终回答
        if not msg.tool_calls:
            return msg.content

        # 把模型的工具调用请求加入对话历史
        messages.append(msg)

        # 逐个执行工具,把结果送回
        for tc in msg.tool_calls:
            func_name = tc.function.name
            args = json.loads(tc.function.arguments)
            result = FUNCTIONS[func_name](**args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": result
            })

    return "已超过最大推理轮数"

# --- 测几个例子 ---
print(agent_loop("北京人口是多少?"))
# 输出:北京市常住人口约 2188 万

print(agent_loop("计算 (35 + 28) * 2 的结果"))
# 输出:126

print(agent_loop("查一下北京天气,然后帮我算如果温度降低 5 度是多少"))
# Agent 先调 search_web 查天气 → 拿到 25-32°C → 再调 calculator 算 32-5 → 最终回答

核心逻辑不到 60 行,但已经具备了一个 Agent 的全部要素:

  • 工具定义:JSON Schema 描述函数签名
  • 模型判断:LLM 自主决定是否调用、调哪个
  • 函数执行:代码安全执行并返回结果
  • 结果反馈:工具结果送回模型,生成自然语言回答
  • 多轮循环:上一轮结果可以做下一轮输入

五、从手写到框架:什么时候该用什么?

手写 Function Calling 灵活性高、零依赖,适合原型验证。如果要上生产,框架能帮你省很多事:

使用场景 推荐方案 优势
快速原型验证 OpenAI SDK 直接手写 零依赖,完全可控
单 Agent 复杂工具链 LangChain Agent 丰富的工具生态和内置 memory
多 Agent 协作编排 LangGraph / CrewAI 内置状态机 + 图编排
企业级 .NET/Java 集成 Semantic Kernel(微软) 强类型,企业级链路追踪
低代码 AI 应用 Dify / Coze 可视化编排,内置 RAG + Agent

六、避坑指南

这几个坑我亲身体验过,帮你省时间:

  1. 工具描述要写得像说明书 。模型理解工具的唯一渠道就是 description 字段。写"搜索信息"不如写"搜索最新的实时信息,适合查天气、新闻、百科类问题"。

  2. 参数名要有语义queryq 好,city_namecity 更明确------模型能通过参数名推断含义。

  3. tool_call_id 必须一一对应。多工具并行调用时,每个工具结果必须匹配正确的 ID,否则 API 报错。

  4. 注意 Token 消耗tools 定义本身会占用 Token。工具多了(10+ 个),光工具定义就可能吃掉 2000+ Token。

  5. 工具失败不要抛异常,把错误信息返回给模型。这样模型可以自主决定换参数重试,而不是让你的程序 crash。

  6. 限制 max_turns。防止 Agent 陷入死循环------5 轮通常是合理的上限。

七、总结

Agent 架构听起来高大上,拆开来看核心就是三步循环:LLM 思考 → 调用工具 → 反馈结果。Function Calling 让它成为可能,框架让它变得可维护。

下一期预告:当 Agent 遇到 RAG------如何让 AI 助手既能查知识库,又能操作数据库,真正实现"能问能查能做"。

关注我,不错过后续内容。欢迎在评论区讨论你的 Agent 使用场景或踩过的坑。

扩展阅读:Function Calling 与 MCP 的关系

如果你读过我之前的 MCP 系列文章,可能会问:Function Calling 和 MCP 有什么区别?

简单说,Function Calling 是一种机制,MCP 是一种协议。Function Calling 定义了"模型怎么告诉程序我要调什么工具"的接口格式,而 MCP 定义了"工具服务器和 AI 客户端之间怎么通信"的完整协议。两者不是替代关系,而是互补关系------你完全可以在 MCP Server 内部用 Function Calling 来实现工具调度。

工具函数的设计原则

写工具函数时,有几个设计原则值得记住:

原则一:函数的输入输出都应该是字符串。 不管是搜索 API 返回的 JSON、数据库查询结果、还是计算器算出的数字------最终给模型看到的都应该是一个人类可读的字符串。模型理解自然语言远比理解结构化数据容易。

原则二:工具粒度要适中。 一个工具函数只做一件事。把"搜索+计算+发邮件"塞进一个函数比拆成三个函数难维护得多,而且模型更可能用错参数。

原则三:错误信息也要有信息量。 "查询失败"不如"查询失败:API 返回 500,可能是网络超时"------后者让模型有机会做出合理的后续决策(比如等一下再重试)。

相关推荐
三声三视16 小时前
uni-app 鸿蒙端传参变成 [object Object]?顺着源码追到 ArkTS router 底层才搞明白
人工智能·ai·uni-app·aigc·ai编程·harmonyos
DisonTangor21 小时前
【微软开源】Mage-Flow 深度解读:微软4B参数图像生成与编辑模型,单卡A100仅需0.59秒
人工智能·microsoft·ai作画·开源·aigc
大模型搬砖师1 天前
金融、制造、互联网:三个行业的 AI 网关落地实录
aigc·ai编程·gpu算力
imbackneverdie1 天前
知网官宣:禁止 AI 列为论文署名作者,标注 Gemini、DeepSeek 等 AI 为作者的稿件统一下架
大数据·人工智能·ai·职场和发展·aigc·科研·高校
春风野草1 天前
AI Agent 工程底座实战:模型路由、限流、错误处理和成本意识
aigc·ai编程
程序员在囧途1 天前
AIGC SaaS 多租户平台怎么做行业客户二次演示?从 https://aigc.likeadmin.cn 到开源应用中心复盘链路
开源·aigc
后端小肥肠1 天前
Doubao-Seed-Evolving实测:我做了个情绪价值拉满的星座记账 Skill
人工智能·aigc·agent
李剑一1 天前
看完DeepSeek梁文锋内部交流会发言实录,我或许明白了为什么一直坚持不融资的DeepSeek开启了融资之路,也明白了中国人做AI一定是能够成功的
aigc·agent
狂师1 天前
从 LLM 评测到 AI Agent 评测,我的一些思考!
人工智能·程序员·aigc
win4r2 天前
🚀Graph Engineering范式:Codex Multi-agent V2支持Kimi、MiniMax、GPT多模型混用+动态派生subagent,并行执行、Pi Agent工具调用,效率倍增
aigc·ai编程·vibecoding