Working
This commit is contained in:
parent
f10f056e0d
commit
6829decfbb
815
jupyter/stock.py
815
jupyter/stock.py
@ -1,322 +1,123 @@
|
|||||||
# %%
|
# %%
|
||||||
import os
|
# Imports [standard]
|
||||||
import json
|
# Standard library modules (no try-except needed)
|
||||||
from dotenv import load_dotenv
|
import argparse
|
||||||
from openai import OpenAI
|
|
||||||
import gradio as gr
|
|
||||||
import ollama
|
|
||||||
import re
|
|
||||||
import yfinance as yf
|
|
||||||
from datetime import datetime
|
|
||||||
import pytz
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
import time
|
|
||||||
import pydle
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import anyio
|
import anyio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def try_import(module_name, pip_name=None):
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
except ImportError:
|
||||||
|
print(f"Module '{module_name}' not found. Install it using:")
|
||||||
|
print(f" pip install {pip_name or module_name}")
|
||||||
|
|
||||||
|
# Third-party modules with import checks
|
||||||
|
try_import('gradio')
|
||||||
|
try_import('ollama')
|
||||||
|
try_import('openai')
|
||||||
|
try_import('pydle')
|
||||||
|
try_import('pytz')
|
||||||
|
try_import('requests')
|
||||||
|
try_import('yfinance', 'yfinance')
|
||||||
|
try_import('dotenv', 'python-dotenv')
|
||||||
|
try_import('geopy', 'geopy')
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from geopy.geocoders import Nominatim
|
||||||
|
import gradio as gr
|
||||||
|
import ollama
|
||||||
|
import openai
|
||||||
|
import pydle
|
||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
import yfinance as yf
|
||||||
|
|
||||||
|
# Local defined imports
|
||||||
|
from tools import (
|
||||||
|
get_weather_by_location,
|
||||||
|
get_current_datetime,
|
||||||
|
get_ticker_price,
|
||||||
|
tools
|
||||||
|
)
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
system_message = """
|
# Defaults
|
||||||
You are a helpful stock trading agent.
|
OLLAMA_API_URL = "http://ollama:11434" # Default Ollama local endpoint
|
||||||
You have real time access to current stock trading values and other information including the current date and time and current weather information.
|
#MODEL_NAME = "deepseek-r1:7b"
|
||||||
If you succeed in getting stock values or time information, do not mention your knowledge cutoff.
|
MODEL_NAME = "llama3.2"
|
||||||
Give short, courteous answers, no more than 1 sentence.
|
CHANNEL = "#airc-test"
|
||||||
You have access to several tools for obtaining real-time information.
|
NICK = "airc"
|
||||||
Always be accurate.
|
IRC_SERVER = "miniircd"
|
||||||
If you don't know the answer, say so. Do not make up details.
|
IRC_PORT = 6667
|
||||||
|
LOG_LEVEL="debug"
|
||||||
|
USE_TLS=False
|
||||||
|
GRADIO_HOST="0.0.0.0"
|
||||||
|
GRADIO_PORT=60673
|
||||||
|
GRADIO_ENABLE=False
|
||||||
|
BOT_ADMIN="james"
|
||||||
|
|
||||||
|
# %%
|
||||||
|
# Globals
|
||||||
|
system_message = f"""
|
||||||
|
You are a helpful information agent connected to the IRC network {IRC_SERVER}. Your name is {NICK}.
|
||||||
|
You are running { { 'model': MODEL_NAME, 'gpu': 'Intel Arc B580', 'cpu': 'Intel Core i9-14900KS', 'ram': '64G' } }.
|
||||||
|
You were launched on {get_current_datetime()}.
|
||||||
|
You have real time access to current stock trading values, the current date and time, and current weather information for locations in the United States.
|
||||||
|
If you use any real time access, do not mention your knowledge cutoff.
|
||||||
|
Give short, courteous answers, no more than 2-3 sentences, keeping the answer less than about 100 characters.
|
||||||
|
If you have to cut the answer short, ask the user if they want more information and provide it if they say Yes.
|
||||||
|
Always be accurate. If you don't know the answer, say so. Do not make up details.
|
||||||
|
|
||||||
|
You have tools to:
|
||||||
|
* get_current_datetime: Get current time and date.
|
||||||
|
* get_weather_by_location: Get-real time weather forecast.
|
||||||
|
* get_ticker_price: Get real-time value of a stock symbol.
|
||||||
|
|
||||||
|
Those are the only tools available.
|
||||||
"""
|
"""
|
||||||
system_log = [{"role": "system", "content": system_message}]
|
system_log = [{"role": "system", "content": system_message}]
|
||||||
history = []
|
history = []
|
||||||
tool_log = []
|
tool_log = []
|
||||||
|
command_log = []
|
||||||
|
model = None
|
||||||
|
client = None
|
||||||
|
irc_bot = None
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
client = ollama.Client(host="http://ollama:11434")
|
# Cmd line overrides
|
||||||
#model = 'deepseek-r1:7b' # Does not support tools with template used by ollama
|
def parse_args():
|
||||||
model = 'llama3.2'
|
parser = argparse.ArgumentParser(description="AI is Really Cool")
|
||||||
|
parser.add_argument("--irc-server", type=str, default=IRC_SERVER, help=f"IRC server address. default={IRC_SERVER}")
|
||||||
|
parser.add_argument("--irc-port", type=int, default=IRC_PORT, help=f"IRC server port. default={IRC_PORT}")
|
||||||
|
parser.add_argument("--irc-nickname", type=str, default=NICK, help=f"Bot nickname. default={NICK}")
|
||||||
|
parser.add_argument("--irc-channel", type=str, default=CHANNEL, help=f"Channel to join. default={CHANNEL}")
|
||||||
|
parser.add_argument("--irc-use-tls", type=bool, default=USE_TLS, help=f"Use TLS with --irc-server. default={USE_TLS}")
|
||||||
|
parser.add_argument("--ollama-server", type=str, default=OLLAMA_API_URL, help=f"Ollama API endpoint. default={OLLAMA_API_URL}")
|
||||||
|
parser.add_argument("--ollama-model", type=str, default=MODEL_NAME, help=f"LLM model to use. default={MODEL_NAME}")
|
||||||
|
parser.add_argument("--gradio-host", type=str, default=GRADIO_HOST, help=f"Host to launch gradio on. default={GRADIO_HOST} only if --gradio-enable is specified.")
|
||||||
|
parser.add_argument("--gradio-port", type=str, default=GRADIO_PORT, help=f"Port to launch gradio on. default={GRADIO_PORT} only if --gradio-enable is specified.")
|
||||||
|
parser.add_argument("--gradio-enable", action="store_true", default=GRADIO_ENABLE, help=f"If set to True, enable Gradio. default={GRADIO_ENABLE}")
|
||||||
|
parser.add_argument("--bot-admin", type=str, default=BOT_ADMIN, help=f"Nick that can send admin commands via IRC. default={BOT_ADMIN}")
|
||||||
|
parser.add_argument('--level', type=str, choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
|
||||||
|
default=LOG_LEVEL, help=f'Set the logging level. default={LOG_LEVEL}')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
# %%
|
def setup_logging(level):
|
||||||
def get_weather_by_location(city, state, country="USA"):
|
numeric_level = getattr(logging, level.upper(), None)
|
||||||
"""
|
if not isinstance(numeric_level, int):
|
||||||
Get weather information from weather.gov based on city, state, and country.
|
raise ValueError(f"Invalid log level: {level}")
|
||||||
|
|
||||||
Args:
|
|
||||||
city (str): City name
|
|
||||||
state (str): State name or abbreviation
|
|
||||||
country (str): Country name (defaults to "USA" as weather.gov is for US locations)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Weather forecast information
|
|
||||||
"""
|
|
||||||
# Step 1: Get coordinates for the location using geocoding
|
|
||||||
location = f"{city}, {state}, {country}"
|
|
||||||
coordinates = get_coordinates(location)
|
|
||||||
|
|
||||||
if not coordinates:
|
|
||||||
return {"error": f"Could not find coordinates for {location}"}
|
|
||||||
|
|
||||||
# Step 2: Get the forecast grid endpoint for the coordinates
|
|
||||||
grid_endpoint = get_grid_endpoint(coordinates)
|
|
||||||
|
|
||||||
if not grid_endpoint:
|
|
||||||
return {"error": f"Could not find weather grid for coordinates {coordinates}"}
|
|
||||||
|
|
||||||
# Step 3: Get the forecast data from the grid endpoint
|
|
||||||
forecast = get_forecast(grid_endpoint)
|
|
||||||
|
|
||||||
return forecast
|
|
||||||
|
|
||||||
def get_coordinates(location):
|
|
||||||
"""Convert a location string to latitude and longitude using Nominatim geocoder."""
|
|
||||||
try:
|
|
||||||
# Create a geocoder with a meaningful user agent
|
|
||||||
geolocator = Nominatim(user_agent="weather_app_example")
|
|
||||||
|
|
||||||
# Get the location
|
|
||||||
location_data = geolocator.geocode(location)
|
|
||||||
|
|
||||||
if location_data:
|
|
||||||
return {
|
|
||||||
"latitude": location_data.latitude,
|
|
||||||
"longitude": location_data.longitude
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
print(f"Location not found: {location}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error getting coordinates: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_grid_endpoint(coordinates):
|
|
||||||
"""Get the grid endpoint from weather.gov based on coordinates."""
|
|
||||||
try:
|
|
||||||
lat = coordinates["latitude"]
|
|
||||||
lon = coordinates["longitude"]
|
|
||||||
|
|
||||||
# Define headers for the API request
|
|
||||||
headers = {
|
|
||||||
"User-Agent": "WeatherAppExample/1.0 (your_email@example.com)",
|
|
||||||
"Accept": "application/geo+json"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make the request to get the grid endpoint
|
|
||||||
url = f"https://api.weather.gov/points/{lat},{lon}"
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
return data["properties"]["forecast"]
|
|
||||||
else:
|
|
||||||
print(f"Error getting grid: {response.status_code} - {response.text}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in get_grid_endpoint: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_forecast(grid_endpoint):
|
|
||||||
"""Get the forecast data from the grid endpoint."""
|
|
||||||
try:
|
|
||||||
# Define headers for the API request
|
|
||||||
headers = {
|
|
||||||
"User-Agent": "WeatherAppExample/1.0 (your_email@example.com)",
|
|
||||||
"Accept": "application/geo+json"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make the request to get the forecast
|
|
||||||
response = requests.get(grid_endpoint, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# Extract the relevant forecast information
|
|
||||||
periods = data["properties"]["periods"]
|
|
||||||
|
|
||||||
# Process the forecast data into a simpler format
|
|
||||||
forecast = {
|
|
||||||
"location": data["properties"].get("relativeLocation", {}).get("properties", {}),
|
|
||||||
"updated": data["properties"].get("updated", ""),
|
|
||||||
"periods": []
|
|
||||||
}
|
|
||||||
|
|
||||||
for period in periods:
|
|
||||||
forecast["periods"].append({
|
|
||||||
"name": period.get("name", ""),
|
|
||||||
"temperature": period.get("temperature", ""),
|
|
||||||
"temperatureUnit": period.get("temperatureUnit", ""),
|
|
||||||
"windSpeed": period.get("windSpeed", ""),
|
|
||||||
"windDirection": period.get("windDirection", ""),
|
|
||||||
"shortForecast": period.get("shortForecast", ""),
|
|
||||||
"detailedForecast": period.get("detailedForecast", "")
|
|
||||||
})
|
|
||||||
|
|
||||||
return forecast
|
|
||||||
else:
|
|
||||||
print(f"Error getting forecast: {response.status_code} - {response.text}")
|
|
||||||
return {"error": f"API Error: {response.status_code}"}
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in get_forecast: {e}")
|
|
||||||
return {"error": f"Exception: {str(e)}"}
|
|
||||||
|
|
||||||
# Example usage
|
|
||||||
def do_weather():
|
|
||||||
city = input("Enter city: ")
|
|
||||||
state = input("Enter state: ")
|
|
||||||
country = input("Enter country (default USA): ") or "USA"
|
|
||||||
|
|
||||||
print(f"Getting weather for {city}, {state}, {country}...")
|
logging.basicConfig(level=numeric_level, format='%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s')
|
||||||
weather_data = get_weather_by_location(city, state, country)
|
|
||||||
|
|
||||||
if "error" in weather_data:
|
logging.info(f"Logging is set to {level} level.")
|
||||||
print(f"Error: {weather_data['error']}")
|
|
||||||
else:
|
|
||||||
print("\nWeather Forecast:")
|
|
||||||
print(f"Location: {weather_data.get('location', {}).get('city', city)}, {weather_data.get('location', {}).get('state', state)}")
|
|
||||||
print(f"Last Updated: {weather_data.get('updated', 'N/A')}")
|
|
||||||
print("\nForecast Periods:")
|
|
||||||
|
|
||||||
for period in weather_data.get("periods", []):
|
|
||||||
print(f"\n{period['name']}:")
|
|
||||||
print(f" Temperature: {period['temperature']}{period['temperatureUnit']}")
|
|
||||||
print(f" Wind: {period['windSpeed']} {period['windDirection']}")
|
|
||||||
print(f" Forecast: {period['shortForecast']}")
|
|
||||||
print(f" Details: {period['detailedForecast']}")
|
|
||||||
|
|
||||||
# %%
|
|
||||||
def get_ticker_price(ticker_symbols):
|
|
||||||
"""
|
|
||||||
Look up the current price of a stock using its ticker symbol.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ticker_symbol (str): The stock ticker symbol (e.g., 'AAPL' for Apple)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Current stock information including price
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
print(f"get_ticker_price('{ticker_symbols}')")
|
|
||||||
for ticker_symbol in ticker_symbols.split(','):
|
|
||||||
ticker_symbol = ticker_symbol.strip()
|
|
||||||
if ticker_symbol == "":
|
|
||||||
continue
|
|
||||||
# Create a Ticker object
|
|
||||||
try:
|
|
||||||
ticker = yf.Ticker(ticker_symbol)
|
|
||||||
|
|
||||||
# Get the latest market data
|
|
||||||
ticker_data = ticker.history(period="1d")
|
|
||||||
|
|
||||||
if ticker_data.empty:
|
|
||||||
results.append({"error": f"No data found for ticker {ticker_symbol}"})
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Get the latest closing price
|
|
||||||
latest_price = ticker_data['Close'].iloc[-1]
|
|
||||||
|
|
||||||
# Get some additional info
|
|
||||||
info = ticker.info
|
|
||||||
results.append({ 'symbol': ticker_symbol, 'price': latest_price })
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
results.append({"error": f"Error fetching data for {ticker_symbol}: {str(e)}"})
|
|
||||||
|
|
||||||
return results[0] if len(results) == 1 else results
|
|
||||||
#{
|
|
||||||
# "symbol": ticker_symbol,
|
|
||||||
# "price": latest_price,
|
|
||||||
# "currency": info.get("currency", "Unknown"),
|
|
||||||
# "company_name": info.get("shortName", "Unknown"),
|
|
||||||
# "previous_close": info.get("previousClose", "Unknown"),
|
|
||||||
# "market_cap": info.get("marketCap", "Unknown"),
|
|
||||||
#}
|
|
||||||
|
|
||||||
|
|
||||||
# %%
|
|
||||||
def get_current_datetime(timezone="America/Los_Angeles"):
|
|
||||||
"""
|
|
||||||
Returns the current date and time in the specified timezone in ISO 8601 format.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timezone (str): Timezone name (e.g., "UTC", "America/New_York", "Europe/London")
|
|
||||||
Default is "America/Los_Angeles"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Current date and time with timezone in the format YYYY-MM-DDTHH:MM:SS+HH:MM
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if timezone == 'system' or timezone == '' or not timezone:
|
|
||||||
timezone = 'America/Los_Angeles'
|
|
||||||
# Get current UTC time (timezone-aware)
|
|
||||||
local_tz = pytz.timezone("America/Los_Angeles")
|
|
||||||
local_now = datetime.now(tz=local_tz)
|
|
||||||
|
|
||||||
# Convert to target timezone
|
|
||||||
target_tz = pytz.timezone(timezone)
|
|
||||||
target_time = local_now.astimezone(target_tz)
|
|
||||||
|
|
||||||
return target_time.isoformat()
|
|
||||||
except Exception as e:
|
|
||||||
return {'error': f"Invalid timezone {timezone}: {str(e)}"}
|
|
||||||
|
|
||||||
|
|
||||||
# %%
|
|
||||||
tools = [{
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": "get_ticker_price",
|
|
||||||
"description": "Get the current stock price of one or more ticker symbols. Returns an array of objects with 'symbol' and 'price' fields. Call this whenever you need to know the latest value of stock ticker symbols, for example when a user asks 'How much is Intel trading at?' or 'What are the prices of AAPL and MSFT?'",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ticker": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The company stock ticker symbol. For multiple tickers, provide a comma-separated list (e.g., 'AAPL,MSFT,GOOGL').",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["ticker"],
|
|
||||||
"additionalProperties": False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": "get_current_datetime",
|
|
||||||
"description": "Get the current date and time in a specified timezone",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"timezone": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Timezone name (e.g., 'UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'). Default is 'America/Los_Angeles'."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": "get_weather_by_location",
|
|
||||||
"description": "Get the current and future weather for a given CITY and STATE location in the United States. For example, if the user asks 'What is the weather in Portland?' or 'What is the forecast for tomorrow?'",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"city": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "City to find the weather (e.g., 'Portland', 'Seattle')."
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "State to find the weather (e.g., 'OR', 'WA')."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [ "city", "state" ],
|
|
||||||
"additionalProperties": False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
def handle_tool_calls(message):
|
def handle_tool_calls(message):
|
||||||
@ -345,58 +146,20 @@ def handle_tool_calls(message):
|
|||||||
"content": str(ret),
|
"content": str(ret),
|
||||||
"name": tool_call['function']['name']
|
"name": tool_call['function']['name']
|
||||||
})
|
})
|
||||||
return response[0] if len(response) == 1 else response, tools_used
|
if len(response) == 1:
|
||||||
|
return response[0], tools_used
|
||||||
|
else:
|
||||||
|
return response, tools_used
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
irc_channel = None
|
async def chat(history, is_irc=False):
|
||||||
def chat(history, is_irc=False):
|
global client, model, irc_bot, system_log, tool_log
|
||||||
global irc_bot, irc_channel
|
if not client:
|
||||||
|
return history
|
||||||
|
|
||||||
message = history[-1]
|
message = history[-1]
|
||||||
|
|
||||||
logging.info("chat()")
|
logging.info(f"chat('{message}')")
|
||||||
if irc_channel and not is_irc:
|
|
||||||
asyncio.run(irc_bot.message(irc_channel, f"[console] {message['content']}"))
|
|
||||||
|
|
||||||
matches = re.match(r"^!([^\s]+)\s*(.*)$", message['content'])
|
|
||||||
if message['role'] == 'user' and matches:
|
|
||||||
command = matches.group(1)
|
|
||||||
arguments = matches.group(2).strip()
|
|
||||||
|
|
||||||
# Handle special IRC commands
|
|
||||||
if command == "test":
|
|
||||||
response = f"test received: {arguments}"
|
|
||||||
|
|
||||||
elif command == "server":
|
|
||||||
server = arguments.split(" ", 1)
|
|
||||||
if len(server) == 1:
|
|
||||||
server = "irc.libera.chat"
|
|
||||||
else:
|
|
||||||
server = server[1]
|
|
||||||
|
|
||||||
response = asyncio.run(irc_bot.connect(server, 6667, tls=False))
|
|
||||||
|
|
||||||
elif command == "join":
|
|
||||||
channel = arguments.strip()
|
|
||||||
if channel == "" or re.match(r"\s", channel):
|
|
||||||
response = "Usage: !join CHANNEL"
|
|
||||||
else:
|
|
||||||
if not re.match(r"^#", channel):
|
|
||||||
channel = f"#{channel}"
|
|
||||||
if irc_channel and irc_channel != channel:
|
|
||||||
asyncio.run(irc_bot.part(channel))
|
|
||||||
if channel:
|
|
||||||
asyncio.run(irc_bot.join(channel))
|
|
||||||
irc_channel = channel
|
|
||||||
response = f"Joined {channel}."
|
|
||||||
|
|
||||||
else:
|
|
||||||
response = f"Unrecognized command: {command}"
|
|
||||||
|
|
||||||
airc_response = [{ "role": "user", "content": response, "metadata": { "title": f"airc: {command}"}}]
|
|
||||||
|
|
||||||
return history + airc_response
|
|
||||||
|
|
||||||
#return response, chat_history
|
|
||||||
messages = system_log + history
|
messages = system_log + history
|
||||||
response = client.chat(model=model, messages=messages, tools=tools)
|
response = client.chat(model=model, messages=messages, tools=tools)
|
||||||
tools_used = []
|
tools_used = []
|
||||||
@ -405,7 +168,11 @@ def chat(history, is_irc=False):
|
|||||||
tool_result, tools_used = handle_tool_calls(message)
|
tool_result, tools_used = handle_tool_calls(message)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
messages.append(tool_result)
|
messages.append(tool_result)
|
||||||
response = client.chat(model=model, messages=messages)
|
try:
|
||||||
|
response = client.chat(model=model, messages=messages)
|
||||||
|
except Exception:
|
||||||
|
logging.exception({ 'model': model, 'messages': messages })
|
||||||
|
|
||||||
tool_log.append({ 'call': message, 'tool_result': tool_result, 'tools_used': tools_used, 'response': response['message']['content']})
|
tool_log.append({ 'call': message, 'tool_result': tool_result, 'tools_used': tools_used, 'response': response['message']['content']})
|
||||||
|
|
||||||
reply = response['message']['content']
|
reply = response['message']['content']
|
||||||
@ -414,191 +181,285 @@ def chat(history, is_irc=False):
|
|||||||
else:
|
else:
|
||||||
history += [{"role":"assistant", "content":reply}]
|
history += [{"role":"assistant", "content":reply}]
|
||||||
|
|
||||||
if irc_channel:
|
|
||||||
asyncio.run(irc_bot.message(irc_channel, reply))
|
|
||||||
|
|
||||||
return history
|
return history
|
||||||
|
|
||||||
|
# %%
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
# Queue to pass messages from IRC and CLI thread to Gradio
|
|
||||||
message_queue = queue.Queue()
|
|
||||||
|
|
||||||
def check_message_queue(history=[]):
|
class DynamicIRCBot(pydle.Client):
|
||||||
if message_queue.empty():
|
def __init__(self, nickname, channel, bot_admin, system_info, **kwargs):
|
||||||
return gr.skip()
|
super().__init__(nickname, **kwargs)
|
||||||
|
self.history = []
|
||||||
while not message_queue.empty():
|
self.channel = channel
|
||||||
irc_message = message_queue.get_nowait()
|
self.bot_admin = bot_admin
|
||||||
logging.info(f"Processing: {irc_message}")
|
self.system_info = system_info
|
||||||
# Add the CLI message to chat history
|
|
||||||
last_message = history[-1] if len(history) else None
|
async def on_connect(self):
|
||||||
|
await super().on_connect()
|
||||||
|
logging.info(f"CONNECT: {self.nickname}")
|
||||||
|
if self.channel:
|
||||||
|
await self.join(self.channel, self.nickname)
|
||||||
|
|
||||||
|
async def on_join(self, channel, user):
|
||||||
|
await super().on_join(channel, user)
|
||||||
|
logging.info(f"JOIN: {user} => {channel}")
|
||||||
|
|
||||||
|
async def on_part(self, channel, user):
|
||||||
|
await super().on_part(channel, user)
|
||||||
|
logging.info(f"PART: {channel} => {user}")
|
||||||
|
|
||||||
|
async def on_message(self, target, source, message, is_gradio=False):
|
||||||
|
global system_log, tool_log, system_log, command_log
|
||||||
|
|
||||||
|
if not is_gradio:
|
||||||
|
await super().on_message(target, source, message)
|
||||||
|
message = message.strip()
|
||||||
|
logging.info(f"MESSAGE: {source} => {target}: {message}")
|
||||||
|
if source == self.nickname and not is_gradio:
|
||||||
|
return
|
||||||
|
last_message = self.history[-1] if len(self.history) > 0 and self.history[-1]["role"] == "user" else None
|
||||||
try:
|
try:
|
||||||
matches = re.match(r"^([^:]+)\s*:\s*(.*)$", irc_message['message'])
|
matches = re.match(r"^([^:]+)\s*:\s*(.*)$", message)
|
||||||
if not matches:
|
if matches:
|
||||||
if last_message and last_message["role"] == "user":
|
|
||||||
logging.info("Not a user directed message: modifying last USER context")
|
|
||||||
last_message['content'] += irc_message['message']
|
|
||||||
history[-1] = last_message
|
|
||||||
else:
|
|
||||||
logging.info("Not a user directed message: appending new USER context")
|
|
||||||
history.append({
|
|
||||||
"role": "user",
|
|
||||||
"content": irc_message['message']
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
user = matches.group(1).strip()
|
user = matches.group(1).strip()
|
||||||
content = matches.group(2).strip()
|
content = matches.group(2).strip()
|
||||||
if user != irc_bot.nickname:
|
else:
|
||||||
if last_message and last_message["role"] == "user":
|
user = None
|
||||||
logging.info(f"Not directed message to {irc_bot.nickname} ({user}): modifying last USER context")
|
content = message
|
||||||
last_message['content'] += irc_message['message']
|
|
||||||
history[-1] = last_message
|
|
||||||
else:
|
|
||||||
logging.info(f"Not directed message to {irc_bot.nickname} ({user}): appending a new USER context")
|
|
||||||
history.append({
|
|
||||||
"role": "user",
|
|
||||||
"content": content
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
if last_message and last_message["role"] == "user":
|
|
||||||
logging.info(f"Directed message to {irc_bot.nickname}: modifying last USER context")
|
|
||||||
last_message['content'] += f"\n{irc_message['message']}"
|
|
||||||
history[-1] = last_message
|
|
||||||
else:
|
|
||||||
logging.info(f"Directed message to {irc_bot.nickname}: appending a new USER context")
|
|
||||||
history.append({
|
|
||||||
"role": "user",
|
|
||||||
"content": f"\n{irc_message['message']}"
|
|
||||||
})
|
|
||||||
history = chat(history, is_irc=True)
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.error({ "error": f"Unable to process message {irc_message}", "exception": e})
|
|
||||||
|
|
||||||
return history
|
|
||||||
|
# If this message is not directed to the bot
|
||||||
|
if not user or user != self.nickname:
|
||||||
|
logging.info(f"Message not directed to {self.nickname}")
|
||||||
|
# Add this message to the history either to the current 'user' context or create
|
||||||
|
# add a new message
|
||||||
|
if last_message:
|
||||||
|
logging.info(f"Modifying last USER context")
|
||||||
|
last_message['content'] += f"\n{source}: {content}"
|
||||||
|
else:
|
||||||
|
logging.info(f"Appending new USER context")
|
||||||
|
last_message = {
|
||||||
|
"role": "user",
|
||||||
|
"content": f"{source}: {content}"
|
||||||
|
}
|
||||||
|
self.history.append(last_message)
|
||||||
|
return
|
||||||
|
|
||||||
|
matches = re.match(r"^!([^\s]+)\s*(.*)?$", content)
|
||||||
|
if not matches or (self.bot_admin and source != self.bot_admin and source != self.nickname):
|
||||||
|
logging.info(f"Non-command directed message to {self.nickname}: Invoking chat...")
|
||||||
|
# Add this message to the history either to the current 'user' context or create
|
||||||
|
# add a new message
|
||||||
|
if last_message:
|
||||||
|
logging.info(f"Modifying last USER context")
|
||||||
|
last_message['content'] += f"\n{source}: {content}"
|
||||||
|
else:
|
||||||
|
logging.info(f"Appending new USER context")
|
||||||
|
last_message = {
|
||||||
|
"role": "user",
|
||||||
|
"content": f"{source}: {content}"
|
||||||
|
}
|
||||||
|
self.history.append(last_message)
|
||||||
|
self.history = await chat(self.history, is_irc=True)
|
||||||
|
chat_response = self.history[-1]
|
||||||
|
lines = [line.strip() for line in chat_response['content'].split('\n') if line.strip()]
|
||||||
|
if len(lines) > 10:
|
||||||
|
lines = lines[:9] + ["...and so on. I'll send the rest to you in a PRIVMSG (wip) :)"]
|
||||||
|
await self.message(target, "\n".join(lines))
|
||||||
|
return
|
||||||
|
|
||||||
|
command = matches.group(1)
|
||||||
|
arguments = matches.group(2).strip()
|
||||||
|
logging.info(f"Command directed to {self.nickname}: command={command}, arguments={arguments}")
|
||||||
|
|
||||||
|
match command:
|
||||||
|
case "test":
|
||||||
|
response = f"test received: {arguments}"
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
response = str(self.system_info)
|
||||||
|
|
||||||
|
case "context":
|
||||||
|
if len(self.history) > 1:
|
||||||
|
response = '"' + '","'.join(self.history[-1]['content'].split('\n')) + '"'
|
||||||
|
else:
|
||||||
|
response = "<no context>"
|
||||||
|
|
||||||
|
case "reset":
|
||||||
|
system_log = [{"role": "system", "content": system_message}]
|
||||||
|
history = []
|
||||||
|
tool_log = []
|
||||||
|
command_log = []
|
||||||
|
response = 'All contexts reset'
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
if arguments != "":
|
||||||
|
system_log = [{"role": "system", "content": arguments}]
|
||||||
|
response = 'System message updated.'
|
||||||
|
else:
|
||||||
|
lines = [line.strip() for line in system_log[0]['content'].split('\n') if line.strip()]
|
||||||
|
response = " ".join(lines)
|
||||||
|
|
||||||
|
case "server":
|
||||||
|
server = arguments.split(" ", 1)
|
||||||
|
if len(server) == 1:
|
||||||
|
server = IRC_SERVER
|
||||||
|
else:
|
||||||
|
server = server[1]
|
||||||
|
try:
|
||||||
|
await self.connect(server, 6667, tls=False)
|
||||||
|
except Exception:
|
||||||
|
logging.exception({ "error": f"Unable to process message {content}"})
|
||||||
|
reponse = f"Unable to connect to {server}"
|
||||||
|
|
||||||
|
case "join":
|
||||||
|
channel = arguments.strip()
|
||||||
|
if channel == "" or re.match(r"\s", channel):
|
||||||
|
response = "Usage: !join CHANNEL"
|
||||||
|
else:
|
||||||
|
if not re.match(r"^#", channel):
|
||||||
|
channel = f"#{channel}"
|
||||||
|
if self.channel and self.channel != channel:
|
||||||
|
await self.part(channel)
|
||||||
|
if channel:
|
||||||
|
await self.bot.join(channel)
|
||||||
|
self.channel = channel
|
||||||
|
response = f"Joined {channel}."
|
||||||
|
|
||||||
|
case _:
|
||||||
|
response = f"Unrecognized command: {command}"
|
||||||
|
|
||||||
|
await self.message(target, f"!{command}: {response}")
|
||||||
|
command_log.append({ 'source': source, 'command': f"{content}", 'response': response })
|
||||||
|
except Exception:
|
||||||
|
logging.exception({ "error": f"Unable to process message {content}"})
|
||||||
|
await self.message(target, f"I'm experiencing difficulties processing '{content}'")
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
def create_ui():
|
last_history_len = 0
|
||||||
global chatbot, timer
|
last_command_len = 0
|
||||||
with gr.Blocks() as ui:
|
css = """
|
||||||
with gr.Row():
|
body, html, #root {
|
||||||
with gr.Column(scale=2):
|
background-color: #F0F0F0;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
async def create_ui():
|
||||||
|
global irc_bot
|
||||||
|
|
||||||
|
with gr.Blocks(css=css, fill_height=True, fill_width=True) as ui:
|
||||||
|
with gr.Row(scale=1):
|
||||||
|
with gr.Column(scale=1):
|
||||||
chatbot = gr.Chatbot(
|
chatbot = gr.Chatbot(
|
||||||
# value=check_message_queue,
|
# value=check_message_queue,
|
||||||
# every=1,
|
# every=1,
|
||||||
type="messages",
|
type="messages",
|
||||||
height=600, # Set a fixed height
|
#height=600, # Set a fixed height
|
||||||
container=True, # Enable scrolling
|
container=True, # Enable scrolling
|
||||||
elem_id="chatbot",
|
elem_id="chatbot",
|
||||||
|
scale=1
|
||||||
)
|
)
|
||||||
entry = gr.Textbox(
|
entry = gr.Textbox(
|
||||||
label="Chat with our AI Assistant:",
|
label="Chat with our AI Assistant:",
|
||||||
container=False,
|
container=False,
|
||||||
scale=1
|
scale=0
|
||||||
)
|
)
|
||||||
with gr.Column(scale=1):
|
with gr.Column(scale=1):
|
||||||
chat_history = gr.JSON(
|
chat_history = gr.JSON(
|
||||||
system_log,
|
system_log,
|
||||||
label="Chat History",
|
label="Chat History",
|
||||||
height=300 # Set height for JSON viewer
|
height=100, # Set height for JSON viewer,
|
||||||
|
scale=1
|
||||||
)
|
)
|
||||||
tool_history = gr.JSON(
|
tool_history = gr.JSON(
|
||||||
tool_log,
|
tool_log,
|
||||||
label="Tool calls",
|
label="Tool calls",
|
||||||
height=300 # Set height for JSON viewer
|
height=100, # Set height for JSON viewer
|
||||||
|
scale=1
|
||||||
)
|
)
|
||||||
with gr.Row():
|
command_history = gr.JSON(
|
||||||
|
command_log,
|
||||||
|
label="Command calls",
|
||||||
|
height=100, # Set height for JSON viewer
|
||||||
|
scale=1
|
||||||
|
)
|
||||||
|
with gr.Row(scale=0):
|
||||||
clear = gr.Button("Clear")
|
clear = gr.Button("Clear")
|
||||||
timer = gr.Timer(1)
|
timer = gr.Timer(1)
|
||||||
|
|
||||||
def do_entry(message, history):
|
async def do_entry(message):
|
||||||
history += [{"role":"user", "content":message}]
|
if not irc_bot:
|
||||||
return "", history, system_log + history, tool_log # message, chat, logs
|
return gr.skip()
|
||||||
|
await irc_bot.message(irc_bot.channel, f"[console] {message}")
|
||||||
|
await irc_bot.on_message(irc_bot.channel, irc_bot.nickname, f"{irc_bot.nickname}: {message}", is_gradio=True)
|
||||||
|
return "", irc_bot.history
|
||||||
|
|
||||||
def do_clear():
|
def do_clear():
|
||||||
history = []
|
if not irc_bot:
|
||||||
|
return gr.skip()
|
||||||
|
irc_bot.history = []
|
||||||
tool_log = []
|
tool_log = []
|
||||||
return history, system_log, tool_log
|
command_log = []
|
||||||
|
return irc_bot.history, system_log, tool_log, command_log
|
||||||
|
|
||||||
def update_log(history):
|
def update_log(history):
|
||||||
# This function updates the log after the chatbot responds
|
# This function updates the log after the chatbot responds
|
||||||
return system_log + history, tool_log
|
return system_log + history, tool_log, command_log
|
||||||
|
|
||||||
|
def check_history():
|
||||||
|
global last_history_len, last_command_len
|
||||||
|
if not irc_bot or last_history_len == len(irc_bot.history):
|
||||||
|
history = gr.skip()
|
||||||
|
else:
|
||||||
|
history = irc_bot.history
|
||||||
|
last_history_len = len(irc_bot.history)
|
||||||
|
if last_command_len == len(command_log):
|
||||||
|
commands = gr.skip()
|
||||||
|
else:
|
||||||
|
commands = command_log
|
||||||
|
last_command_len = len(command_log)
|
||||||
|
|
||||||
|
return history, commands
|
||||||
|
|
||||||
entry.submit(
|
entry.submit(
|
||||||
do_entry,
|
do_entry,
|
||||||
inputs=[entry, chatbot],
|
inputs=[entry],
|
||||||
outputs=[entry, chatbot, chat_history, tool_history]
|
outputs=[entry, chatbot]
|
||||||
).then(
|
|
||||||
chat, # Your chat function that processes responses
|
|
||||||
inputs=chatbot,
|
|
||||||
outputs=chatbot
|
|
||||||
).then(
|
).then(
|
||||||
update_log, # This new function updates the log after chatbot processing
|
update_log, # This new function updates the log after chatbot processing
|
||||||
inputs=chatbot,
|
inputs=chatbot,
|
||||||
outputs=[chat_history, tool_history]
|
outputs=[chat_history, tool_history, command_history]
|
||||||
)
|
)
|
||||||
|
|
||||||
timer.tick(check_message_queue, inputs=chatbot, outputs=chatbot).then(
|
timer.tick(check_history, inputs=None, outputs=[chatbot, command_history])
|
||||||
update_log, # This new function updates the log after chatbot processing
|
|
||||||
inputs=chatbot,
|
|
||||||
outputs=[chat_history, tool_history]
|
|
||||||
)
|
|
||||||
|
|
||||||
clear.click(do_clear, inputs=None, outputs=[chatbot, chat_history, tool_history], queue=False)
|
clear.click(do_clear, inputs=None, outputs=[chatbot, chat_history, tool_history, command_history], queue=False)
|
||||||
|
|
||||||
return ui
|
return ui
|
||||||
|
|
||||||
# %%
|
# %%
|
||||||
|
|
||||||
# %%
|
|
||||||
irc_bot = None # Placeholder for the bot instance
|
|
||||||
|
|
||||||
class DynamicIRCBot(pydle.Client):
|
|
||||||
async def on_connect(self):
|
|
||||||
await super().on_connect()
|
|
||||||
print(f"CONNECT: {self.nickname}")
|
|
||||||
if irc_channel:
|
|
||||||
await self.join(irc_channel, self.nickname)
|
|
||||||
|
|
||||||
async def on_join(self, channel, user):
|
|
||||||
await super().on_join(channel, user)
|
|
||||||
print(f"JOIN: {user} => {channel}")
|
|
||||||
|
|
||||||
async def on_part(self, channel, user):
|
|
||||||
await super().on_part(channel, user)
|
|
||||||
print(f"PART: {channel} => {user}")
|
|
||||||
|
|
||||||
async def on_message(self, target, source, message):
|
|
||||||
global chatbot, timer
|
|
||||||
await super().on_message(target, source, message)
|
|
||||||
print(f"MESSAGE: {source} => {target}: {message}")
|
|
||||||
if source == self.nickname:
|
|
||||||
return
|
|
||||||
message_queue.put({'target': target, 'source': source, 'message': message})
|
|
||||||
|
|
||||||
# Main function to run everything
|
# Main function to run everything
|
||||||
async def main():
|
async def main():
|
||||||
setup_logging("DEBUG")
|
global irc_bot, client, model
|
||||||
|
# Parse command-line arguments
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
# Setup logging based on the provided level
|
||||||
|
setup_logging(args.level)
|
||||||
|
|
||||||
global irc_bot, irc_channel
|
client = ollama.Client(host=args.ollama_server)
|
||||||
irc_bot = DynamicIRCBot("airc")
|
model = args.ollama_model
|
||||||
irc_channel = "#airc-test"
|
|
||||||
# await irc_bot.connect("irc.libera.chat", 6667, tls=False)
|
|
||||||
await irc_bot.connect("miniircd", 6667, tls=False)
|
|
||||||
|
|
||||||
ui = create_ui()
|
irc_bot = DynamicIRCBot(args.irc_nickname, args.irc_channel, args.bot_admin, args)
|
||||||
ui.launch(server_name="0.0.0.0", server_port=60673, prevent_thread_lock=True, pwa=True)
|
await irc_bot.connect(args.irc_server, args.irc_port, tls=args.irc_use_tls)
|
||||||
|
|
||||||
|
if args.gradio_enable:
|
||||||
|
ui = await create_ui()
|
||||||
|
ui.launch(server_name=args.gradio_host, server_port=args.gradio_port, prevent_thread_lock=True, pwa=True)
|
||||||
|
logging.info(args)
|
||||||
|
|
||||||
await irc_bot.handle_forever()
|
await irc_bot.handle_forever()
|
||||||
|
|
||||||
def setup_logging(level):
|
|
||||||
numeric_level = getattr(logging, level.upper(), None)
|
|
||||||
if not isinstance(numeric_level, int):
|
|
||||||
raise ValueError(f"Invalid log level: {level}")
|
|
||||||
|
|
||||||
logging.basicConfig(level=numeric_level, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
||||||
logging.info(f"Logging is set to {level} level.")
|
|
||||||
|
|
||||||
# Run the main function using anyio
|
# Run the main function using anyio
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
327
jupyter/tools.py
Normal file
327
jupyter/tools.py
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
# %%
|
||||||
|
# Imports [standard]
|
||||||
|
# Standard library modules (no try-except needed)
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import anyio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def try_import(module_name, pip_name=None):
|
||||||
|
try:
|
||||||
|
__import__(module_name)
|
||||||
|
except ImportError:
|
||||||
|
print(f"Module '{module_name}' not found. Install it using:")
|
||||||
|
print(f" pip install {pip_name or module_name}")
|
||||||
|
|
||||||
|
# Third-party modules with import checks
|
||||||
|
try_import('gradio')
|
||||||
|
try_import('ollama')
|
||||||
|
try_import('openai')
|
||||||
|
try_import('pydle')
|
||||||
|
try_import('pytz')
|
||||||
|
try_import('requests')
|
||||||
|
try_import('yfinance', 'yfinance')
|
||||||
|
try_import('dotenv', 'python-dotenv')
|
||||||
|
try_import('geopy', 'geopy')
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from geopy.geocoders import Nominatim
|
||||||
|
import gradio as gr
|
||||||
|
import ollama
|
||||||
|
import openai
|
||||||
|
import pydle
|
||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
import yfinance as yf
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def get_weather_by_location(city, state, country="USA"):
|
||||||
|
"""
|
||||||
|
Get weather information from weather.gov based on city, state, and country.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
city (str): City name
|
||||||
|
state (str): State name or abbreviation
|
||||||
|
country (str): Country name (defaults to "USA" as weather.gov is for US locations)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Weather forecast information
|
||||||
|
"""
|
||||||
|
# Step 1: Get coordinates for the location using geocoding
|
||||||
|
location = f"{city}, {state}, {country}"
|
||||||
|
coordinates = get_coordinates(location)
|
||||||
|
|
||||||
|
if not coordinates:
|
||||||
|
return {"error": f"Could not find coordinates for {location}"}
|
||||||
|
|
||||||
|
# Step 2: Get the forecast grid endpoint for the coordinates
|
||||||
|
grid_endpoint = get_grid_endpoint(coordinates)
|
||||||
|
|
||||||
|
if not grid_endpoint:
|
||||||
|
return {"error": f"Could not find weather grid for coordinates {coordinates}"}
|
||||||
|
|
||||||
|
# Step 3: Get the forecast data from the grid endpoint
|
||||||
|
forecast = get_forecast(grid_endpoint)
|
||||||
|
|
||||||
|
return forecast
|
||||||
|
|
||||||
|
def get_coordinates(location):
|
||||||
|
"""Convert a location string to latitude and longitude using Nominatim geocoder."""
|
||||||
|
try:
|
||||||
|
# Create a geocoder with a meaningful user agent
|
||||||
|
geolocator = Nominatim(user_agent="weather_app_example")
|
||||||
|
|
||||||
|
# Get the location
|
||||||
|
location_data = geolocator.geocode(location)
|
||||||
|
|
||||||
|
if location_data:
|
||||||
|
return {
|
||||||
|
"latitude": location_data.latitude,
|
||||||
|
"longitude": location_data.longitude
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(f"Location not found: {location}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting coordinates: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_grid_endpoint(coordinates):
|
||||||
|
"""Get the grid endpoint from weather.gov based on coordinates."""
|
||||||
|
try:
|
||||||
|
lat = coordinates["latitude"]
|
||||||
|
lon = coordinates["longitude"]
|
||||||
|
|
||||||
|
# Define headers for the API request
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "WeatherAppExample/1.0 (your_email@example.com)",
|
||||||
|
"Accept": "application/geo+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make the request to get the grid endpoint
|
||||||
|
url = f"https://api.weather.gov/points/{lat},{lon}"
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data["properties"]["forecast"]
|
||||||
|
else:
|
||||||
|
print(f"Error getting grid: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in get_grid_endpoint: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Weather related function
|
||||||
|
|
||||||
|
def get_forecast(grid_endpoint):
|
||||||
|
"""Get the forecast data from the grid endpoint."""
|
||||||
|
try:
|
||||||
|
# Define headers for the API request
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "WeatherAppExample/1.0 (your_email@example.com)",
|
||||||
|
"Accept": "application/geo+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make the request to get the forecast
|
||||||
|
response = requests.get(grid_endpoint, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Extract the relevant forecast information
|
||||||
|
periods = data["properties"]["periods"]
|
||||||
|
|
||||||
|
# Process the forecast data into a simpler format
|
||||||
|
forecast = {
|
||||||
|
"location": data["properties"].get("relativeLocation", {}).get("properties", {}),
|
||||||
|
"updated": data["properties"].get("updated", ""),
|
||||||
|
"periods": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for period in periods:
|
||||||
|
forecast["periods"].append({
|
||||||
|
"name": period.get("name", ""),
|
||||||
|
"temperature": period.get("temperature", ""),
|
||||||
|
"temperatureUnit": period.get("temperatureUnit", ""),
|
||||||
|
"windSpeed": period.get("windSpeed", ""),
|
||||||
|
"windDirection": period.get("windDirection", ""),
|
||||||
|
"shortForecast": period.get("shortForecast", ""),
|
||||||
|
"detailedForecast": period.get("detailedForecast", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
return forecast
|
||||||
|
else:
|
||||||
|
print(f"Error getting forecast: {response.status_code} - {response.text}")
|
||||||
|
return {"error": f"API Error: {response.status_code}"}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in get_forecast: {e}")
|
||||||
|
return {"error": f"Exception: {str(e)}"}
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
def do_weather():
|
||||||
|
city = input("Enter city: ")
|
||||||
|
state = input("Enter state: ")
|
||||||
|
country = input("Enter country (default USA): ") or "USA"
|
||||||
|
|
||||||
|
print(f"Getting weather for {city}, {state}, {country}...")
|
||||||
|
weather_data = get_weather_by_location(city, state, country)
|
||||||
|
|
||||||
|
if "error" in weather_data:
|
||||||
|
print(f"Error: {weather_data['error']}")
|
||||||
|
else:
|
||||||
|
print("\nWeather Forecast:")
|
||||||
|
print(f"Location: {weather_data.get('location', {}).get('city', city)}, {weather_data.get('location', {}).get('state', state)}")
|
||||||
|
print(f"Last Updated: {weather_data.get('updated', 'N/A')}")
|
||||||
|
print("\nForecast Periods:")
|
||||||
|
|
||||||
|
for period in weather_data.get("periods", []):
|
||||||
|
print(f"\n{period['name']}:")
|
||||||
|
print(f" Temperature: {period['temperature']}{period['temperatureUnit']}")
|
||||||
|
print(f" Wind: {period['windSpeed']} {period['windDirection']}")
|
||||||
|
print(f" Forecast: {period['shortForecast']}")
|
||||||
|
print(f" Details: {period['detailedForecast']}")
|
||||||
|
|
||||||
|
# %%
|
||||||
|
|
||||||
|
# Stock related function
|
||||||
|
def get_ticker_price(ticker_symbols):
|
||||||
|
"""
|
||||||
|
Look up the current price of a stock using its ticker symbol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ticker_symbol (str): The stock ticker symbol (e.g., 'AAPL' for Apple)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Current stock information including price
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
print(f"get_ticker_price('{ticker_symbols}')")
|
||||||
|
for ticker_symbol in ticker_symbols.split(','):
|
||||||
|
ticker_symbol = ticker_symbol.strip()
|
||||||
|
if ticker_symbol == "":
|
||||||
|
continue
|
||||||
|
# Create a Ticker object
|
||||||
|
try:
|
||||||
|
ticker = yf.Ticker(ticker_symbol)
|
||||||
|
|
||||||
|
# Get the latest market data
|
||||||
|
ticker_data = ticker.history(period="1d")
|
||||||
|
|
||||||
|
if ticker_data.empty:
|
||||||
|
results.append({"error": f"No data found for ticker {ticker_symbol}"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the latest closing price
|
||||||
|
latest_price = ticker_data['Close'].iloc[-1]
|
||||||
|
|
||||||
|
# Get some additional info
|
||||||
|
info = ticker.info
|
||||||
|
results.append({ 'symbol': ticker_symbol, 'price': latest_price })
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results.append({"error": f"Error fetching data for {ticker_symbol}: {str(e)}"})
|
||||||
|
|
||||||
|
return results[0] if len(results) == 1 else results
|
||||||
|
#{
|
||||||
|
# "symbol": ticker_symbol,
|
||||||
|
# "price": latest_price,
|
||||||
|
# "currency": info.get("currency", "Unknown"),
|
||||||
|
# "company_name": info.get("shortName", "Unknown"),
|
||||||
|
# "previous_close": info.get("previousClose", "Unknown"),
|
||||||
|
# "market_cap": info.get("marketCap", "Unknown"),
|
||||||
|
#}
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
def get_current_datetime(timezone="America/Los_Angeles"):
|
||||||
|
"""
|
||||||
|
Returns the current date and time in the specified timezone in ISO 8601 format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timezone (str): Timezone name (e.g., "UTC", "America/New_York", "Europe/London")
|
||||||
|
Default is "America/Los_Angeles"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Current date and time with timezone in the format YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if timezone == 'system' or timezone == '' or not timezone:
|
||||||
|
timezone = 'America/Los_Angeles'
|
||||||
|
# Get current UTC time (timezone-aware)
|
||||||
|
local_tz = pytz.timezone("America/Los_Angeles")
|
||||||
|
local_now = datetime.now(tz=local_tz)
|
||||||
|
|
||||||
|
# Convert to target timezone
|
||||||
|
target_tz = pytz.timezone(timezone)
|
||||||
|
target_time = local_now.astimezone(target_tz)
|
||||||
|
|
||||||
|
return target_time.isoformat()
|
||||||
|
except Exception as e:
|
||||||
|
return {'error': f"Invalid timezone {timezone}: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
|
# %%
|
||||||
|
tools = [{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_ticker_price",
|
||||||
|
"description": "Get the current stock price of one or more ticker symbols. Returns an array of objects with 'symbol' and 'price' fields. Call this whenever you need to know the latest value of stock ticker symbols, for example when a user asks 'How much is Intel trading at?' or 'What are the prices of AAPL and MSFT?'",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ticker": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The company stock ticker symbol. For multiple tickers, provide a comma-separated list (e.g., 'AAPL,MSFT,GOOGL').",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["ticker"],
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_current_datetime",
|
||||||
|
"description": "Get the current date and time in a specified timezone",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"timezone": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Timezone name (e.g., 'UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'). Default is 'America/Los_Angeles'."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_weather_by_location",
|
||||||
|
"description": "Get the current and future weather for a given CITY and STATE location in the United States. For example, if the user asks 'What is the weather in Portland?' or 'What is the forecast for tomorrow?'",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"city": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "City to find the weather (e.g., 'Portland', 'Seattle')."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "State to find the weather (e.g., 'OR', 'WA')."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [ "city", "state" ],
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
__all__ = [ 'tools', 'get_current_datetime', 'get_weather_by_location', 'get_ticker_price' ]
|
Loading…
x
Reference in New Issue
Block a user