LLM函数调用全解析:构建智能股票分析助手,让大模型拥有实时数据处理能力

180 阅读11分钟

深入解析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)
  • 运行结果

image.png

高级特性:自定义数据分析

除了依赖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)

运行结果

image.png

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使用最佳实践

  1. 频率限制:免费版通常有5次/分钟、500次/天的限制
  2. 错误处理:实现重试机制和降级方案
  3. 数据缓存:对不变的数据进行缓存,减少API调用
  4. 密钥管理:使用环境变量存储敏感信息
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"]
        }
    }
})

总结与最佳实践

通过本文的详细解析,我们学习了:

  1. 函数调用的核心概念:如何让LLM识别需求并调用外部工具
  2. 工具定义规范:使用JSON Schema准确描述函数用途和参数
  3. 对话流程管理:维护完整的消息历史确保上下文连贯
  4. 错误处理机制:构建健壮的生产级应用
  5. API集成技巧:如何有效使用第三方API服务

关键成功因素:

  • 清晰的工具描述让LLM准确理解函数用途
  • 完整的错误处理确保系统稳定性
  • 合理的数据缓存降低API调用成本
  • 模块化设计便于功能扩展和维护

函数调用技术为LLM应用开辟了广阔的可能性,让模型能够突破知识截止日期的限制,真正成为智能的实时信息处理助手。希望本文能为你在LLM应用开发道路上提供有价值的参考!


注:本文中的API密钥均为示例,实际使用时请替换为自己的有效密钥,并妥善保管敏感信息。