大模型Function Calling实战:从原理到天气查询应用开发

📅 2026/7/15 7:36:44
大模型Function Calling实战:从原理到天气查询应用开发
在实际大模型应用开发中我们经常遇到一个核心问题如何让大语言模型LLM不仅能生成文本还能执行结构化操作比如查询数据库、调用外部 API、执行计算或控制设备这就是 Function Calling函数调用要解决的核心问题。它本质上是一种机制让 LLM 能够理解开发者的意图并输出结构化数据来触发外部函数或工具而不是仅仅返回一段自然语言。很多开发者第一次接触 Function Calling 时容易把它简单理解为“让 AI 写代码并执行”但实际上它的设计更精巧LLM 本身并不直接执行任何函数它只负责“理解”和“决策”。当用户输入一个需求时LLM 会根据你预先定义的函数工具列表判断是否需要调用某个函数以及调用时应该传入什么参数。然后你的应用程序会收到一个结构化的调用请求由你的代码安全地执行对应的函数最后将执行结果返回给 LLM让 LLM 基于结果生成最终回答。这种方式既发挥了 LLM 的理解和推理能力又避免了让它直接操作系统资源的安全风险。下面我们通过原理、流程、实战和排查四个部分彻底讲清楚 Function Calling。1. Function Calling 的核心原理与工作流程要理解 Function Calling不能只看单次交互而要看清楚整个对话过程中LLM、你的应用程序、外部函数之间是如何协作的。1.1 为什么需要 Function Calling在没有 Function Calling 之前如果我们想让 LLM 查询天气通常的做法是让 LLM 生成一段包含城市名称的自然语言然后开发者用正则表达式或文本解析的方式从 LLM 的回复中提取城市名再去调用天气 API。这种方式存在几个问题解析不可靠LLM 的回复格式可能变化正则表达式容易失效。意图判断困难如何准确判断用户是想查询天气而不是闲聊“今天天气不错”多步骤操作复杂如果需要连续调用多个函数文本交互的方式很难维护状态。Function Calling 通过结构化数据解决了这些问题。LLM 不再需要生成自然语言来描述“请调用天气 API 查询北京天气”而是直接输出{ function_name: get_weather, arguments: {city: 北京} }你的应用程序收到这个结构化请求后就可以直接调用对应的get_weather函数传入参数city北京。1.2 Function Calling 的完整工作流程一次完整的 Function Calling 交互通常包含以下步骤定义函数工具开发者预先定义一组可用的函数包括函数名称、描述、参数列表和参数类型。用户输入用户提出需求比如“今天北京天气怎么样”LLM 决策LLM 根据函数定义和用户输入判断是否需要调用函数、调用哪个函数、参数值是什么。结构化响应LLM 返回结构化调用请求而不是自然语言回答。本地执行函数你的应用程序解析调用请求安全地执行对应的本地函数或 API 调用。结果返回 LLM将函数执行结果返回给 LLM。生成最终回答LLM 基于函数执行结果生成面向用户的自然语言回答。这个流程的关键在于LLM 只负责“思考”判断是否需要调用、如何调用不负责“执行”。执行权始终在开发者手中这保证了系统的安全性和可控性。1.3 函数定义的标准格式要让 LLM 理解你的函数需要按照特定的格式描述函数。不同的大模型平台格式略有差异但核心要素相同{ name: get_weather, description: 获取指定城市的当前天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称如北京、上海 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位摄氏度或华氏度, default: celsius } }, required: [city] } }每个字段都有明确的作用name函数标识符LLM 通过这个名称选择要调用的函数。description帮助 LLM 理解这个函数的用途描述要清晰准确。parameters定义参数的 JSON Schema包括类型、描述、是否必需、枚举值等。LLM 会根据这些描述来学习你的函数能力。如果描述不准确LLM 可能做出错误的调用决策。2. 环境准备与依赖配置在实际项目中实现 Function Calling需要准备开发环境、选择合适的大模型平台、配置相应的 SDK。2.1 开发环境要求Function Calling 对开发环境没有特殊要求但建议使用较新的 Python 版本# 检查 Python 版本 python --version # Python 3.8 # 创建虚拟环境推荐 python -m venv function_calling_env source function_calling_env/bin/activate # Linux/Mac # 或 function_calling_env\Scripts\activate # Windows # 安装核心依赖 pip install openai如果使用其他大模型平台如智谱 AI、百度文心等需要安装对应的 SDK# 智谱 AI pip install zhipuai # 百度千帆 pip install qianfan2.2 大模型平台选择与配置目前主流的大模型平台都支持 Function Calling 功能但在具体实现上有些差异平台SDK支持情况关键差异OpenAIopenai全面支持最早推出文档最完善智谱 AIzhipuai支持针对中文优化参数略有不同百度文心qianfan支持需要配置安全机制通义千问dashscope支持阿里系生态集成以 OpenAI 为例需要配置 API Keyimport os from openai import OpenAI # 方式1设置环境变量 os.environ[OPENAI_API_KEY] your-api-key-here # 方式2直接传入客户端 client OpenAI(api_keyyour-api-key-here)国内平台通常需要额外的配置比如智谱 AIimport zhipuai # 配置 API Key zhipuai.api_key your-zhipuai-api-key2.3 项目结构设计一个典型的 Function Calling 项目应该包含以下模块function_calling_project/ ├── main.py # 主程序入口 ├── tools/ # 函数工具模块 │ ├── __init__.py │ ├── weather_tool.py # 天气查询工具 │ ├── calculator_tool.py # 计算工具 │ └── database_tool.py # 数据库查询工具 ├── config/ # 配置管理 │ ├── __init__.py │ └── settings.py # API密钥等配置 ├── models/ # 数据模型 │ ├── __init__.py │ └── schemas.py # 请求响应模型 └── requirements.txt # 依赖列表这种模块化设计便于维护和扩展当需要增加新的函数工具时只需要在tools目录下添加新的模块即可。3. 实战从零实现天气查询 Function Calling下面我们通过一个完整的天气查询案例演示如何实现 Function Calling。这个案例包含函数定义、LLM 调用、函数执行和结果处理的全流程。3.1 定义天气查询函数首先我们需要定义一个获取天气的函数以及对应的函数描述import requests import json def get_weather(city: str, unit: str celsius) - str: 获取指定城市的天气信息 Args: city: 城市名称 unit: 温度单位celsius摄氏度或 fahrenheit华氏度 Returns: str: 天气信息描述 # 这里使用模拟数据实际项目中可以接入真实天气API weather_data { 北京: {temperature: 25, condition: 晴, humidity: 40}, 上海: {temperature: 28, condition: 多云, humidity: 60}, 广州: {temperature: 32, condition: 雨, humidity: 80} } if city not in weather_data: return f找不到城市 {city} 的天气信息 data weather_data[city] temp data[temperature] # 单位转换 if unit fahrenheit: temp temp * 9/5 32 unit_str 华氏度 else: unit_str 摄氏度 return f{city}天气{data[condition]}温度{temp}{unit_str}湿度{data[humidity]}% # 函数描述用于告诉LLM这个函数的功能 weather_function { name: get_weather, description: 获取指定城市的当前天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称如北京、上海、广州 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位摄氏度或华氏度, default: celsius } }, required: [city] } }3.2 实现完整的 Function Calling 流程接下来实现主程序处理用户输入、调用 LLM、执行函数并生成最终回答import json from openai import OpenAI class FunctionCallingAgent: def __init__(self, api_key): self.client OpenAI(api_keyapi_key) self.available_functions { get_weather: get_weather } self.tools [weather_function] def process_user_query(self, user_input): 处理用户查询的完整流程 # 第一步调用LLM让LLM决定是否需要调用函数 response self.client.chat.completions.create( modelgpt-3.5-turbo, messages[{role: user, content: user_input}], toolsself.tools, tool_choiceauto # 让LLM自动决定是否调用函数 ) message response.choices[0].message print(fLLM原始响应: {message}) # 第二步检查LLM是否要求调用函数 if message.tool_calls: # 处理所有函数调用请求 for tool_call in message.tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) print(fLLM要求调用函数: {function_name}, 参数: {function_args}) # 第三步执行对应的函数 if function_name in self.available_functions: function_to_call self.available_functions[function_name] function_response function_to_call(**function_args) print(f函数执行结果: {function_response}) # 第四步将函数结果返回给LLM让LLM生成最终回答 second_response self.client.chat.completions.create( modelgpt-3.5-turbo, messages[ {role: user, content: user_input}, message, { role: tool, tool_call_id: tool_call.id, content: function_response } ] ) return second_response.choices[0].message.content else: return f错误未知函数 {function_name} else: # 如果不需要调用函数直接返回LLM的回答 return message.content # 使用示例 if __name__ __main__: agent FunctionCallingAgent(api_keyyour-api-key) # 测试用例 test_queries [ 今天北京天气怎么样, 查询上海的天气用华氏度显示, 今天天气不错 # 这个应该不会触发函数调用 ] for query in test_queries: print(f\n用户查询: {query}) result agent.process_user_query(query) print(f最终回答: {result})3.3 关键代码解析这个实现中有几个关键点需要特别注意1. LLM 调用配置response self.client.chat.completions.create( modelgpt-3.5-turbo, messages[{role: user, content: user_input}], toolsself.tools, # 传入函数定义 tool_choiceauto # 让LLM自动决定 )tool_choice参数控制 LLM 的函数调用行为autoLLM 自主决定是否调用函数none禁止调用函数{type: function, function: {name: get_weather}}强制调用指定函数2. 函数调用结果处理{ role: tool, tool_call_id: tool_call.id, # 必须匹配之前的调用ID content: function_response # 函数执行结果 }返回函数结果时必须提供正确的tool_call_id这样 LLM 才能将结果与之前的调用请求关联起来。3. 错误处理机制实际项目中需要添加完善的错误处理try: function_args json.loads(tool_call.function.arguments) except json.JSONDecodeError as e: return f参数解析错误: {e} if function_name not in self.available_functions: return f不支持的函数: {function_name}4. 运行验证与结果分析实现代码后我们需要验证 Function Calling 是否按预期工作分析不同场景下的行为差异。4.1 正常功能验证运行测试用例观察输出结果用户查询: 今天北京天气怎么样 LLM原始响应: ChatCompletionMessage(contentNone, roleassistant, function_callNone, tool_calls[ChatCompletionMessageToolCall(idcall_abc123, functionFunction(arguments{city: 北京}, nameget_weather), typefunction)]) LLM要求调用函数: get_weather, 参数: {city: 北京} 函数执行结果: 北京天气晴温度25摄氏度湿度40% 最终回答: 今天北京天气晴朗温度25摄氏度湿度40%是个好天气从这个输出可以看到完整的调用链用户输入今天北京天气怎么样LLM 识别出需要调用get_weather函数参数为{city: 北京}本地执行get_weather函数返回天气信息LLM 基于天气信息生成友好的最终回答4.2 边界情况测试测试一些边界情况验证系统的健壮性# 测试不明确的需求 query 今天天气不错 result agent.process_user_query(query) # 预期不调用函数直接生成闲聊回复 # 测试参数缺失的情况 query 查询天气 # 没有指定城市 result agent.process_user_query(query) # 预期LLM应该询问具体城市而不是直接调用函数 # 测试不支持的函数 # 如果用户提到一个我们没有定义的函数LLM应该忽略或说明不支持4.3 多函数调用场景当定义多个函数时测试 LLM 如何选择# 添加计算器函数 calculator_function { name: calculate, description: 执行数学计算, parameters: { type: object, properties: { expression: {type: string, description: 数学表达式如 23*4} }, required: [expression] } } def calculate(expression: str) - str: 执行数学计算 try: result eval(expression) # 实际项目中要用更安全的方式 return f{expression} {result} except Exception as e: return f计算错误: {e} # 测试LLM的函数选择能力 queries [ 北京天气怎么样然后计算 25*4, # 应该调用两个函数 先计算100/5再告诉我上海天气 # 测试顺序理解 ]5. 常见问题排查与解决方案在实际开发中Function Calling 可能会遇到各种问题。下面列出常见问题及解决方法。5.1 函数调用不触发问题现象明明定义了函数但 LLM 就是不调用。可能原因函数描述不够清晰LLM 无法理解何时该调用用户输入意图不明确API 参数配置错误排查步骤检查函数描述是否准确描述了使用场景确认tool_choice参数不是none查看 LLM 的完整响应确认是否理解了用户意图解决方案# 改进函数描述 weather_function { name: get_weather, description: 当用户询问天气、气温、湿度、天气预报时调用此函数, # 更明确的触发条件 parameters: {...} }5.2 参数解析错误问题现象LLM 要求调用函数但参数解析失败。可能原因参数格式不符合 JSON 标准参数类型不匹配必需参数缺失排查步骤try: function_args json.loads(tool_call.function.arguments) # 验证必需参数是否存在 required_params weather_function[parameters][required] for param in required_params: if param not in function_args: print(f缺失必需参数: {param}) except json.JSONDecodeError as e: print(fJSON解析错误: {e}) print(f原始参数: {tool_call.function.arguments})解决方案 在函数描述中明确参数要求并添加参数验证def get_weather(city: str, unit: str celsius) - str: # 参数验证 if not city or not isinstance(city, str): return 错误城市名称不能为空 if unit not in [celsius, fahrenheit]: return 错误不支持的温度单位 # ... 其余逻辑5.3 函数执行失败处理问题现象函数执行过程中出现异常。解决方案完善的错误处理和重试机制def safe_function_call(function, **kwargs): 安全的函数调用包装器 try: result function(**kwargs) return result except Exception as e: error_msg f函数执行失败: {str(e)} print(error_msg) return error_msg # 使用方式 function_response safe_function_call(function_to_call, **function_args)5.4 性能优化建议当函数调用频繁时需要考虑性能优化缓存函数结果对相同参数的函数调用进行缓存批量处理合并多个相关查询减少 LLM 调用次数超时控制为函数执行设置超时时间import functools import time from threading import Thread, Event functools.lru_cache(maxsize100) def cached_get_weather(city: str, unit: str celsius) - str: 带缓存的天气查询 return get_weather(city, unit) def timeout_function_call(function, timeout10, **kwargs): 带超时的函数调用 result [None] event Event() def worker(): result[0] function(**kwargs) event.set() thread Thread(targetworker) thread.start() event.wait(timeout) if not event.is_set(): return 函数执行超时 return result[0]6. 生产环境最佳实践将 Function Calling 应用到生产环境时需要考虑更多工程化问题。6.1 安全考虑输入验证对所有用户输入和函数参数进行严格验证import re def validate_city_name(city: str) - bool: 验证城市名称是否合法 if not city or len(city) 20: return False # 只允许中文、字母、数字和常见符号 pattern r^[\w\u4e00-\u9fa5\-\.\s]$ return bool(re.match(pattern, city))函数权限控制根据用户身份控制可用的函数class SecureFunctionCallingAgent: def __init__(self, user_role): self.user_role user_role self.function_permissions { user: [get_weather], admin: [get_weather, calculate, database_query] } def get_available_functions(self): return self.function_permissions.get(self.user_role, [])6.2 监控与日志完善的日志记录有助于问题排查import logging logging.basicFormatter %(asctime)s - %(name)s - %(levelname)s - %(message)s logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class LoggingFunctionCallingAgent(FunctionCallingAgent): def process_user_query(self, user_input): logger.info(f处理用户查询: {user_input}) try: result super().process_user_query(user_input) logger.info(查询处理成功) return result except Exception as e: logger.error(f查询处理失败: {e}) return 系统繁忙请稍后重试6.3 配置管理生产环境需要将配置外置化# config/settings.py import os from dataclasses import dataclass dataclass class Settings: openai_api_key: str os.getenv(OPENAI_API_KEY) model_name: str os.getenv(MODEL_NAME, gpt-3.5-turbo) request_timeout: int int(os.getenv(REQUEST_TIMEOUT, 30)) classmethod def validate(cls): if not cls.openai_api_key: raise ValueError(OPENAI_API_KEY 环境变量未设置) # 使用配置 settings Settings() settings.validate()6.4 扩展方向掌握了基础 Function Calling 后可以进一步探索并行函数调用让 LLM 同时调用多个函数动态函数注册运行时动态添加或移除函数函数组合让 LLM 学会按顺序调用多个函数完成复杂任务流式响应在函数执行过程中逐步返回结果Function Calling 的真正价值在于将 LLM 的推理能力与现有系统无缝集成。通过良好的架构设计可以构建出既智能又可靠的 AI 应用系统。在实际项目中建议从简单场景开始逐步验证每个环节的稳定性和效果再扩展到更复杂的业务逻辑。