深入解析LLM函数调用:构建智能股票分析助手
本文详细介绍了如何通过函数调用扩展大语言模型的能力,让LLM能够处理实时数据和复杂任务,并以股票数据分析为例展示完整实现。
引言:为什么需要函数调用?
在日常使用大语言模型时,我们经常会遇到这样的困境:模型的知识截止日期限制了它对实时信息的获取能力。比如询问"今天微软股价如何?",传统的LLM无法给出准确答案。这就是函数调用(Function Calling)技术的用武之地。
函数调用允许LLM识别用户需求,然后调用我们预先定义好的外部函数来获取实时数据、执行复杂计算或访问专有系统。本文将带你深入理解这一技术,并构建一个完整的股票分析助手。
技术架构概览
在开始编码前,让我们先理解整个系统的工作流程:
graph TD
A[用户提问] --> B[LLM识别意图]
B --> C[返回工具调用请求]
C --> D[执行外部函数]
D --> E[获取实时数据]
E --> F[返回数据给LLM]
F --> G[LLM分析数据]
G --> H[生成最终回答]
环境准备与配置
安装必要的库
# 安装核心依赖
pip install requests openai
# requests:用于发送HTTP请求,与外部API交互
# openai:OpenAI官方库,提供与LLM交互的接口
API密钥配置
from openai import OpenAI
# 初始化客户端
client = OpenAI(
api_key='apikey', # 你的DeepSeek API密钥
base_url='https://api.deepseek.com/v1' # 使用DeepSeek API端点
)
关键点解析:
api_key:身份验证凭证,确保只有授权用户可以使用服务base_url:指定API端点,这里使用DeepSeek而非OpenAI官方- 客户端模式:统一接口,便于后续切换不同模型提供商
核心代码深度解析
1. 工具函数定义:获取股票数据
- 参数说明:
- symbol: 股票代码,如 'MSFT'(微软)、'AAPL'(苹果)
- interval: 数据时间间隔,支持1min、5min、15min、30min、60min
- api_key: Alpha Vantage服务的API密钥
import requests
import json
def get_intraday_stock_data(symbol, interval='5min', api_key='Alpha Vantage服务的API密钥'):
"""
获取股票日内交易数据 - 核心数据获取函数
参数说明:
- symbol: 股票代码,如 'MSFT'(微软)、'AAPL'(苹果)
- interval: 数据时间间隔,支持1min、5min、15min、30min、60min
- api_key: Alpha Vantage服务的API密钥
返回格式:
- 包含时间序列数据的JSON对象
"""
base_url = "https://www.alphavantage.co/query"
# 构建请求参数
params = {
'function': 'TIME_SERIES_INTRADAY', # API功能:日内时间序列
'symbol': symbol, # 目标股票代码
'interval': interval, # 数据粒度
'apikey': api_key, # 认证密钥
'outputsize': 'compact', # 数据量:compact(最近100条)或full(全部)
'datatype': 'json' # 返回格式:JSON
}
# 发送GET请求
response = requests.get(base_url, params=params)
# 检查请求状态
if response.status_code == 200:
data = response.json()
# 错误处理:检查API返回的错误信息
if 'Error Message' in data:
raise ValueError(f"API错误: {data['Error Message']}")
elif 'Information' in data:
print(f"API信息: {data['Information']}")
return data
else:
# 网络请求失败处理
raise ConnectionError(f"请求失败,状态码: {response.status_code}")
技术细节说明:
-
HTTP请求构建:使用
requests.get()发送GET请求,参数通过URL参数传递 -
错误处理机制:检查状态码和API返回的错误信息
-
参数设计思想:
outputsize: 'compact':平衡数据量和响应速度interval: '5min':兼顾数据精度和API调用限制
-
构建请求参数
- 这些字段名('function', 'symbol', 'interval' 等)是由 Alpha Vantage API 官方文档定义和规定的,绝对不能自定义。
-
为什么不能自定义?
- API 服务器的约定: Alpha Vantage 的后端服务器程序被编写为只识别这些特定的字段名。当它收到一个HTTP请求时,它会查找名为 function 的参数来知道要执行什么操作,查找 symbol 来知道要查询哪只股票。如果您将 symbol 改成 stock_symbol,服务器将无法理解您的意图,导致请求失败或返回错误。
-
标准化协议: RESTful API 遵循一种标准的通信协议。请求参数的名字和含义是这份“协议”的一部分。遵守这个协议,你的程序才能和对方的服务器成功“对话”。
2. 工具配置:告诉LLM可用的函数
tools = [
{
"type": "function",
"function": {
"name": "get_intraday_stock_data", # 函数名,必须与定义一致
"description": "获取指定股票的日内交易数据", # 功能描述,LLM据此判断是否调用
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,例如:AAPL(苹果)、MSFT(微软)、IBM(国际商业机器)",
# 描述越准确,LLM理解越到位
},
"interval": {
"type": "string",
"enum": ["1min", "5min", "15min", "30min", "60min"], # 枚举值,限制输入范围
"description": "数据时间间隔,默认为5分钟",
}
},
"required": ["symbol"], # 必填参数,其他为可选
},
},
}
]
配置要点解析:
- 描述的重要性:LLM完全依赖description来理解函数用途,必须清晰准确
- 参数类型系统:使用JSON Schema定义参数类型和约束
- enum枚举限制:确保输入值在可接受范围内,避免无效调用
3. 与LLM交互的核心流程
# 第一步:构建用户消息
message = [{"role": "user", "content": "微软股票的日内交易数据"}]
# 第二步:发送初始请求,让LLM识别需要调用工具
response = client.chat.completions.create(
model='deepseek-reasoner', # 指定模型,这里使用DeepSeek Reasoning模型
messages=message, # 对话历史
tools=tools, # 可用工具列表
tool_choice="auto", # 让模型自动决定是否调用工具
temperature=0.1 # 控制输出随机性,越低越确定
)
# 第三步:解析模型响应
response1 = response.choices[0].message
print("模型初始响应:", response1)
# 将模型响应加入对话历史,保持上下文连贯
message.append(response1)
参数深度解读:
- model选择:
deepseek-reasoner专门优化了推理能力,适合需要逻辑分析的场景 - temperature设置:
0.1确保输出稳定,适合工具调用场景 - tool_choice策略:
"auto":模型自主决定"none":不调用工具{"type": "function", "function": {"name": "xxx"}}:强制调用特定工具
4. 工具调用与结果处理
if response1.tool_calls:
# 提取第一个工具调用(支持多个工具同时调用)
tool_call = response1.tool_calls[0]
# 解析参数 - 注意:参数以JSON字符串形式传递
args = json.loads(tool_call.function.arguments)
symbol = args.get("symbol")
print(f"检测到工具调用请求,获取 {symbol} 的股票数据...")
# 执行实际的函数调用
try:
result = get_intraday_stock_data(symbol)
print("数据获取成功!")
except Exception as e:
print(f"数据获取失败: {e}")
return
# 构建工具响应消息
tool_message = {
"role": "tool",
"tool_call_id": tool_call.id, # 必须与请求ID对应
"content": json.dumps(result) # 函数执行结果
}
# 将工具响应加入对话历史
message.append(tool_message)
关键机制说明:
- tool_call_id:建立请求-响应关联,确保数据流向正确
- 错误处理:在实际应用中应该更完善的错误处理和重试机制
- 内容格式:工具返回的内容必须是字符串,复杂数据需要JSON序列化
5. 获取最终分析结果
# 第四步:将工具执行结果返回给LLM进行最终分析
final_response = client.chat.completions.create(
model='deepseek-reasoner',
messages=message, # 此时包含:用户提问 + 模型工具请求 + 工具返回数据
temperature=0.1 # 保持输出稳定性
)
final_answer = final_response.choices[0].message.content
print("\n" + "="*50)
print("📊 智能股票分析报告")
print("="*50)
print(final_answer)
- 运行结果
高级特性:自定义数据分析
除了依赖LLM的分析,我们还可以添加自定义分析逻辑:
# 直接分析您已经获取的数据
def analyze_stock_performance(data):
"""分析股票表现"""
time_series = data.get('Time Series (5min)', {})
if not time_series:
return "无法获取有效的股票数据"
# 获取时间戳列表
timestamps = list(time_series.keys())
# 最新数据
latest_timestamp = timestamps[0]
latest_data = time_series[latest_timestamp]
latest_price = float(latest_data['4. close'])
# 今日开盘数据(假设第一个数据是开盘)
open_timestamp = timestamps[-1]
open_data = time_series[open_timestamp]
open_price = float(open_data['1. open'])
# 计算涨跌幅
change = latest_price - open_price
change_percent = (change / open_price) * 100
# 获取最高价和最低价
high_prices = [float(data['2. high']) for data in time_series.values()]
low_prices = [float(data['3. low']) for data in time_series.values()]
day_high = max(high_prices)
day_low = min(low_prices)
# 生成分析报告
analysis = f"""
微软(MSFT)股票今日表现分析:
--------------------------------
📈 当前价格: ${latest_price:.2f}
🟢 今日开盘: ${open_price:.2f}
📊 涨跌幅: {change:+.2f} ({change_percent:+.2f}%)
🔺 今日最高: ${day_high:.2f}
🔻 今日最低: ${day_low:.2f}
⏰ 最后更新: {latest_timestamp}
--------------------------------
"""
if change > 0:
analysis += "🎯 今日表现: 上涨趋势"
else:
analysis += "🎯 今日表现: 下跌趋势"
return analysis
# 直接分析您已经获取的数据
analysis_result = analyze_stock_performance(result)# 传参数
print("=== 微软股票分析报告 ===")
print(analysis_result)
运行结果
API文档使用指南
理解Alpha Vantage API
在构建此类应用时,深入理解使用的API文档至关重要:
# Alpha Vantage API参数详解
API_PARAMS = {
'function': 'TIME_SERIES_INTRADAY', # 必选,指定数据类型
'symbol': 'MSFT', # 必选,股票代码
'interval': '5min', # 必选,数据间隔
'apikey': 'YOUR_KEY', # 必选,身份验证
'outputsize': 'compact', # 可选,数据量控制
'datatype': 'json', # 可选,返回格式
}
# 响应数据结构理解
SAMPLE_RESPONSE = {
"Meta Data": {
"1. Information": "Intraday (5min) open, high, low, close prices and volume",
"2. Symbol": "MSFT",
"3. Last Refreshed": "2024-01-15 16:00:00",
"4. Interval": "5min",
"5. Output Size": "Compact",
"6. Time Zone": "US/Eastern"
},
"Time Series (5min)": {
"2024-01-15 16:00:00": {
"1. open": "384.1100",
"2. high": "384.6500",
"3. low": "383.5200",
"4. close": "384.2100",
"5. volume": "1526376"
}
# ... 更多时间点数据
}
}
API使用最佳实践
- 频率限制:免费版通常有5次/分钟、500次/天的限制
- 错误处理:实现重试机制和降级方案
- 数据缓存:对不变的数据进行缓存,减少API调用
- 密钥管理:使用环境变量存储敏感信息
import os
import time
from functools import lru_cache
# 更健壮的实现
class StockDataFetcher:
def __init__(self):
self.api_key = os.getenv('ALPHA_VANTAGE_API_KEY')
self.last_call_time = 0
self.min_interval = 12 # 秒,满足5次/分钟限制
@lru_cache(maxsize=100) # 缓存最近100次查询
def get_intraday_data_cached(self, symbol, interval='5min'):
# 速率限制
current_time = time.time()
if current_time - self.last_call_time < self.min_interval:
time.sleep(self.min_interval - (current_time - self.last_call_time))
self.last_call_time = time.time()
return get_intraday_stock_data(symbol, interval, self.api_key)
完整代码整合
import json
import requests
import os
from openai import OpenAI
class SmartStockAssistant:
def __init__(self):
# 初始化LLM客户端
self.client = OpenAI(
api_key=os.getenv('DEEPSEEK_API_KEY', 'apikey'),
base_url='https://api.deepseek.com/v1'
)
# 定义可用工具
self.tools = [{
"type": "function",
"function": {
"name": "get_intraday_stock_data",
"description": "获取指定股票的日内交易数据",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,例如:AAPL(苹果)、MSFT(微软)",
},
"interval": {
"type": "string",
"enum": ["1min", "5min", "15min", "30min", "60min"],
"description": "数据时间间隔",
}
},
"required": ["symbol"],
},
},
}]
def get_stock_data(self, symbol, interval='5min'):
"""获取股票数据的具体实现"""
api_key = os.getenv('ALPHA_VANTAGE_API_KEY', 'SS00X8SPAFEGSKXP')
base_url = "https://www.alphavantage.co/query"
params = {
'function': 'TIME_SERIES_INTRADAY',
'symbol': symbol,
'interval': interval,
'apikey': api_key,
'outputsize': 'compact',
'datatype': 'json'
}
response = requests.get(base_url, params=params)
return response.json()
def analyze_stock_question(self, question):
"""主分析流程"""
messages = [{"role": "user", "content": question}]
try:
# 第一步:让LLM识别是否需要调用工具
response = self.client.chat.completions.create(
model='deepseek-reasoner',
messages=messages,
tools=self.tools,
tool_choice="auto",
temperature=0.1
)
assistant_message = response.choices[0].message
messages.append(assistant_message)
# 第二步:如果有工具调用,执行并获取数据
if assistant_message.tool_calls:
tool_call = assistant_message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
symbol = args.get("symbol")
print(f"🔍 正在获取 {symbol} 的实时数据...")
stock_data = self.get_stock_data(symbol)
# 第三步:将数据返回给LLM
tool_response = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(stock_data)
}
messages.append(tool_response)
# 第四步:获取最终分析
final_response = self.client.chat.completions.create(
model='deepseek-reasoner',
messages=messages,
temperature=0.1
)
return final_response.choices[0].message.content
else:
return assistant_message.content
except Exception as e:
return f"分析过程中出现错误: {str(e)}"
# 使用示例
if __name__ == "__main__":
assistant = SmartStockAssistant()
# 测试不同的问题
questions = [
"微软股票的日内交易数据",
"苹果公司今天股价表现如何?",
"给我NVDA的股票数据"
]
for question in questions:
print(f"\n🤔 用户提问: {question}")
print("="*60)
answer = assistant.analyze_stock_question(question)
print(f"🤖 AI回答:\n{answer}")
print("="*60)
扩展应用场景
这个框架可以轻松扩展到其他领域:
1. 天气查询助手
tools.append({
"type": "function",
"function": {
"name": "get_weather_forecast",
"description": "获取指定城市的天气预报",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"days": {"type": "integer", "description": "预报天数"}
},
"required": ["city"]
}
}
})
2. 新闻搜索助手
tools.append({
"type": "function",
"function": {
"name": "search_news",
"description": "搜索最新新闻",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"count": {"type": "integer", "description": "返回数量"}
},
"required": ["query"]
}
}
})
总结与最佳实践
通过本文的详细解析,我们学习了:
- 函数调用的核心概念:如何让LLM识别需求并调用外部工具
- 工具定义规范:使用JSON Schema准确描述函数用途和参数
- 对话流程管理:维护完整的消息历史确保上下文连贯
- 错误处理机制:构建健壮的生产级应用
- API集成技巧:如何有效使用第三方API服务
关键成功因素:
- 清晰的工具描述让LLM准确理解函数用途
- 完整的错误处理确保系统稳定性
- 合理的数据缓存降低API调用成本
- 模块化设计便于功能扩展和维护
函数调用技术为LLM应用开辟了广阔的可能性,让模型能够突破知识截止日期的限制,真正成为智能的实时信息处理助手。希望本文能为你在LLM应用开发道路上提供有价值的参考!
注:本文中的API密钥均为示例,实际使用时请替换为自己的有效密钥,并妥善保管敏感信息。