• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

LLM函数调用实战:Function Calling从定义到完整代码教程

AI Coding 实测 admin 35分钟前 5次浏览 已收录 扫描二维码

函数调用解决了什么问题

用LLM做个聊天机器人很简单,但你试试让它查个天气、算个账、查个库存,马上就露馅。模型会一本正经地编数据,或者干脆说”我无法访问外部数据”。Function Calling就是解决这个问题的。

原理不复杂。你提前告诉模型有哪些工具可以用,模型收到用户问题后判断要不要调工具,如果要调就返回函数名和参数。你的代码执行这个函数,把结果喂回给模型,模型再组织成自然语言回答。整个过程分两轮API调用。

OpenAI在2023年6月推出这个功能,现在Claude、Gemini、Qwen等主流模型都支持类似机制。这篇文章用OpenAI的API做演示,但思路通用于所有支持function calling的模型。

完整代码实现

下面是一个可以运行的最小实现。你只需要装openai库,配好API key就能跑。

先定义工具。这一步最关键,模型全靠你的描述来决定什么时候调什么工具:

import openai
import json

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的当前天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如:北京、上海、深圳"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算,支持加减乘除和括号",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "数学表达式,如:125 * 8 + 30"
                    }
                },
                "required": ["expression"]
            }
        }
    }
]

description字段别偷懒。写清楚函数做什么、什么时候该用,模型才能准确判断。很多人工具定义写得太模糊,模型该调的时候不调,不该调的时候乱调,问题基本都出在这。

然后是工具的实际实现:

def get_weather(city):
    # 实际项目替换为真实天气API
    weather_data = {
        "北京": {"temp": 15, "condition": "晴", "humidity": 45},
        "上海": {"temp": 22, "condition": "多云", "humidity": 68},
        "深圳": {"temp": 28, "condition": "阵雨", "humidity": 82}
    }
    return json.dumps(
        weather_data.get(city, {"error": "未找到该城市天气数据"}),
        ensure_ascii=False
    )

def calculate(expression):
    try:
        # 生产环境请用ast.literal_eval或专门的表达式解析库
        result = eval(expression)
        return json.dumps({"result": result})
    except Exception as e:
        return json.dumps({"error": str(e)})

最后是对话主循环。这是核心逻辑:

def run_conversation(user_input):
    messages = [{"role": "user", "content": user_input}]

    # 第一轮:模型决定是否调用工具
    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )

    message = response.choices[0].message
    messages.append(message)

    # 模型决定调用工具
    if message.tool_calls:
        for tool_call in message.tool_calls:
            func_name = tool_call.function.name
            func_args = json.loads(tool_call.function.arguments)

            # 执行对应的函数
            if func_name == "get_weather":
                result = get_weather(**func_args)
            elif func_name == "calculate":
                result = calculate(**func_args)
            else:
                result = json.dumps({"error": "未知函数"})

            # 把结果返回给模型
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

        # 第二轮:模型根据工具结果生成最终回复
        final_response = openai.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages
        )
        return final_response.choices[0].message.content

    return message.content

测试一下:

print(run_conversation("北京今天天气怎么样?"))
# 输出:北京今天晴,气温15度,湿度45%。

print(run_conversation("帮我算一下 125 * 8 + 30"))
# 输出:125 * 8 + 30 = 1030。

print(run_conversation("上海和深圳哪个更热?"))
# 输出:深圳更热。深圳当前28度有阵雨,上海22度多云。

第三个例子会自动调用两次get_weather,分别查上海和深圳的数据,然后对比给出结论。模型自己决定了调用顺序。

4个容易踩的坑

第一个坑:description写得太短。 之前说过,但值得再说一次。如果你的工具description只写了”查询天气”,模型可能在用户说”北京适合穿什么”的时候不知道该调天气工具。改成”获取指定城市的当前天气信息,包括温度、天气状况和湿度”就好多了。

第二个坑:参数类型不匹配。 模型偶尔会把数字传成字符串,或者把数组传成单个值。在函数实现里加类型检查和默认值处理。比如city参数如果不是字符串就str()一下。

第三个坑:忘记处理多工具调用。 模型可能在一个回复里同时调用多个工具(像第三个测试例子那样)。你的循环必须处理tool_calls列表里的每一个调用,不能只取第一个。

第四个坑:tool_call_id搞混。 每个tool_call都有唯一id,返回结果时必须对应正确的id。如果你在循环里把id搞串了,模型会把天气数据当成计算结果来解读,输出会很离谱。

成本控制建议

function calling的两轮调用意味着token消耗翻倍。第一轮发出去的消息(包括工具定义)会被算作输入token,第二轮又要带上完整对话历史加上工具返回的结果。工具定义本身也占token,一般200到500 token。

据OpenAI官方定价,gpt-4o-mini的输入token价格是$0.15/百万,输出$0.60/百万。一次普通的function calling对话大概消耗1500到3000 token,算下来不到$0.001。但如果你的应用有并发用户,成本会快速累积。

几个省钱的办法。工具数量控制在3到5个,别一次性塞20个工具进去。不需要工具的对话就不传tools参数,模型不会尝试调用。如果用户问题很明显不需要工具(比如”你好”),可以先做个简单分类跳过function calling流程。

用function calling做AI Agent是当前最实用的路径。比起写一堆prompt让模型输出特定格式再正则解析,function calling的结构化输出靠谱得多。把这个代码跑起来,加上你自己的业务工具,一个能用的AI助手就有了雏形。

喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。