1. 从“聊天”到“行动”:为什么你的AI助手需要Function Calling?

你有没有遇到过这样的情况?你问一个AI助手:“今天北京热不热?我该穿什么衣服?” 它可能会给你一个很长的回答,告诉你北京夏天通常很热,建议穿短袖,但就是给不出此时此刻的真实温度和具体天气状况。这不是AI笨,而是它被“困”在了自己的知识库里——它只能基于训练时学到的数据来回答,无法获取最新的、实时的外部信息。

这就是Function Calling要解决的核心问题。你可以把它想象成给AI装上了一双“手”和“眼睛”。以前,AI只能“想”和“说”;现在,通过Function Calling,它还能“做”——调用外部的工具、API和服务,把真实世界的数据拿进来,再结合自己的理解力,给你一个真正有用的答案。

我刚开始接触这个概念时,觉得特别酷。这不就是把大模型从一个“百科全书式的聊天机器人”,升级成了一个“能替你办事的智能助理”吗?比如查天气、订机票、查股票、控制智能家居,这些需要连接现实世界数据的任务,现在都能通过Function Calling来实现。

DeepSeek作为国内领先的大模型,对Function Calling的支持已经非常成熟和稳定。我实测下来,它的意图识别准确率很高,能很好地理解用户什么时候需要调用外部函数,并且生成的调用参数也很规范。这让我们开发者可以更专注于业务逻辑,而不是在和大模型“沟通”上耗费太多精力。

所以,如果你想让你的AI应用不再“纸上谈兵”,而是能真正解决实际问题,那么掌握Function Calling就是你的必修课。接下来,我就手把手带你,用最少的代码,打造一个能真正查询实时天气的智能助手。

2. 核心装备准备:三样东西就能开工

在开始写代码之前,我们得把“工具箱”准备好。整个过程只需要三样核心装备,获取起来都不难。

第一样装备:DeepSeek的API访问权限。 这是我们智能助手的大脑。你需要去DeepSeek的官方平台注册一个账号。注册成功后,在控制台通常能找到“API Keys”或类似的管理页面,创建一个新的API Key。这个Key就像一把钥匙,我们的代码用它来告诉DeepSeek平台:“嗨,是我,我要使用你的模型服务了。” 记得把它复制保存好,一会儿要填到代码里。DeepSeek提供了兼容OpenAI的API接口,这意味着我们可以直接用熟悉的openai这个Python库来调用它,这对开发者来说非常友好。

第二样装备:一个可靠的天气数据源,也就是天气API。 这里有很多选择,比如和风天气、高德天气、OpenWeatherMap等。为了教程的通用性,我们选用一个国际化的服务,比如WeatherAPI。你只需要去它的官网注册,同样获取一个API Key。它的免费套餐通常足够我们做开发和测试了。这个API的作用是提供实时的、准确的天气数据,比如温度、湿度、天气状况(晴、雨、多云等)。这是我们助手获取信息的“眼睛”。

第三样装备:你的本地开发环境。 确保你的电脑上安装了Python(建议3.8以上版本)。我们只需要两个额外的Python库:openairequests。你可以在终端里用一句命令搞定:pip install openai requestsopenai库用来和DeepSeek对话,requests库用来调用天气API获取数据。

把这三样东西备齐——DeepSeek的API Key、天气API的Key、配置好的Python环境——我们的基础工作就完成了。整个过程就像准备一顿大餐,食材和厨具都到位了,接下来就是展示烹饪技巧的时候。

3. 第一步:定义“手”的能力——编写天气查询函数

现在,我们要为AI助手打造它的第一项“手艺”:查询天气。这个函数是独立于大模型存在的,由我们开发者用Python亲手编写。它的职责很单纯:接收一个城市名字,然后去问天气API,拿到数据后,整理成一个清晰的字符串结果。

我来给你写一个扎实的例子,并解释里面的每一个关键点:

import requests

def get_current_weather(city: str) -> str:
    """
    根据城市名称查询实时天气。
    
    参数:
        city (str): 城市名称,例如 "北京"、"Shanghai"。
        
    返回:
        str: 格式化的天气信息字符串,或错误信息。
    """
    # 1. 替换为你在WeatherAPI上获得的真实API Key
    api_key = "YOUR_WEATHERAPI_KEY_HERE"
    
    # 2. WeatherAPI的当前天气接口地址
    base_url = "http://api.weatherapi.com/v1/current.json"
    
    # 3. 构造请求参数
    params = {
        'key': api_key,
        'q': city,        # 查询的城市
        'aqi': 'no'       # 我们不查询空气质量指数,简化返回
    }
    
    try:
        # 4. 发送HTTP GET请求
        response = requests.get(base_url, params=params, timeout=10)
        
        # 5. 检查请求是否成功 (HTTP状态码200表示成功)
        if response.status_code == 200:
            data = response.json()  # 将返回的JSON数据解析为Python字典
            
            # 6. 从复杂的JSON结构中提取我们关心的核心信息
            location_name = data['location']['name']
            current_data = data['current']
            
            temperature_c = current_data['temp_c']          # 摄氏温度
            weather_description = current_data['condition']['text']  # 天气描述
            humidity = current_data['humidity']             # 湿度
            wind_kph = current_data['wind_kph']             # 风速
            
            # 7. 组装一个对人类友好的结果字符串
            result = (
                f"{location_name}的当前天气:{weather_description}。"
                f"气温 {temperature_c}°C,湿度 {humidity}%,风速 {wind_kph}公里/小时。"
            )
            return result
        else:
            # 处理API返回的错误,例如城市不存在、Key无效等
            return f"查询天气失败。API返回状态码:{response.status_code}。请检查城市名称或API配置。"
            
    except requests.exceptions.Timeout:
        return "天气查询请求超时,请稍后重试。"
    except requests.exceptions.RequestException as e:
        return f"网络请求出错:{e}"
    except KeyError as e:
        return f"解析天气数据时遇到意外格式,缺少字段:{e}"

这个函数我刻意写得很详细,并加了完整的异常处理。在实际项目中,异常处理至关重要。网络可能会超时,API可能返回错误格式,用户可能输入一个不存在的城市。一个健壮的函数必须能妥善处理这些情况,并返回明确的错误信息,而不是让程序崩溃。你可以先直接用这个函数,把YOUR_WEATHERAPI_KEY_HERE替换成你自己的Key,在Python环境里单独测试一下:print(get_current_weather(“Beijing”)),看看是否能成功拿到天气信息。这一步确保了我们的“手”本身是灵活好用的。

4. 第二步:告诉AI“手”怎么用——用JSON Schema定义工具

好了,现在我们有了一只灵巧的“手”(get_current_weather函数),但AI大脑(DeepSeek模型)还不知道这只手的存在,更不知道什么时候该伸手,以及伸手时该怎么摆姿势。这一步,我们就是要给大脑一份详细的“工具说明书”。

这份说明书必须用一种模型能理解的、结构化的语言来写,这就是 JSON Schema。我们通过一个叫做 tools 的列表,把说明书传递给DeepSeek。下面我来拆解这份说明书里的每一个字段:

tools = [
    {
        "type": "function",  # 固定写法,告诉模型这是一个函数工具
        "function": {
            "name": "get_current_weather",  # 必须和我们在第三步定义的函数名完全一致
            "description": "当用户想查询某个城市的当前实时天气时,调用此函数。",  # 这是最重要的部分!用自然语言清晰描述函数的用途和调用场景。
            "parameters": {  # 用JSON Schema定义函数需要的参数
                "type": "object",
                "properties": {  # 定义这个对象有哪些属性(即函数的参数)
                    "city": {
                        "type": "string",
                        "description": "需要查询天气的城市名称,例如:北京、上海、New York。请从用户的问题中提取城市名。"  # 告诉模型如何获取这个参数
                    }
                },
                "required": ["city"]  # 声明哪些参数是调用时必须提供的。这里`city`是必填项。
                # 注意:我们没有设置 `additionalProperties: false`,这是非严格模式,允许模型有一定的灵活性。
            }
        }
    }
]

这里有几个我踩过坑的经验点要分享给你:

  1. name字段:一定要和你实际定义的Python函数名一模一样,大小写敏感。模型在决定调用后,返回的会是这个字符串,我们的代码要靠它来找到并执行对应的函数。
  2. description字段:这是灵魂所在。模型完全依靠这段描述来判断“用户现在的问题是不是需要调用这个函数”。所以描述要准确、具体。比如这里强调是“当前实时天气”,如果用户问“明天天气”,模型可能就不会调用这个查实时天气的函数(除非我们定义了另一个查预报的函数)。你可以把它想象成给模型的一道判断题。
  3. parameters描述city参数的描述里,我加了一句“请从用户的问题中提取城市名”。这是一个非常有效的提示(prompt),能引导模型更准确地进行信息抽取。比如用户问“巴黎天气怎么样?”,模型就会知道要把“巴黎”这个字符串提取出来,作为city参数的值。

这份“工具说明书”的质量,直接决定了AI助手是否能做出正确的决策。写得模糊,模型就可能该调用时不调用,或者调用时参数乱填。在后续的测试环节,如果发现模型意图识别不准,第一个要回来检查优化的就是这里的描述。

5. 第三步:组装大脑与手——构建完整的对话流程

现在,我们有了大脑(DeepSeek模型)和手(天气函数及说明书),接下来要设计一套流程,让它们协同工作。这个流程就像一场精心安排的对话,涉及用户、AI助手和外部工具三方。

整个流程的核心步骤,我画了一个简单的示意图帮你理解:

用户提问
    ↓
[DeepSeek模型] 接收问题,分析意图,查看可用的工具(`tools`列表)
    ↓
模型判断:需要调用 `get_current_weather` 吗?
    ├── 是 → 生成结构化调用请求 (如 `{"city": "北京"}`),暂停文本生成
    └── 否 → 直接生成自然语言回答
    ↓ (假设需要调用)
我们的代码收到调用请求,执行真实的 `get_current_weather("北京")` 函数
    ↓
函数返回结果 (如 "北京的当前天气:晴。气温 25°C...")
    ↓
我们将结果作为“工具执行回复”反馈给模型
    ↓
[DeepSeek模型] 收到工具结果,结合原始问题,生成最终的自然语言回答
    ↓
将最终回答呈现给用户

下面就是实现这个流程的完整Python代码。我把关键逻辑都写在了注释里:

import requests
from openai import OpenAI  # 使用兼容OpenAI的库

# 1. 初始化DeepSeek客户端
client = OpenAI(
    api_key="YOUR_DEEPSEEK_API_KEY_HERE",  # 替换成你的DeepSeek API Key
    base_url="https://api.deepseek.com",  # DeepSeek的API端点
)

# 2. 天气查询函数 (同第三步)
def get_current_weather(city: str) -> str:
    # ... (此处省略函数具体实现,和第三步的代码完全一样)
    # 记得替换里面的 WeatherAPI Key
    pass

# 3. 工具定义 (同第四步)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "当用户想查询某个城市的当前实时天气时,调用此函数。",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "需要查询天气的城市名称,例如:北京、上海、New York。请从用户的问题中提取城市名。",
                    }
                },
                "required": ["city"],
            },
        }
    }
]

def chat_with_weather_assistant(user_query: str) -> str:
    """
    与智能天气助手对话的主函数。
    """
    # 初始化对话历史,从用户问题开始
    messages = [{"role": "user", "content": user_query}]
    
    # 第一次调用DeepSeek:让它分析是否需要调用工具
    print("🧠 AI正在思考是否需要查询天气...")
    response = client.chat.completions.create(
        model="deepseek-chat",  # 指定使用DeepSeek模型
        messages=messages,
        tools=tools,  # 关键:传入我们定义的工具列表
        tool_choice="auto",  # 让模型自主决定是否调用工具。也可强制设为 "none"或指定某个工具。
        temperature=0.1,  # 较低的温度使输出更确定,适合工具调用场景
    )
    
    assistant_message = response.choices[0].message
    messages.append(assistant_message)  # 将模型的回复加入对话历史
    
    # 检查模型是否决定调用工具
    if assistant_message.tool_calls:
        print("🔧 AI决定调用外部工具...")
        # 处理每一个工具调用(本例中通常只有一个)
        for tool_call in assistant_message.tool_calls:
            func_name = tool_call.function.name
            func_args = tool_call.function.arguments  # 这是一个JSON字符串
            
            # 安全地解析参数(这里用了json.loads,比eval更安全)
            import json
            arguments = json.loads(func_args)
            city = arguments.get("city")
            
            # 根据函数名,调用我们预先定义好的函数
            if func_name == "get_current_weather":
                tool_result = get_current_weather(city)
            else:
                tool_result = f"错误:未知的函数调用 {func_name}"
            
            # 将工具执行结果以特定格式反馈给模型
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,  # 必须对应之前的调用ID
                "content": tool_result,  # 工具返回的结果
            })
        
        # 第二次调用DeepSeek:让它基于工具结果生成最终回答
        print("🤖 AI正在整合信息,生成最终回答...")
        second_response = client.chat.completions.create(
            model="deepseek-chat",
            messages=messages,  # 此时的messages包含了用户问题、模型调用请求、工具结果
            temperature=0.7,  # 可以调高一点,让最终回答更自然
        )
        final_answer = second_response.choices[0].message.content
        return final_answer
    else:
        # 模型认为不需要调用工具,直接返回它的回答
        print("💬 AI直接回答了问题。")
        return assistant_message.content

# 4. 让我们来试试效果!
if __name__ == "__main__":
    test_questions = [
        "今天北京天气怎么样?",
        "上海和广州现在哪里更热?",  # 注意:这可能需要模型发起两次工具调用
        "帮我写一首关于春天的诗。",  # 与天气无关,不应触发工具调用
        "巴黎现在多少度?",
    ]
    
    for question in test_questions:
        print(f"\n👤 用户: {question}")
        print("-" * 40)
        answer = chat_with_weather_assistant(question)
        print(f"🤖 助手: {answer}")
        print("-" * 40)

这段代码是一个完整的、可运行的智能体核心。我强烈建议你把它复制到本地,填好两个API Key,然后运行看看。观察控制台的打印,你会清晰地看到“AI正在思考...” -> “AI决定调用工具...” -> “AI正在整合信息...” 这个过程,直观地理解Function Calling的运作机制。

6. 效果实测与进阶调优:让你的助手更聪明可靠

代码跑起来后,你可能会遇到一些有趣的情况,这正是调优的起点。我们来分析几个典型场景:

场景一:精准命中。 用户问“北京天气如何?”。模型会轻松识别意图,提取城市“北京”,调用工具,并最终回复:“北京的当前天气:晴。气温 22°C,湿度 35%,风速 10公里/小时。” 完美!

场景二:模糊或复杂查询。 用户问“我下周要去上海和杭州出差,那边天气好吗?”。这里就有挑战了:1. 时间点是“下周”,我们的函数只支持当前天气。2. 涉及两个城市。对于这种情况,目前的助手可能表现不佳。它可能只调用一次查询上海当前天气,或者生成错误的参数。

如何调优?

  1. 增强工具描述:在工具的description里更明确地写上“本函数仅查询当前实时天气,无法查询未来预报或历史天气”。这能帮助模型更好地做决策。
  2. 定义更多工具:我们可以创建第二个函数 get_weather_forecast(city, days),专门查询多日预报,并在tools列表里同时提供这两个工具。模型就能根据用户问题中的“下周”关键词,选择调用预报函数。
  3. 优化参数提取:对于“上海和杭州”,模型可能需要发起并行工具调用。这需要检查你使用的DeepSeek模型版本是否支持parallel_tool_calls=True参数。如果支持,模型可以一次性生成两个工具调用请求,我们的代码需要能同时处理它们。
  4. 处理模型“幻觉”:有时模型可能因为描述不清,调用函数时参数乱写,比如city: “那个很冷的地方”。这时,我们的get_current_weather函数内部的错误处理就会返回友好提示,模型接收到这个错误结果后,可能会在最终回答中告诉用户“无法找到该城市的天气”。

另一个重要参数是tool_choice。在我们的代码里它设为”auto”。你还可以尝试:

  • tool_choice={“type”: “function”, “function”: {“name”: “get_current_weather”}} 来强制模型调用特定函数。
  • tool_choice=”none” 来强制模型不调用任何函数,即使它觉得需要。

实测和调优是一个迭代过程。多准备一些边缘案例进行测试,比如带国家名的城市(“法国巴黎”)、口语化表达(“帝都这天儿咋样?”)、错误拼写等,根据助手的反馈不断打磨工具描述和函数健壮性,你的助手就会变得越来越聪明可靠。

7. 不止于天气:Function Calling的无限可能

一旦掌握了天气查询这个范例,你就打开了AI应用开发的一扇新大门。Function Calling的本质是让大模型成为所有数字服务的统一、智能的交互界面。它的应用场景远远不止查天气。

想象一下这些场景:

  • 智能电商客服:用户说“帮我找一款500元以内的无线蓝牙耳机,要续航长的”。你可以定义search_products(category, max_price, keyword)函数,连接你的商品数据库。模型理解需求后,调用函数搜索,再将结果整理成“为您找到以下几款符合要求的耳机...”这样的自然语言。
  • 个人效率助手:用户说“把我明天上午十点的会议记到日历里”。定义create_calendar_event(title, start_time, end_time)函数,连接Google Calendar或飞书日历API。模型提取时间、事件信息,调用函数创建日程。
  • 数据分析助手:用户上传一张销售表格图片,然后问“Q3季度哪个产品销量最高?”。你可以结合视觉模型分析图片提取数据,然后定义query_sales_data(metric, period, product)函数,让模型调用它来获取具体分析结果。

技术架构上的扩展:

  1. 工具路由:当你有几十个函数时,不可能把所有定义都塞在一次请求里。你可以设计一个“工具路由层”。先让模型根据用户意图,选择一个大的工具类别,然后再动态加载该类别的具体工具列表进行第二轮调用。
  2. 结果后处理:工具返回的可能是复杂的JSON。你可以在函数内部或模型回复前,增加一个后处理步骤,比如将数据转换成更清晰的图表描述、提取关键指标等,让模型的最终回答信息量更大。
  3. 与智能体(Agent)框架结合:像Dify、LangChain这样的框架,提供了更高层级的抽象。它们可以帮你管理对话状态、工具集,甚至实现自动迭代(让模型根据上一个工具的结果决定下一步做什么)。这非常适合构建复杂的多步骤任务助手。

从我自己的项目经验来看,初期最容易犯的错误是试图让一个函数做太多事,或者工具描述写得过于宽泛。我的建议是:保持工具的小而专。一个函数只做好一件事,并用清晰、无歧义的语言描述它。这样模型的判断准确率最高。先从一个工具开始,跑通闭环,获得正反馈,然后再逐步添加更多工具,你的智能助手的能力就会像搭积木一样不断增长。

Function Calling这项技术,真正让大模型从“时代的旁观者”变成了“世界的参与者”。它不再只是谈论世界,而是可以操作和影响世界。希望你通过这个天气助手的实践,已经感受到了这种魔力。接下来,就大胆地去为你关心的领域和问题,打造专属的智能解决方案吧。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐