docrag/tools/finance_tool.py
Z User b811162f78 Implement tool calling loop for LLM
- 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
2026-03-29 16:07:56 +00:00

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"],
},
},
}