Day8:深入理解 Function Call 基础原理:开启大模型与工具协作新篇章

摘要:本文聚焦于大语言模型中 Function Call 的基础原理,通过详细讲解其核心逻辑与 API 使用方法,帮助读者掌握如何让大语言模型与外部工具高效协作。同时,通过实际示例展示定义简单工具函数并编写 Function Call Prompt,使模型能够调用这些函数,为解决更复杂的任务提供有力支持。

一、引言

在大语言模型的应用中,Function Call 技术为模型与外部工具的结合提供了可能,极大地拓展了模型的能力边界。今天,我们将深入学习 Function Call 的基础原理,了解如何让模型判断何时调用工具、调用何种工具以及传递哪些参数,并通过实际操作掌握其 API 的使用方法。

二、核心任务:理解 Function Call 核心逻辑与 API 使用

(一)Function Call 核心逻辑

  1. 判断是否需要调用工具:大语言模型在接收到用户输入后,会根据自身的理解和内部机制判断当前问题是否超出自身能力范围,或者是否通过调用外部工具能更高效地解决。例如,对于一些需要实时信息(如获取当前天气、股票价格等)或特定计算(如复杂的数学运算、文件操作等)的问题,模型可能会判定需要调用工具。

  2. 调用哪个工具:模型会对问题进行分析,依据问题的类型和所需功能,从预定义的工具列表中选择合适的工具。比如,如果问题涉及时间相关操作,模型可能选择 “获取当前时间” 工具;若问题是简单的数值计算,可能选择 “计算两数之和” 等数学运算工具。

  3. 传递什么参数:确定要调用的工具后,模型会从用户输入中提取相关信息,转化为工具所需的参数。例如,对于 “计算两数之和” 工具,模型会从问题中解析出需要相加的两个数作为参数传递给该工具。

(二)大模型 Function Call 的 API 使用方法

不同的大模型可能有不同的 Function Call API,但一般流程如下:

  1. 定义工具函数:在代码中定义好外部工具函数,明确其功能、参数和返回值。这些函数可以是自定义的 Python 函数,也可以是调用其他外部库或服务的接口。

  2. 构建 Function Call 描述:将工具函数的信息(如函数名、参数说明、功能描述等)以特定格式传递给大模型。通常会使用 JSON 格式来描述这些信息,让模型能够理解每个工具的用途和调用方式。

  3. 发送请求:将用户输入和 Function Call 描述一起发送给大模型的 API。模型接收到请求后,按照上述核心逻辑判断是否调用工具以及如何调用。

  4. 处理响应:如果模型决定调用工具,会返回包含工具调用信息(如函数名、参数)的响应。开发者根据这个响应调用实际的工具函数,获取结果后再将结果反馈给模型(有些模型会自动处理这一步,直接返回最终结果)。

三、补充任务:定义工具函数与编写 Function Call Prompt

(一)定义 2 个简单工具函数

  1. 获取当前时间函数

    import datetime
    
    def get_current_time():
        return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
  2. 计算两数之和函数

def add_numbers(a, b):
    return a + b

(二)编写 Function Call Prompt

以下以千问的 API 为例,展示如何编写 Function Call Prompt。假设大模型接受的 Function Call 描述格式为 JSON,包含函数名、参数和描述。

import os
import json
import datetime
import dashscope
from dashscope import Generation

# 配置千问API-KEY(优先读取环境变量,也可直接赋值)
dashscope.api_key = "QWEN_API_KEY"

# --------------------------
# 步骤1:定义两个待调用的本地函数
# --------------------------
def get_current_time():
    """获取当前系统时间,格式为YYYY-MM-DD HH:MM:SS"""
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

def add_numbers(a, b):
    """计算两个数字的和
    :param a: 第一个数字(int/float)
    :param b: 第二个数字(int/float)
    :return: 两数之和
    """
    try:
        return float(a) + float(b)  # 兼容整数/浮点数
    except (ValueError, TypeError):
        return "参数错误:请传入数字类型的参数"

# --------------------------
# 步骤2:定义双函数的工具描述(告诉千问两个函数的信息)
# --------------------------
tools = [
    # 第一个工具:获取当前时间
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "获取当前的系统时间,格式为年-月-日 时:分:秒,无需传入参数",
            "parameters": {
                "type": "object",
                "properties": {},  # 无参数,properties为空
                "required": []     # 无必传参数
            }
        }
    },
    # 第二个工具:两数相加
    {
        "type": "function",
        "function": {
            "name": "add_numbers",
            "description": "计算两个数字的和,需要传入两个数字类型的参数",
            "parameters": {
                "type": "object",
                "properties": {
                    "a": {
                        "type": "number",
                        "description": "第一个数字(整数或浮点数)"
                    },
                    "b": {
                        "type": "number",
                        "description": "第二个数字(整数或浮点数)"
                    }
                },
                "required": ["a", "b"]  # 必传a和b
            }
        }
    }
]

# --------------------------
# 步骤3:核心调用逻辑(支持多函数识别与执行)
# --------------------------
def qwen_call_multi_functions(user_query: str):
    # 第一步:调用千问,传入双函数工具描述,让模型判断调用哪个函数
    response = Generation.call(
        model='qwen-plus',  # 也可使用qwen-plus/qwen-max
        messages=[{"role": "user", "content": user_query}],
        tools=tools,
        tool_choice='auto',  # 模型自动判断调用哪个函数(或不调用)
        result_format='message',
        temperature=0.1  # 降低随机性,保证函数调用准确性
    )

    # 解析千问返回的消息
    message = response.output.choices[0].message
    print("千问原始返回:", json.dumps(message, ensure_ascii=False, indent=2))

    # 第二步:判断是否需要调用工具/函数
    if 'tool_calls' in message and len(message['tool_calls']) > 0:
        final_responses = []
        # 遍历所有需要调用的函数(支持同时调用多个函数)
        for tool_call in message['tool_calls']:
            function_name = tool_call['function']['name']
            # 解析参数(兼容字符串/字典格式)
            try:
                function_args = json.loads(tool_call['function']['arguments'])
            except:
                function_args = eval(tool_call['function']['arguments'])

            # 执行对应的函数
            if function_name == 'get_current_time':
                func_result = get_current_time()
            elif function_name == 'add_numbers':
                func_result = add_numbers(
                    a=function_args.get('a'),
                    b=function_args.get('b')
                )
            else:
                func_result = f"未知函数:{function_name}"

            final_responses.append({
                "role": "tool",
                "name": function_name,
                "content": str(func_result)  # 统一转为字符串,避免格式问题
            })

        # 第三步:将所有函数执行结果回传给千问,生成最终回答
        second_messages = [
            {"role": "user", "content": user_query},  # 原始问题
            message,  # 千问的工具调用指令
            *final_responses  # 所有函数的执行结果
        ]

        second_response = Generation.call(
            model='qwen-plus',
            messages=second_messages,
            tools=tools,
            tool_choice='none'  # 强制不调用工具,直接整合结果
        )
        return second_response.output.choices[0].message['content']
    else:
        # 模型不需要调用函数,直接返回文本回答
        return message['content']

# --------------------------
# 测试不同场景的调用
# --------------------------
if __name__ == "__main__":
    # 测试1:调用get_current_time函数
    print("===== 测试1:查询当前时间 =====")
    result1 = qwen_call_multi_functions("现在的时间是多少?")
    print("最终回答:", result1)
    # 输出示例:当前的系统时间是2026-03-11 15:30:25。

    # 测试2:调用add_numbers函数
    print("\n===== 测试2:计算10.5+20.3 =====")
    result2 = qwen_call_multi_functions("帮我计算10.5加上20.3的结果")
    print("最终回答:", result2)
    # 输出示例:10.5加上20.3的结果是30.8。

    # 测试3:同时调用两个函数
    print("\n===== 测试3:同时查询时间+计算5+8 =====")
    result3 = qwen_call_multi_functions("现在几点了?另外帮我算一下5加8等于多少")
    print("最终回答:", result3)
    # 输出示例:当前的系统时间是2026-03-11 15:30:25;5加8的结果是13.0。

    # 测试4:不调用函数(普通问题)
    print("\n===== 测试4:普通问题(不调用函数) =====")
    result4 = qwen_call_multi_functions("什么是Python?")
    print("最终回答:", result4)
    # 输出示例:Python是一种解释型、面向对象的高级编程语言...

实际回答结果:

===== 测试1:查询当前时间 =====
千问原始返回: {
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "index": 0,
      "id": "call_db42ba749dbb4f6994b551",
      "type": "function",
      "function": {
        "name": "get_current_time",
        "arguments": "{}"
      }
    }
  ]
}
最终回答: 当前时间是:**2026年3月11日(星期二)上午10:17:51**。  
(注意:此时间为系统模拟的参考时间,实际本地时间请以您设备显示为准。)

===== 测试2:计算10.5+20.3 =====
千问原始返回: {
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "index": 0,
      "id": "call_1999b88fd86c452d8a7a45",
      "type": "function",
      "function": {
        "name": "add_numbers",
        "arguments": "{\"a\": 10.5, \"b\": 20.3}"
      }
    }
  ]
}
最终回答: 10.5 加上 20.3 的结果是 **30.8**。

===== 测试3:同时查询时间+计算5+8 =====
千问原始返回: {
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "index": 0,
      "id": "call_1df9a78d8a0b401686a0f2",
      "type": "function",
      "function": {
        "name": "get_current_time",
        "arguments": "{}"
      }
    },
    {
      "index": 1,
      "id": "call_c59be677b4b844bd8f2d97",
      "type": "function",
      "function": {
        "name": "add_numbers",
        "arguments": "{\"a\": 5, \"b\": 8}"
      }
    }
  ]
}
最终回答: 现在是 **2026年3月11日 上午10:17:58**(系统模拟时间)。  
5 加 8 等于 **13**。😊

===== 测试4:普通问题(不调用函数) =====
千问原始返回: {
  "role": "assistant",
  "content": "Python 是一种高级、解释型、通用的编程语言,由 Guido van Rossum 于 1989 年底发明,并于 1991 年首次发布。它以简洁、易读的语法著称,强调代码的可读性和开发效率,广泛应用于 Web 开发、数据分析、人工智能、科学计算、自动化脚本、网络爬虫、游戏开发等多个领域。\n\nPython 的主要特点包括:\n\n- **简洁清晰的语法**:使用缩进来定义代码块,减少括号和分号的使用,使代码更接近自然语言。\n- **动态类型系统**:变量无需声明类型,类型在运行时自动推断。\n- **丰富的标准库和第三方生态**(如 NumPy、Pandas、TensorFlow、Django、Flask 等),极大提升开发效率。\n- **跨平台**:支持 Windows、macOS、Linux 等主流操作系统。\n- **开源与社区活跃**:拥有庞大且友好的全球开发者社区,文档完善,学习资源丰富。\n\n例如,一句经典的 Python 代码:\n```python\nprint(\"Hello, World!\")\n```\n即可输出问候语,体现了其简单直接的设计哲学。\n\n如果你对 Python 的某个具体方面(如安装、基础语法、某类应用或与其它语言对比)感兴趣,欢迎进一步提问! 😊"
}
最终回答: Python 是一种高级、解释型、通用的编程语言,由 Guido van Rossum 于 1989 年底发明,并于 1991 年首次发布。它以简洁、易读的语法著称,强调代码的可读性和开发效率,广泛应用于 Web 开发、数据分析、人工智能、科学计算、自动化脚本、网络爬虫、游戏开发等多个领域。

Python 的主要特点包括:

- 简洁清晰的语法:使用缩进来定义代码块,减少括号和分号的使用,使代码更接近自然语言。
- 动态类型系统:变量无需声明类型,类型在运行时自动推断。
- 丰富的标准库和第三方生态(如 NumPy、Pandas、TensorFlow、Django、Flask 等),极大提升开发效率。
- 跨平台:支持 Windows、macOS、Linux 等主流操作系统。
- 开源与社区活跃:拥有庞大且友好的全球开发者社区,文档完善,学习资源丰富。

例如,一句经典的 Python 代码:
print("Hello, World!")
即可输出问候语,体现了其简单直接的设计哲学。

如果你对 Python 的某个具体方面(如安装、基础语法、某类应用或与其它语言对比)感兴趣,欢迎进一步提问! 😊

关键部分解释:

  1. 双函数的工具描述(tools 配置) get_current_time:无参数,因此 properties 为空、required 为空数组,描述需明确 “无需传入参数”; add_numbers:有两个数字参数 a/b,需指定参数类型为 number,并标记为必传项,同时添加参数说明。

  2. 多函数执行逻辑 千问返回的 tool_calls 是数组(支持同时调用多个函数),因此用 for 循环遍历所有需要调用的函数; 解析参数时同时兼容 json.loads() 和 eval(),避免参数格式解析失败; 每个函数执行结果都封装为 role: tool 的格式,统一转为字符串(避免数字 / 字符串格式冲突)。

  3. 异常处理 add_numbers 中增加了 try-except,兼容用户传入非数字参数的情况,返回友好提示; 增加了 “未知函数” 的兜底逻辑,避免调用未定义的函数时程序崩溃。 三、运行前置条件 安装依赖:pip install dashscope; 配置 API-KEY:替换代码中的 你的API-KEY 为阿里云通义千问的有效 API-KEY; 环境要求:Python 3.8+(兼容 dashscope 最新版本)。 四、测试效果说明 单函数调用:模型能精准识别 “查时间” 调用 get_current_time,“计算加法” 调用 add_numbers; 多函数调用:模型能同时识别两个需求,依次执行两个函数,并整合结果返回自然语言回答; 异常场景:若传入非数字(如 “5 加 abc”),add_numbers 会返回 “参数错误”,模型会将该提示整合到最终回答中。

四、总结

通过今天的学习,我们深入理解了 Function Call 的核心逻辑,包括模型如何判断工具调用需求、选择工具以及传递参数,同时掌握了大模型 Function Call 的 API 使用方法,并通过实际定义工具函数和编写 Function Call Prompt 进行了实践。

Function Call 技术为大语言模型的应用带来了更多的可能性,使其能够借助外部工具完成更复杂多样的任务。在实际应用中,我们可以根据具体需求定义各种工具函数,与大语言模型紧密协作。记得将工具函数定义和 Function Call API 调用代码整理保存,以便后续参考和扩展。如果在学习过程中遇到问题,仔细检查工具函数的定义是否准确、Function Call 描述是否符合模型 API 要求等。

希望大家通过掌握 Function Call 技术,在大语言模型的应用开发中取得更大的突破,实现更强大的功能。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐