# 构建智能 LLM Agent:函数调用实战指南
## 背景介绍
大语言模型发展到现在,光能”说”已经不够了,怎样让它真正”做”事情?函数调用(Function Calling)就是解决这个问题的关键技术。
说白了,函数调用让 LLM 可以和外部系统打交道——查实时数据、执行操作,不再局限于训练数据里那点过时的东西。
这篇文章会手把手教你搭建一个能调用函数的 LLM Agent,让 AI 真正参与到实际工作中。
## 问题描述
传统的 LLM 应用有几个硬伤:
第一,知识太旧。训练数据有截止日期,问它今天天气怎么样,它只能瞎猜。
第二,只能说不能做。生成文字没问题,但没法帮你查数据库、调 API。
第三,没法获取你个人的数据。你的文件、数据库、邮件,它都访问不了。
函数调用这个机制让 LLM 可以声明自己要调什么函数,由应用层去执行,然后把结果喂回来。这样就实现了”想—做—结果”的完整闭环。
## 详细步骤
### 环境准备
先装好必要的依赖:
“`bash
pip install openai python-dotenv requests
“`
### 定义工具函数
我们定义几个实用的工具函数:
“`python
import json
import requests
from datetime import datetime
from typing import Any, Dict, List
# 工具函数定义
def get_weather(location: str) -> str:
“””获取指定位置的天气信息”””
# 模拟天气 API 调用
weather_data = {
“北京”: “晴,15-25°C”,
“上海”: “多云,18-27°C”,
“深圳”: “雷阵雨,24-30°C”
}
return weather_data.get(location, “未知”)
def search_wiki(query: str) -> str:
“””搜索维基百科获取信息”””
url = f”https://en.wikipedia.org/api/rest_v1/page/summary/{query}”
try:
resp = requests.get(url, timeout=5)
data = resp.json()
return data.get(“extract”, “未找到相关内容”)
except:
return “搜索失败,请稍后重试”
def calculate(expression: str) -> str:
“””数学计算器”””
try:
# 安全计算:只允许基本运算
allowed_chars = set(“0123456789+-*/.() “)
if all(c in allowed_chars for c in expression):
result = eval(expression)
return str(result)
return “表达式包含非法字符”
except Exception as e:
return f”计算错误: {str(e)}”
def get_current_time() -> str:
“””获取当前时间”””
return datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)
“`
这里我写了四个函数:查天气、搜维基百科、算数学题、获取当前时间。这些是最常见的需求。
### 构建函数调用格式
LLM 需要知道每个函数的名称、用途和参数。把工具函数转换成它能理解的格式:
“`python
functions = [
{
“type”: “function”,
“function”: {
“name”: “get_weather”,
“description”: “获取指定位置的天气信息”,
“parameters”: {
“type”: “object”,
“properties”: {
“location”: {
“type”: “string”,
“description”: “城市名称,如北京、上海、深圳”
}
},
“required”: [“location”]
}
}
},
{
“type”: “function”,
“function”: {
“name”: “search_wiki”,
“description”: “搜索维基百科获取信息”,
“parameters”: {
“type”: “object”,
“properties”: {
“query”: {
“type”: “string”,
“description”: “搜索关键词”
}
},
“required”: [“query”]
}
}
},
{
“type”: “function”,
“function”: {
“name”: “calculate”,
“description”: “数学计算器,支持加减乘除”,
“parameters”: {
“type”: “object”,
“properties”: {
“expression”: {
“type”: “string”,
“description”: “数学表达式,如 2+3*4”
}
},
“required”: [“expression”]
}
}
},
{
“type”: “function”,
“function”: {
“name”: “get_current_time”,
“description”: “获取当前时间”,
“parameters”: {
“type”: “object”,
“properties”: {}
}
}
}
]
“`
每个函数都有名字、描述和参数规范。LLM 就是靠这些信息决定该调哪个函数的。
### 实现 Agent 主循环
核心部分来了。我要构建一个持续交互的循环,处理 LLM 的函数调用请求:
“`python
class LLMAgent:
def __init__(self, api_key: str, model: str = “gpt-4”):
from openai import OpenAI
self.client = OpenAI(api_key=api_key)
self.model = model
self.tools = functions
self.messages = []
# 工具函数映射
self.tool_map = {
“get_weather”: get_weather,
“search_wiki”: search_wiki,
“calculate”: calculate,
“get_current_time”: get_current_time
}
def chat(self, user_input: str) -> str:
“””处理用户输入并返回响应”””
self.messages.append({“role”: “user”, “content”: user_input})
# 第一次调用:让 LLM 决定是否需要调用函数
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools,
tool_choice=”auto”
)
# 处理响应
return self._handle_response(response)
def _handle_response(self, response) -> str:
“””处理 LLM 响应,可能包含函数调用”””
message = response.choices[0].message
# 检查是否有函数调用
if message.tool_calls:
# 添加助手消息(包含函数调用)
self.messages.append({
“role”: “assistant”,
“content”: message.content,
“tool_calls”: [
{
“id”: tc.id,
“type”: “function”,
“function”: {
“name”: tc.function.name,
“arguments”: tc.function.arguments
}
}
for tc in 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 in self.tool_map:
result = self.tool_map[func_name](**func_args)
else:
result = f”未知函数: {func_name}”
# 添加函数结果到消息历史
self.messages.append({
“role”: “tool”,
“tool_call_id”: tool_call.id,
“content”: str(result)
})
# 第二次调用:获取最终响应
final_response = self.client.chat.completions.create(
model=self.model,
messages=self.messages,
tools=self.tools
)
final_message = final_response.choices[0].message.content
self.messages.append({“role”: “assistant”, “content”: final_message})
return final_message
else:
# 无函数调用,直接返回响应
self.messages.append({“role”: “assistant”, “content”: message.content})
return message.content
def reset(self):
“””重置对话历史”””
self.messages = []
“`
这个 Agent 的工作流程是:用户发来消息 → LLM 判断要不要调函数 → 如果要调就执行 → 把执行结果再发给 LLM → 得到最终回复。需要两轮 API 调用。
### 完整使用示例
“`python
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 初始化 Agent
agent = LLMAgent(
api_key=os.getenv(“OPENAI_API_KEY”),
model=”gpt-4″
)
# 测试各种场景
print(“=== 测试 1: 天气查询 ===”)
print(agent.chat(“北京今天天气怎么样?”))
print(“\n=== 测试 2: 数学计算 ===”)
print(agent.chat(“帮我计算 (125 + 367) * 3 = ?”))
print(“\n=== 测试 3: 维基百科搜索 ===”)
print(agent.chat(“请介绍一下 Python 编程语言”))
print(“\n=== 测试 4: 当前时间 ===”)
print(agent.chat(“现在几点了?”))
print(“\n=== 测试 5: 多函数组合 ===”)
print(agent.chat(“北京天气如何?顺便帮我算一下 99*88”))
“`
我设置了五个测试场景:从简单的查天气、算数学,到搜维基百科、问时间,最后还有一个需要同时调用两个函数的复杂问题。
## 运行结果
跑一下上面的测试代码,看看效果:
“`
=== 测试 1: 天气查询 ===
北京今天天气晴,气温15-25°C,适合外出。
=== 测试 2: 数学计算 ===
(125 + 367) * 3 = 1476
=== 测试 3: 维基百科搜索 ===
Python 是一种广泛使用的高级编程语言,由 Guido van Rossum 于 1991 年首次发布。Python 强调代码的可读性和简洁的语法,相比于 C++ 或 Java,Python 让开发者能够用更少的代码表达想法。
=== 测试 4: 当前时间 ===
当前时间是 2024-03-15 14:30:25。
=== 测试 5: 多函数组合 ===
北京天气晴,气温15-25°C。(99 * 88) = 8712。
“`
五个测试全部通过。值得注意的是测试5,Agent 一次性调了两个函数,把两个结果都返回了。这个能力很关键——它意味着你可以让 AI 同时处理多个任务。
## 总结
学完这篇文章,你应该掌握了搭建 LLM Agent 函数调用系统的整个流程:
第一步,定义工具函数。把需要暴露给 AI 的能力写成 Python 函数。
第二步,转换函数格式。用 JSON Schema 规范描述每个函数的签名。
第三步,实现调用循环。处理 LLM 发来的函数调用请求,执行并把结果喂回去。
第四步,多函数组合。支持同时调用多个函数,处理复杂任务。
这种架构有几个明显的好处:
可扩展性强。想加新功能?再加一个函数就行了,LLM 那边不用改。
安全性好。函数执行完全在应用层控制,想加权限校验、审计日志都很方便。
很灵活。同步异步都支持,想接数据库就接数据库,想调 API 就调 API。
你可以在这个基础上继续扩展:接上数据库查询能力、调用第三方 API、甚至接入 LangChain 之类的框架搭建更复杂的 Agent。
说到底,LLM 要从玩具变成生产力工具,关键不在于它多会”说”,而在于它能多会”做”。函数调用就是那座桥。