引言:从“大脑”到“手脚”的进化
我们常常惊叹于大模型的博学与智慧,它能写诗、编程、解答难题,像一个无所不知的“大脑”。但这个大脑始终被困在数字世界的象牙塔里,它知道的仅限于它被训练时“喂”的数据,无法感知实时天气,不能操作我的电脑,更没法帮我订一张机票。
函数调用的出现,正是为了给这个“大脑”装上“手脚”。它让大模型不再只是空想,而是学会了使用工具,可以行动,真正地融入并改变我们的现实世界。
今天,我们就来彻底搞懂它。
第一部分:理论篇——函数调用的核心原理
1.1 什么是函数调用
简单来说,函数调用是大模型与外部世界沟通的“翻译官”和“调度员”。
-
传统模式: 用户输入 -> 模型思考 -> 模型输出文本回答。
-
函数调用模式: 用户输入 -> 模型思考 -> 模型决定调用哪个函数 -> 系统执行函数 -> 将结果返回给模型 -> 模型整合结果,生成最终回答给用户。
在这个过程中,模型并不亲自执行函数(它没有这个能力),而是生成一个结构化的请求,告诉系统:“请帮我执行某某某函数,参数是啥啥啥。”
1.2 它是如何工作的?关键三步走
这个过程的核心是一个清晰的交互流程:
-
定义函数(工具清单): 首先,我们开发者需要像准备“工具清单”一样,将可供调用的函数(如
get_weather(location),search_database(query))及其详细的描述、参数格式,通过系统提示词告知大模型。 -
模型决策与请求(大脑下指令): 当用户提出请求(如“北京今天天气怎么样?”),大模型会结合对话上下文和“工具清单”进行思考。如果发现需要调用函数,它就不会直接生成回答,而是输出一个标准的 函数调用请求,例如:
{"name": "get_weather", "arguments": {"location": "北京"}}。 -
执行与回复(动手并汇报): 我们的程序接收到这个请求后,会去真正执行
get_weather("北京")这个函数(比如调用一个天气API),拿到真实的数据(如{“temp": 25, "condition": "晴"})。然后,将这个数据结果重新塞回给大模型。 -
最终生成(总结汇报): 大模型拿到了真实数据,它会像一个秘书一样,将枯燥的数据组织成自然、人性化的语言回复给用户:“北京今天天气晴朗,气温25摄氏度,是个出门的好天气。”
核心价值: 通过这种方式,大模型的能力边界被无限拓展了。它不再受限于训练数据,可以处理实时、私域、需要具体操作的任务。
第二部分:实战篇——手把手教你实现函数调用
让我们通过两个经典案例来感受代码是如何实现的。
在开始实战前,我们先进行环境配置。
2.1 创建 .env 配置文件
本文使用的是大模型聚合平台,飞机
# 聚合平台服务商地址
API_BASE_URL=https://api.ufunai.cn/v1
API_KEY=sk-xxxxx
# 数据库路径
DATABASE_PATH=data/company.db
2.2 创建配置加载模块 config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
"""配置类,从环境变量读取配置"""
# API配置
API_BASE_URL = os.getenv('API_BASE_URL')
API_KEY = os.getenv('API_KEY')
# 数据库配置
DATABASE_PATH = os.getenv('DATABASE_PATH', 'data/company.db')
@classmethod
def validate_config(cls):
"""验证必要配置是否存在"""
if not cls.API_BASE_URL:
raise ValueError("API_BASE_URL 未配置,请在 .env 文件中设置")
if not cls.API_KEY:
raise ValueError("API_KEY 未配置,请在 .env 文件中设置")
print("配置加载成功!")
print(f"API Base URL: {cls.API_BASE_URL}")
# 初始化时验证配置
Config.validate_config()
2.3 创建模型调用模块api_client.py
import requests
import json
from config import Config
class OpenAICompatibleClient:
"""OpenAI兼容API的通用客户端"""
def __init__(self):
self.base_url = Config.API_BASE_URL
self.api_key = Config.API_KEY
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
def chat_completions(self, model, messages, tools=None, tool_choice="auto"):
"""
调用聊天补全API
Args:
model: 模型名称
messages: 消息列表
tools: 工具定义列表
tool_choice: 工具选择策略
Returns:
API响应
"""
url = f"{self.base_url}/chat/completions"
payload = {
"model": model,
"messages": messages,
"temperature": 0
}
if tools:
payload["tools"] = tools
payload["tool_choice"] = tool_choice
try:
response = requests.post(url, headers=self.headers, json=payload, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"API请求失败: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"响应内容: {e.response.text}")
raise
# 创建全局客户端实例
client = OpenAICompatibleClient()
2.4 案例一:智能天气助手
让我们通过一个完整的天气查询案例来理解函数调用的实现。
步骤1:模型对话并处理函数调用
import json
from api_client import client
class WeatherAssistant:
def __init__(self):
self.tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:北京、上海、广州",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,摄氏度或华氏度"
},
},
"required": ["location"],
},
},
}
]
# 注册可用的函数
self.available_functions = {
"get_current_weather": self.get_current_weather,
}
def get_current_weather(self, location: str, unit: str = "celsius") -> str:
"""
获取当前天气信息
实际项目中可以替换为真实的天气API
"""
print(f"正在查询 {location} 的天气...")
# 这里使用模拟数据,实际项目中可以调用真实天气API
weather_data = {
"北京": {"temperature": 25, "condition": "晴朗", "humidity": 40},
"上海": {"temperature": 28, "condition": "多云", "humidity": 65},
"广州": {"temperature": 32, "condition": "阵雨", "humidity": 80},
"深圳": {"temperature": 30, "condition": "晴朗", "humidity": 70},
"杭州": {"temperature": 26, "condition": "阴天", "humidity": 75},
}
if location in weather_data:
data = weather_data[location]
# 处理温度单位转换
if unit == "fahrenheit":
data["temperature"] = data["temperature"] * 9 / 5 + 32
result = {
"location": location,
"temperature": data["temperature"],
"unit": unit,
"condition": data["condition"],
"humidity": f"{data['humidity']}%"
}
return json.dumps(result, ensure_ascii=False)
else:
return json.dumps({"error": f"找不到城市 {location} 的天气信息"})
def process_query(self, user_query: str) -> str:
"""处理用户查询"""
messages = [{"role": "user", "content": user_query}]
try:
# 第一步:发送查询给模型
response = client.chat_completions(
model="gpt-4",
messages=messages,
tools=self.tools,
tool_choice="auto",
)
response_message = response["choices"][0]["message"]
messages.append(response_message)
print(f"用户问题: {user_query}")
# 第二步:检查是否需要调用函数
if "tool_calls" in response_message and response_message["tool_calls"]:
print("模型决定调用函数...")
for tool_call in response_message["tool_calls"]:
function_name = tool_call["function"]["name"]
function_args = json.loads(tool_call["function"]["arguments"])
print(f"调用函数: {function_name}")
print(f"函数参数: {function_args}")
# 执行函数
if function_name in self.available_functions:
function_response = self.available_functions[function_name](**function_args)
print(f"函数返回: {function_response}")
print("---")
# 将结果添加回对话
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": function_response,
})
# 第三步:让模型基于结果生成最终回答
second_response = client.chat_completions(
model="gpt-4",
messages=messages,
)
final_response = second_response["choices"][0]["message"]["content"]
return final_response
else:
# 不需要调用函数,直接返回回答
return response_message["content"]
except Exception as e:
return f"处理请求时出错: {str(e)}"
def main():
"""主函数"""
assistant = WeatherAssistant()
print("=== 智能天气助手 ===")
print("输入 '退出' 或 'quit' 结束程序")
print()
while True:
user_input = input("请输入您的问题: ").strip()
if user_input.lower() in ['退出', 'quit', 'exit']:
print("再见!")
break
if not user_input:
continue
response = assistant.process_query(user_input)
print(f"助手: {response}")
print()
if __name__ == "__main__":
main()
运行程序后,输入:上海的天气情况
步骤2:运行结果
配置加载成功!
=== 智能天气助手 ===
输入 '退出' 或 'quit' 结束程序
请输入您的问题: >? 上海的天气情况
用户问题: 上海的天气情况
模型决定调用函数...
调用函数: get_current_weather
函数参数: {'location': '上海', 'unit': 'celsius'}
正在查询 上海 的天气...
函数返回: {"location": "上海", "temperature": 28, "unit": "celsius", "condition": "多云", "humidity": "65%"}
---
助手: 上海当前的天气情况是多云,温度为28摄氏度,湿度为65%。
2.5 案例二:数据库查询机器人
假设我们有一个员工数据库,用户可以自然语言查询。
步骤1:数据库初始化 init_database.py
首先,我们创建一个SQLite数据库并插入一些示例数据。
import sqlite3
import os
from config import Config
def init_database():
"""初始化示例数据库"""
# 确保目录存在
os.makedirs('data', exist_ok=True)
conn = sqlite3.connect(Config.DATABASE_PATH)
cursor = conn.cursor()
# 创建员工表
cursor.execute('''
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
department TEXT NOT NULL,
position TEXT NOT NULL,
salary INTEGER,
hire_date TEXT
)
''')
# 插入示例数据
employees = [
('张三', '销售部', '销售经理', 15000, '2020-03-15'),
('李四', '销售部', '销售代表', 8000, '2021-06-20'),
('王五', '技术部', '技术总监', 25000, '2019-01-10'),
('赵六', '技术部', '高级工程师', 18000, '2020-08-25'),
('钱七', '技术部', '软件工程师', 12000, '2022-02-14'),
('孙八', '人事部', 'HR经理', 10000, '2020-11-30'),
('周九', '财务部', '财务主管', 13000, '2019-09-05'),
('吴十', '财务部', '会计', 9000, '2021-04-18'),
]
cursor.executemany('''
INSERT INTO employees (name, department, position, salary, hire_date)
VALUES (?, ?, ?, ?, ?)
''', employees)
conn.commit()
conn.close()
print("数据库初始化完成!")
if __name__ == "__main__":
init_database()
步骤2:完整的数据库机器人代码 database_assistant.py
import json
import sqlite3
from config import Config
from api_client import client
class DatabaseAssistantAccurate:
def __init__(self, db_path=None):
self.db_path = db_path or Config.DATABASE_PATH
self.conversation_history = []
# 验证数据库连接
self._verify_database()
# 定义工具函数
self.tools = [
{
"type": "function",
"function": {
"name": "get_department_employee_count",
"description": "查询指定部门的员工数量",
"parameters": {
"type": "object",
"properties": {
"department": {
"type": "string",
"description": "部门名称,如:销售部、技术部、人事部、财务部",
}
},
"required": ["department"],
},
},
},
{
"type": "function",
"function": {
"name": "get_department_salary_stats",
"description": "查询指定部门的薪资统计信息,包括平均薪资、最高薪资、最低薪资等",
"parameters": {
"type": "object",
"properties": {
"department": {
"type": "string",
"description": "部门名称,如:销售部、技术部、人事部、财务部",
}
},
"required": ["department"],
},
},
}
]
self.available_functions = {
"get_department_employee_count": self.get_department_employee_count,
"get_department_salary_stats": self.get_department_salary_stats,
}
# 严格的系统提示
self.system_prompt = """你是一个数据库查询助手。当用户询问部门信息时,请调用适当的函数获取数据。
重要规则:
1. 必须调用函数获取真实数据,不能编造数据
2. 函数返回的结果包含真实的数据库数据
3. 请基于函数返回的实际数据生成回答,确保所有数字都是准确的
函数返回的数据格式:
- 员工数量查询: {"department": "部门名", "employee_count": 实际数字}
- 薪资统计: {"department": "部门名", "employee_count": 人数, "avg_salary": 平均薪资, "max_salary": 最高薪资, "min_salary": 最低薪资}"""
self.conversation_history = [{"role": "system", "content": self.system_prompt}]
def _verify_database(self):
"""验证数据库连接和数据"""
print("=== 数据库验证 ===")
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 检查表是否存在
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='employees'")
if not cursor.fetchone():
print("错误: employees表不存在")
return False
# 显示当前数据
cursor.execute("""
SELECT department, COUNT(*), AVG(salary), MAX(salary), MIN(salary)
FROM employees
GROUP BY department
""")
results = cursor.fetchall()
print("数据库中的数据:")
for dept, count, avg_sal, max_sal, min_sal in results:
print(f" {dept}: {count}人, 平均薪资{avg_sal:.0f}元")
conn.close()
print("数据库验证完成")
return True
except Exception as e:
print(f"数据库验证错误: {e}")
return False
def execute_query(self, query: str, params: tuple = ()) -> list:
"""执行SQL查询并返回结果"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
cursor.execute(query, params)
results = cursor.fetchall()
return [dict(row) for row in results]
except Exception as e:
print(f"数据库查询错误: {e}")
return [{"error": str(e)}]
finally:
conn.close()
def get_department_employee_count(self, department: str) -> str:
"""获取指定部门的员工数量"""
print(f"[函数调用] 查询 {department} 的员工数量")
query = "SELECT COUNT(*) as count FROM employees WHERE department = ?"
results = self.execute_query(query, (department,))
if results and "count" in results[0]:
count = results[0]["count"]
print(f"[函数结果] {department} 有 {count} 名员工")
# 返回可以直接使用的自然语言回答
return json.dumps({
"department": department,
"employee_count": count,
"direct_answer": f"{department}共有{count}名员工"
}, ensure_ascii=False)
else:
error_msg = f"无法获取{department}的员工数量"
return json.dumps({
"department": department,
"error": error_msg,
"direct_answer": error_msg
}, ensure_ascii=False)
def get_department_salary_stats(self, department: str) -> str:
"""获取指定部门的薪资统计信息"""
print(f"[函数调用] 查询 {department} 的薪资统计")
query = """
SELECT
COUNT(*) as employee_count,
ROUND(AVG(salary), 2) as avg_salary,
MAX(salary) as max_salary,
MIN(salary) as min_salary
FROM employees
WHERE department = ?
"""
results = self.execute_query(query, (department,))
if results and "employee_count" in results[0] and results[0]["employee_count"] > 0:
stats = results[0]
print(f"[函数结果] {department} 薪资统计: {stats['employee_count']}人, 平均{stats['avg_salary']}元")
# 返回可以直接使用的自然语言回答
return json.dumps({
"department": department,
"employee_count": stats["employee_count"],
"avg_salary": float(stats["avg_salary"]),
"max_salary": stats["max_salary"],
"min_salary": stats["min_salary"],
"direct_answer": f"{department}共有{stats['employee_count']}名员工,平均薪资{stats['avg_salary']}元,最高薪资{stats['max_salary']}元,最低薪资{stats['min_salary']}元"
}, ensure_ascii=False)
else:
error_msg = f"无法获取{department}的薪资统计信息"
return json.dumps({
"department": department,
"error": error_msg,
"direct_answer": error_msg
}, ensure_ascii=False)
def process_query(self, user_query: str) -> str:
"""处理用户查询 - 确保返回真实准确的数据"""
# 添加用户消息到对话历史
self.conversation_history.append({"role": "user", "content": user_query})
print(f"\n用户问题: {user_query}")
print("---")
try:
# 第一步:让模型分析用户意图
response = client.chat_completions(
model="gpt-4",
messages=self.conversation_history,
tools=self.tools,
tool_choice="auto",
)
response_message = response["choices"][0]["message"]
# 检查是否需要调用函数
if "tool_calls" in response_message and response_message["tool_calls"]:
print("模型决定调用函数:")
# 处理所有函数调用
for tool_call in response_message["tool_calls"]:
function_name = tool_call["function"]["name"]
function_args = json.loads(tool_call["function"]["arguments"])
print(f" - {function_name}({function_args})")
# 执行函数
if function_name in self.available_functions:
function_response = self.available_functions[function_name](**function_args)
print(f" 函数返回: {function_response}")
# 解析函数返回的JSON,直接使用direct_answer字段
result_data = json.loads(function_response)
final_response = result_data.get("direct_answer", function_response)
# 将函数结果添加到对话历史
self.conversation_history.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": function_response,
})
# 直接使用函数返回的direct_answer作为最终回答
self.conversation_history.append({
"role": "assistant",
"content": final_response
})
return final_response
# 如果没有找到可用的函数,返回错误
return "无法处理您的查询,请尝试其他问题"
else:
# 不需要调用函数,直接返回模型的回答
final_response = response_message["content"]
self.conversation_history.append({
"role": "assistant",
"content": final_response
})
return final_response
except Exception as e:
error_msg = f"处理请求时出错: {str(e)}"
print(error_msg)
return error_msg
def reset_conversation(self):
"""重置对话历史"""
self.conversation_history = [{"role": "system", "content": self.system_prompt}]
return "对话历史已重置"
def main():
"""主函数"""
print("=== 数据库查询助手===")
print()
assistant = DatabaseAssistantAccurate()
# 测试关键查询
test_queries = [
"技术部有多少员工",
"销售部的薪资情况怎么样",
"财务部有多少人",
"人事部的工资水平如何"
]
print(f"测试问题: ")
for query in test_queries:
print(query)
print("开始交互式对话 (输入 '退出' 结束):")
while True:
user_input = input("请输入您的问题: ").strip()
if user_input.lower() in ['退出', 'quit', 'exit']:
print("再见!")
break
if not user_input:
continue
response = assistant.process_query(user_input)
print(f"助手: {response}")
assistant.reset_conversation()
print()
if __name__ == "__main__":
main()
步骤3:运行结果:
配置加载成功!
=== 数据库查询助手===
=== 数据库验证 ===
数据库中的数据:
人事部: 1人, 平均薪资10000元
技术部: 3人, 平均薪资18333元
财务部: 2人, 平均薪资11000元
销售部: 2人, 平均薪资11500元
数据库验证完成
测试问题:
技术部有多少员工
销售部的薪资情况怎么样
财务部有多少人
人事部的工资水平如何
开始交互式对话 (输入 '退出' 结束):
请输入您的问题: 技术部有多少员工
用户问题: 技术部有多少员工
---
模型决定调用函数:
- get_department_employee_count({'department': '技术部'})
[函数调用] 查询 技术部 的员工数量
[函数结果] 技术部 有 3 名员工
函数返回: {"department": "技术部", "employee_count": 3, "direct_answer": "技术部共有3名员工"}
助手: 技术部共有3名员工
请输入您的问题: 销售部的薪资情况怎么样
用户问题: 销售部的薪资情况怎么样
---
模型决定调用函数:
- get_department_salary_stats({'department': '销售部'})
[函数调用] 查询 销售部 的薪资统计
[函数结果] 销售部 薪资统计: 2人, 平均11500.0元
函数返回: {"department": "销售部", "employee_count": 2, "avg_salary": 11500.0, "max_salary": 15000, "min_salary": 8000, "direct_answer": "销售部共有2名员工,平均薪资11500.0元,最高薪资15000元,最低薪资8000元"}
助手: 销售部共有2名员工,平均薪资11500.0元,最高薪资15000元,最低薪资8000元
请输入您的问题: 财务部有多少人
用户问题: 财务部有多少人
---
模型决定调用函数:
- get_department_employee_count({'department': '财务部'})
[函数调用] 查询 财务部 的员工数量
[函数结果] 财务部 有 2 名员工
函数返回: {"department": "财务部", "employee_count": 2, "direct_answer": "财务部共有2名员工"}
助手: 财务部共有2名员工
2.6 代码解析
-
灵活的函数映射:通过
available_functions字典,实现了函数名称到实际函数的动态映射。 -
结构化数据返回:数据库查询结果转换为JSON格式,既保持了结构化数据的完整性,又便于模型理解。
-
多函数协作:模型可以根据问题的复杂程度,决定调用单个或多个函数来解决问题。
-
自然语言转换:模型将结构化的数据库结果转换成了易于理解的文字描述,用户体验更好。
通过这两个案例,我们可以看到,函数调用将自然语言与结构化操作完美地桥接了起来。
2.7 遇到的问题及解决方案
上面的代码在执行的时候有可能会出现不调用函数、返回的结果跟实际数据不一致的问题,最后才发现是Temperature参数在搞鬼,先简单的说一下这个参数的作用(后续单独拿一篇文章来介绍)
在生成式语言模型中,Temperature参数控制了模型生成文本时的多样性和随机性。简单来说,Temperature参数决定了模型在生成下一个单词时,选择概率的分布是否平滑或者更加尖锐。
这个参数本质上是一个对模型概率分布的重新缩放因子,用来调整输出的熵值,进而影响输出的随机程度。
Temperature 越低,模型的输出越确定,生成的文本更加保守和可预测。反之,较高的 Temperature 会使输出更加随机、多样化,但可能导致文本质量下降。(本文设置的是0,之前是默认值,对于 OpenAI 的 GPT 系列模型,temperature 的默认值通常是 1.0)
第三部分:前景与替代技术
3.1 函数调用的未来前景
函数调用是目前最成熟、最可控的AI行动方案。它的未来在于:
-
AI Agent(智能体)的核心: Agent的本质就是“感知-规划-行动”的循环,函数调用正是其“行动”的部分。
-
企业级应用标配: 将成为企业集成AI能力的主流方式,用于连接CRM、ERP、数据库等内部系统。
-
万物互联的入口: 通过函数调用,大模型可以控制智能家居、启动汽车、操作工厂机器人。
3.2 主要的替代与演进技术
函数调用并非唯一路径,其他技术也在探索中:
-
LangChain / LlamaIndex: 这些框架在函数调用的基础上,提供了更高级的抽象,比如工具链、工作流编排、长上下文处理等,让开发复杂的AI应用更简单。可以看作是“函数调用++”。
-
RAG: 检索增强生成,主要用于知识库扩展,解决的是模型“知识陈旧”和“幻觉”问题。它和函数调用是互补关系,一个负责“查资料”,一个负责“做动作”。
-
ReAct框架: 一种提示词范式,指导模型在思考中交替进行 Reasoning(推理) 和 Action(行动),其行动步骤通常就是通过函数调用实现。
第四部分:总结
函数调用技术,是大模型从“数字世界的思想家”迈向“现实世界的行动者”的关键一步。
-
它的本质是桥接: 桥接了非结构化的自然语言和结构化的数字世界。
-
它的价值是赋能: 赋能AI使用人类已有的庞大工具和系统,极大降低了AI的应用门槛。
-
它的未来是生态: 随着工具函数的日益丰富和标准化,一个由大模型作为“中枢大脑”,调度万千数字工具为人类服务的时代正在加速到来。
作为开发者,理解和掌握函数调用,就等于拿到了开启AI应用新时代的钥匙。现在,就开始为你的大模型打造专属的“工具库”吧!
源代码如需获取,请关注公众号(优趣AI),发送消息ufunai-functioncall获取,扫一扫即可获取!
创作不易,码字更不易,如果觉得这篇文章对你有帮助,记得点个关注、在看或收藏,给作者一点鼓励吧~
863

被折叠的 条评论
为什么被折叠?



