- Pass all registered tools to LLM during chat completion - Handle tool_calls from LLM response - Execute tools and feed results back to LLM - Loop until LLM returns final response - Updated system prompt to encourage tool use - Updated streaming to handle tool calls - Increased MAX_TOOL_ITERATIONS to 5
524 lines
16 KiB
Python
Executable File
524 lines
16 KiB
Python
Executable File
"""
|
|
Financial Data Tool - Get stock quotes, crypto prices, and financial data
|
|
|
|
Free sources used:
|
|
- Yahoo Finance (yfinance library - completely free)
|
|
- CoinGecko API (free tier: 10-50 calls/minute)
|
|
- FRED API (Federal Reserve Economic Data - free with API key)
|
|
- ExchangeRate-API (free tier)
|
|
|
|
Most functions work without API keys.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
|
|
import requests
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Free API endpoints
|
|
COINGECKO_API = "https://api.coingecko.com/api/v3"
|
|
EXCHANGE_RATE_API = "https://api.exchangerate-api.com/v4/latest"
|
|
FRED_API = "https://api.stlouisfed.org/fred"
|
|
|
|
|
|
def finance_get_stock_info(
|
|
symbol: str,
|
|
) -> dict:
|
|
"""
|
|
Get stock information from Yahoo Finance.
|
|
|
|
Args:
|
|
symbol: Stock ticker symbol (e.g., AAPL, GOOGL, TSLA)
|
|
|
|
Returns:
|
|
Dictionary with stock information
|
|
"""
|
|
try:
|
|
import yfinance as yf
|
|
|
|
ticker = yf.Ticker(symbol.upper())
|
|
info = ticker.info
|
|
|
|
# Extract key financial data
|
|
result = {
|
|
"success": True,
|
|
"source": "yahoo_finance",
|
|
"symbol": symbol.upper(),
|
|
"company_name": info.get("longName", info.get("shortName", "")),
|
|
"current_price": info.get("currentPrice") or info.get("regularMarketPrice"),
|
|
"previous_close": info.get("previousClose"),
|
|
"open": info.get("open"),
|
|
"day_high": info.get("dayHigh"),
|
|
"day_low": info.get("dayLow"),
|
|
"52_week_high": info.get("fiftyTwoWeekHigh"),
|
|
"52_week_low": info.get("fiftyTwoWeekLow"),
|
|
"market_cap": info.get("marketCap"),
|
|
"pe_ratio": info.get("trailingPE"),
|
|
"forward_pe": info.get("forwardPE"),
|
|
"dividend_yield": info.get("dividendYield"),
|
|
"volume": info.get("volume"),
|
|
"avg_volume": info.get("averageVolume"),
|
|
"beta": info.get("beta"),
|
|
"eps": info.get("trailingEps"),
|
|
"revenue": info.get("totalRevenue"),
|
|
"profit_margins": info.get("profitMargins"),
|
|
"description": info.get("longBusinessSummary", "")[:1000],
|
|
"sector": info.get("sector"),
|
|
"industry": info.get("industry"),
|
|
"website": info.get("website"),
|
|
"timestamp": datetime.now().isoformat(),
|
|
}
|
|
|
|
# Remove None values
|
|
result = {k: v for k, v in result.items() if v is not None}
|
|
|
|
return result
|
|
|
|
except ImportError:
|
|
return {
|
|
"success": False,
|
|
"error": "yfinance not installed. Run: pip install yfinance",
|
|
"source": "yahoo_finance",
|
|
}
|
|
except Exception as e:
|
|
log.error(f"Stock info fetch failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "yahoo_finance",
|
|
"symbol": symbol,
|
|
}
|
|
|
|
|
|
def finance_get_stock_history(
|
|
symbol: str,
|
|
period: str = "1mo",
|
|
interval: str = "1d",
|
|
) -> dict:
|
|
"""
|
|
Get historical stock prices from Yahoo Finance.
|
|
|
|
Args:
|
|
symbol: Stock ticker symbol
|
|
period: Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)
|
|
interval: Data interval (1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo)
|
|
|
|
Returns:
|
|
Dictionary with historical price data
|
|
"""
|
|
try:
|
|
import yfinance as yf
|
|
|
|
ticker = yf.Ticker(symbol.upper())
|
|
hist = ticker.history(period=period, interval=interval)
|
|
|
|
if hist.empty:
|
|
return {
|
|
"success": False,
|
|
"error": f"No historical data found for {symbol}",
|
|
"source": "yahoo_finance",
|
|
}
|
|
|
|
# Convert to list of dicts
|
|
prices = []
|
|
for index, row in hist.iterrows():
|
|
prices.append({
|
|
"date": index.isoformat(),
|
|
"open": round(row["Open"], 2),
|
|
"high": round(row["High"], 2),
|
|
"low": round(row["Low"], 2),
|
|
"close": round(row["Close"], 2),
|
|
"volume": int(row["Volume"]),
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"source": "yahoo_finance",
|
|
"symbol": symbol.upper(),
|
|
"period": period,
|
|
"interval": interval,
|
|
"prices": prices,
|
|
"count": len(prices),
|
|
}
|
|
|
|
except ImportError:
|
|
return {
|
|
"success": False,
|
|
"error": "yfinance not installed. Run: pip install yfinance",
|
|
"source": "yahoo_finance",
|
|
}
|
|
except Exception as e:
|
|
log.error(f"Stock history fetch failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "yahoo_finance",
|
|
}
|
|
|
|
|
|
def finance_get_crypto_price(
|
|
coin_id: str = "bitcoin",
|
|
vs_currency: str = "usd",
|
|
) -> dict:
|
|
"""
|
|
Get cryptocurrency price from CoinGecko.
|
|
|
|
Args:
|
|
coin_id: Coin ID (e.g., bitcoin, ethereum, dogecoin) - use coin name from CoinGecko
|
|
vs_currency: Currency to show price in (e.g., usd, eur, btc)
|
|
|
|
Returns:
|
|
Dictionary with cryptocurrency data
|
|
"""
|
|
try:
|
|
url = f"{COINGECKO_API}/simple/price"
|
|
params = {
|
|
"ids": coin_id.lower(),
|
|
"vs_currencies": vs_currency.lower(),
|
|
"include_market_cap": "true",
|
|
"include_24hr_vol": "true",
|
|
"include_24hr_change": "true",
|
|
"include_last_updated_at": "true",
|
|
}
|
|
|
|
response = requests.get(url, params=params, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
if coin_id.lower() not in data:
|
|
return {
|
|
"success": False,
|
|
"error": f"Coin not found: {coin_id}. Try using the full coin name (e.g., 'bitcoin' not 'btc')",
|
|
"source": "coingecko",
|
|
}
|
|
|
|
coin_data = data[coin_id.lower()]
|
|
return {
|
|
"success": True,
|
|
"source": "coingecko",
|
|
"coin_id": coin_id.lower(),
|
|
"currency": vs_currency.lower(),
|
|
"price": coin_data.get(vs_currency.lower()),
|
|
"market_cap": coin_data.get(f"{vs_currency.lower()}_market_cap"),
|
|
"24h_volume": coin_data.get(f"{vs_currency.lower()}_24h_vol"),
|
|
"24h_change": coin_data.get(f"{vs_currency.lower()}_24h_change"),
|
|
"last_updated": datetime.fromtimestamp(
|
|
coin_data.get("last_updated_at", 0)
|
|
).isoformat() if coin_data.get("last_updated_at") else None,
|
|
}
|
|
|
|
except Exception as e:
|
|
log.error(f"Crypto price fetch failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "coingecko",
|
|
}
|
|
|
|
|
|
def finance_get_top_cryptos(
|
|
limit: int = 10,
|
|
vs_currency: str = "usd",
|
|
) -> dict:
|
|
"""
|
|
Get top cryptocurrencies by market cap from CoinGecko.
|
|
|
|
Args:
|
|
limit: Number of coins to return (default: 10)
|
|
vs_currency: Currency for prices (default: usd)
|
|
|
|
Returns:
|
|
Dictionary with top cryptocurrencies
|
|
"""
|
|
try:
|
|
url = f"{COINGECKO_API}/coins/markets"
|
|
params = {
|
|
"vs_currency": vs_currency.lower(),
|
|
"order": "market_cap_desc",
|
|
"per_page": limit,
|
|
"page": 1,
|
|
"sparkline": "false",
|
|
}
|
|
|
|
response = requests.get(url, params=params, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
results = []
|
|
for coin in data:
|
|
results.append({
|
|
"id": coin.get("id"),
|
|
"symbol": coin.get("symbol", "").upper(),
|
|
"name": coin.get("name"),
|
|
"price": coin.get("current_price"),
|
|
"market_cap": coin.get("market_cap"),
|
|
"market_cap_rank": coin.get("market_cap_rank"),
|
|
"24h_change": coin.get("price_change_percentage_24h"),
|
|
"volume": coin.get("total_volume"),
|
|
"circulating_supply": coin.get("circulating_supply"),
|
|
"image": coin.get("image"),
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"source": "coingecko",
|
|
"currency": vs_currency.lower(),
|
|
"results": results,
|
|
"count": len(results),
|
|
}
|
|
|
|
except Exception as e:
|
|
log.error(f"Top cryptos fetch failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "coingecko",
|
|
}
|
|
|
|
|
|
def finance_get_exchange_rate(
|
|
base_currency: str = "USD",
|
|
target_currency: Optional[str] = None,
|
|
) -> dict:
|
|
"""
|
|
Get exchange rates from ExchangeRate-API (free).
|
|
|
|
Args:
|
|
base_currency: Base currency code (default: USD)
|
|
target_currency: Target currency code (optional, returns all if not specified)
|
|
|
|
Returns:
|
|
Dictionary with exchange rate(s)
|
|
"""
|
|
try:
|
|
url = f"https://api.exchangerate-api.com/v4/latest/{base_currency.upper()}"
|
|
response = requests.get(url, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
rates = data.get("rates", {})
|
|
|
|
if target_currency:
|
|
target_currency = target_currency.upper()
|
|
if target_currency in rates:
|
|
return {
|
|
"success": True,
|
|
"source": "exchangerate-api",
|
|
"base": base_currency.upper(),
|
|
"target": target_currency,
|
|
"rate": rates[target_currency],
|
|
"last_updated": data.get("date"),
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": f"Currency not found: {target_currency}",
|
|
"source": "exchangerate-api",
|
|
}
|
|
|
|
return {
|
|
"success": True,
|
|
"source": "exchangerate-api",
|
|
"base": base_currency.upper(),
|
|
"rates": rates,
|
|
"count": len(rates),
|
|
"last_updated": data.get("date"),
|
|
}
|
|
|
|
except Exception as e:
|
|
log.error(f"Exchange rate fetch failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "exchangerate-api",
|
|
}
|
|
|
|
|
|
def finance_search_crypto(
|
|
query: str,
|
|
) -> dict:
|
|
"""
|
|
Search for cryptocurrencies on CoinGecko.
|
|
|
|
Args:
|
|
query: Search query (coin name or symbol)
|
|
|
|
Returns:
|
|
Dictionary with search results
|
|
"""
|
|
try:
|
|
url = f"{COINGECKO_API}/search"
|
|
params = {"query": query}
|
|
|
|
response = requests.get(url, params=params, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
coins = data.get("coins", [])[:10]
|
|
results = []
|
|
for coin in coins:
|
|
results.append({
|
|
"id": coin.get("id"),
|
|
"symbol": coin.get("symbol", "").upper(),
|
|
"name": coin.get("name"),
|
|
"market_cap_rank": coin.get("market_cap_rank"),
|
|
"thumb": coin.get("thumb"),
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"source": "coingecko",
|
|
"query": query,
|
|
"results": results,
|
|
"count": len(results),
|
|
}
|
|
|
|
except Exception as e:
|
|
log.error(f"Crypto search failed: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"source": "coingecko",
|
|
}
|
|
|
|
|
|
# Tool schemas for OpenAI function calling
|
|
FINANCE_GET_STOCK_INFO_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_get_stock_info",
|
|
"description": "Get current stock information and key financial metrics from Yahoo Finance. Use for stock quotes and company data.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {
|
|
"type": "string",
|
|
"description": "Stock ticker symbol (e.g., AAPL, GOOGL, TSLA, MSFT)",
|
|
},
|
|
},
|
|
"required": ["symbol"],
|
|
},
|
|
},
|
|
}
|
|
|
|
FINANCE_GET_STOCK_HISTORY_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_get_stock_history",
|
|
"description": "Get historical stock prices from Yahoo Finance. Use for price trends and charts.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"symbol": {
|
|
"type": "string",
|
|
"description": "Stock ticker symbol",
|
|
},
|
|
"period": {
|
|
"type": "string",
|
|
"description": "Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, max)",
|
|
"default": "1mo",
|
|
},
|
|
"interval": {
|
|
"type": "string",
|
|
"description": "Data interval (1m, 5m, 15m, 1h, 1d, 1wk, 1mo)",
|
|
"default": "1d",
|
|
},
|
|
},
|
|
"required": ["symbol"],
|
|
},
|
|
},
|
|
}
|
|
|
|
FINANCE_GET_CRYPTO_PRICE_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_get_crypto_price",
|
|
"description": "Get cryptocurrency price and market data from CoinGecko. Use the full coin name (e.g., 'bitcoin' not 'btc').",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"coin_id": {
|
|
"type": "string",
|
|
"description": "CoinGecko coin ID (e.g., bitcoin, ethereum, dogecoin, solana)",
|
|
},
|
|
"vs_currency": {
|
|
"type": "string",
|
|
"description": "Currency for price (default: usd)",
|
|
"default": "usd",
|
|
},
|
|
},
|
|
"required": ["coin_id"],
|
|
},
|
|
},
|
|
}
|
|
|
|
FINANCE_GET_TOP_CRYPTOS_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_get_top_cryptos",
|
|
"description": "Get top cryptocurrencies by market capitalization from CoinGecko.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Number of coins to return (default: 10)",
|
|
"default": 10,
|
|
},
|
|
"vs_currency": {
|
|
"type": "string",
|
|
"description": "Currency for prices (default: usd)",
|
|
"default": "usd",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
},
|
|
}
|
|
|
|
FINANCE_GET_EXCHANGE_RATE_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_get_exchange_rate",
|
|
"description": "Get currency exchange rates. Returns all rates for base currency or specific rate if target provided.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"base_currency": {
|
|
"type": "string",
|
|
"description": "Base currency code (default: USD)",
|
|
"default": "USD",
|
|
},
|
|
"target_currency": {
|
|
"type": "string",
|
|
"description": "Target currency code (optional, returns all if not specified)",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
},
|
|
}
|
|
|
|
FINANCE_SEARCH_CRYPTO_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "finance_search_crypto",
|
|
"description": "Search for cryptocurrencies on CoinGecko by name or symbol. Use this to find the correct coin_id for finance_get_crypto_price.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {
|
|
"type": "string",
|
|
"description": "Search query (coin name or symbol)",
|
|
},
|
|
},
|
|
"required": ["query"],
|
|
},
|
|
},
|
|
}
|