INTRODUCTION TO THE ANCIENT WISDOM REIMAGINED
The Oracle of Delphi stood for centuries as humanity's bridge between the mortal realm and divine wisdom. Pilgrims traveled vast distances to pose their questions to the Pythia, who would enter a trance-like state and deliver cryptic prophecies. These pronouncements were never straightforward answers but rather metaphorical riddles that required interpretation and contemplation. The Oracle spoke in symbols, allegories, and veiled references that forced questioners to think deeply about their circumstances.
In our modern age, we can recreate this mystical experience by combining several powerful technologies. Large Language Models provide the linguistic intelligence to generate responses. Astrological calculations add personalized cosmic context based on the questioner's birth data and current planetary positions. The geographic location of the user determines which celestial bodies are visible and their positions in the sky at the moment of inquiry. All these elements combine to create responses that mirror the enigmatic style of the ancient Oracle while remaining relevant to the user's actual question.
This article will guide you through building a complete system that accepts questions from users, optionally collects their birth information, calculates astrological data based on their location, and generates responses in the style of the Delphic Oracle. The system will work with both local and remote Large Language Models, supporting various GPU architectures including Intel, AMD ROCm, Apple Metal Performance Shaders, and Nvidia CUDA. Every component will be explained in detail with working code examples that you can adapt and extend.
ARCHITECTURAL OVERVIEW OF THE ORACLE SYSTEM
The Oracle system consists of several interconnected components that work together to produce mystical yet meaningful responses. The first component handles user input, collecting the question and optional birth data. The second component determines the user's geographic location, which is essential for accurate astrological calculations. The third component performs astronomical and astrological calculations to determine planetary positions, zodiac signs, and other celestial data. The fourth component interfaces with Large Language Models, whether running locally or accessed remotely.
The fifth component constructs prompts that combine the user's question with astrological context and instructs the model to respond in the Oracle's metaphorical style. The final component presents the response to the user in an appropriately mystical format.
Each component must be designed with flexibility in mind. The location detection should support both automatic geolocation and manual input. The astrological calculations must be precise and based on actual astronomical data rather than simplified approximations.
The LLM interface needs to support multiple backends seamlessly, allowing users to switch between local models running on various GPU architectures and remote API services. The prompt construction should be sophisticated enough to guide the model toward genuinely helpful metaphorical responses rather than vague nonsense.
UNDERSTANDING ASTROLOGICAL CALCULATIONS FOR THE ORACLE
Astrological calculations form the heart of our Oracle's personalized responses. When a user provides their birth date, time, and location, we can calculate their natal chart, which shows the positions of celestial bodies at the moment of their birth. When they ask a question, we also calculate the current positions of these bodies and compare them to the natal positions. This creates a rich tapestry of astrological aspects that the Oracle can weave into its response.
The fundamental calculation involves determining the ecliptic longitude of celestial bodies. The ecliptic is the apparent path of the Sun across the sky over the course of a year. Planetary positions are measured in degrees along this path, with the zodiac signs dividing the ecliptic into twelve segments of thirty degrees each. Aries occupies zero to thirty degrees, Taurus thirty to sixty degrees, and so forth. When we know a planet's ecliptic longitude, we can determine which zodiac sign it occupies and its exact position within that sign.
To perform these calculations accurately, we need astronomical ephemeris data. An ephemeris is a table showing the positions of celestial bodies at regular intervals. The Swiss Ephemeris is a highly accurate and freely available ephemeris that covers thousands of years. It provides functions to calculate planetary positions for any given date, time, and location. The library accounts for precession, nutation, and other astronomical phenomena that affect the apparent positions of celestial bodies.
Here is a simple example showing how to calculate the Sun's position for a given date and time:
import swisseph as swe
from datetime import datetime
# Set the ephemeris path
swe.set_ephe_path('/usr/share/ephe')
# Define a birth date and time
birth_datetime = datetime(1985, 6, 15, 14, 30, 0)
# Convert to Julian Day Number
julian_day = swe.julday(birth_datetime.year, birth_datetime.month,
birth_datetime.day,
birth_datetime.hour + birth_datetime.minute / 60.0)
# Calculate Sun position (Sun is planet number 0)
sun_position, return_flag = swe.calc_ut(julian_day, swe.SUN)
# Extract ecliptic longitude
sun_longitude = sun_position[0]
# Determine zodiac sign
zodiac_signs = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn',
'Aquarius', 'Pisces']
sign_index = int(sun_longitude / 30)
degree_in_sign = sun_longitude % 30
print(f"Sun is at {degree_in_sign:.2f} degrees {zodiac_signs[sign_index]}")
This code demonstrates the basic workflow for astrological calculations. We first convert a calendar date and time into a Julian Day Number, which is the standard astronomical way of representing time as a continuous count of days since a reference epoch. The Swiss Ephemeris library uses Julian Day Numbers for all its calculations. We then call the calculation function with the Julian Day and the identifier for the celestial body we want to calculate. The Sun is represented by the constant swe.SUN. The function returns the ecliptic longitude in degrees, which we can then convert into a zodiac sign and position within that sign.
For a complete natal chart, we need to calculate positions for all the major planets, the Moon, and the lunar nodes. We also need to calculate the Ascendant and Midheaven, which are points in the chart that depend on the exact time and location of birth. The Ascendant is the zodiac sign rising on the eastern horizon at the moment of birth, while the Midheaven is the point of the ecliptic that is highest in the sky. These calculations require the geographic coordinates of the birth location.
Here is how we calculate the Ascendant and Midheaven for a specific location:
import swisseph as swe
def calculate_houses(julian_day, latitude, longitude):
"""
Calculate house cusps and angles for a given time and location.
Args:
julian_day: Julian Day Number for the calculation time
latitude: Geographic latitude in degrees (positive north)
longitude: Geographic longitude in degrees (positive east)
Returns:
Dictionary containing Ascendant, Midheaven, and house cusps
"""
# Calculate houses using Placidus system
house_cusps, ascmc = swe.houses(julian_day, latitude, longitude, b'P')
# Extract key points
ascendant = ascmc[0] # Ascendant
midheaven = ascmc[1] # Midheaven (MC)
return {
'ascendant': ascendant,
'midheaven': midheaven,
'house_cusps': house_cusps
}
# Example usage for Athens, Greece (location of ancient Delphi)
athens_lat = 37.9838
athens_lon = 23.7275
julian_day = swe.julday(2025, 10, 21, 18.0)
houses = calculate_houses(julian_day, athens_lat, athens_lon)
print(f"Ascendant: {houses['ascendant']:.2f} degrees")
print(f"Midheaven: {houses['midheaven']:.2f} degrees")
The houses function calculates the twelve astrological houses, which divide the sky into sectors based on the rotation of the Earth. Different house systems exist, but the Placidus system is most commonly used in Western astrology. The function returns two arrays: house_cusps contains the starting degrees of each house, and ascmc contains the angles including Ascendant and Midheaven.
IMPLEMENTING LOCATION DETECTION AND GEOCODING
Accurate astrological calculations require knowing the user's geographic location. The Oracle system needs to determine both the current location for calculating present planetary positions and the birth location if the user provides birth data. There are several approaches to obtaining location information, each with different trade-offs between accuracy, privacy, and user convenience.
For web-based implementations, the browser's Geolocation API can provide the user's current coordinates if they grant permission. This works well for determining the current location but obviously cannot help with historical birth locations. For command-line or desktop applications, we can use IP geolocation services that estimate location based on the user's IP address. These are less accurate but require no user interaction. The most reliable approach is to ask users to enter their location explicitly, either as coordinates or as a city name that we then geocode into coordinates.
Geocoding converts place names into geographic coordinates. Several services provide geocoding APIs, including OpenStreetMap's Nominatim service, which is free and open-source. When a user enters a city name, we query the geocoding service to obtain latitude and longitude coordinates. We should also handle timezone information, as astrological calculations require converting local time to Universal Time.
Here is a function that performs geocoding using the Nominatim service:
import requests
from typing import Optional, Dict
def geocode_location(location_name: str) -> Optional[Dict[str, float]]:
"""
Convert a location name to geographic coordinates using Nominatim.
Args:
location_name: Name of the location (city, address, etc.)
Returns:
Dictionary with 'latitude' and 'longitude' keys, or None if not found
"""
# Nominatim API endpoint
url = "https://nominatim.openstreetmap.org/search"
# Parameters for the request
params = {
'q': location_name,
'format': 'json',
'limit': 1
}
# Set a user agent as required by Nominatim usage policy
headers = {
'User-Agent': 'OracleOfDelphi/1.0'
}
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()
results = response.json()
if results and len(results) > 0:
return {
'latitude': float(results[0]['lat']),
'longitude': float(results[0]['lon']),
'display_name': results[0]['display_name']
}
else:
return None
except requests.RequestException as e:
print(f"Geocoding error: {e}")
return None
# Example usage
location = geocode_location("Delphi, Greece")
if location:
print(f"Found: {location['display_name']}")
print(f"Coordinates: {location['latitude']}, {location['longitude']}")
This function sends a request to the Nominatim API with the location name and receives back a JSON response containing matching locations. We take the first result and extract the latitude and longitude. The display_name field shows the full formatted address, which we can show to the user for confirmation. The function includes proper error handling for network issues and returns None if the location cannot be found.
For timezone handling, we need to convert local times to Universal Time for astronomical calculations. The pytz library provides comprehensive timezone support. When we have coordinates, we can use the timezonefinder library to determine which timezone those coordinates fall within. This is essential when users provide birth times in their local timezone.
from datetime import datetime
import pytz
from timezonefinder import TimezoneFinder
def convert_to_utc(local_datetime: datetime, latitude: float,
longitude: float) -> datetime:
"""
Convert a local datetime to UTC based on geographic coordinates.
Args:
local_datetime: Datetime in local time (naive datetime object)
latitude: Geographic latitude
longitude: Geographic longitude
Returns:
Datetime in UTC
"""
# Find the timezone for the coordinates
tf = TimezoneFinder()
timezone_str = tf.timezone_at(lat=latitude, lng=longitude)
if timezone_str is None:
raise ValueError("Could not determine timezone for coordinates")
# Get the timezone object
local_tz = pytz.timezone(timezone_str)
# Localize the naive datetime to the local timezone
localized_dt = local_tz.localize(local_datetime)
# Convert to UTC
utc_dt = localized_dt.astimezone(pytz.UTC)
return utc_dt
# Example: Convert birth time to UTC
birth_local = datetime(1985, 6, 15, 14, 30, 0)
birth_lat = 40.7128 # New York
birth_lon = -74.0060
birth_utc = convert_to_utc(birth_local, birth_lat, birth_lon)
print(f"Local time: {birth_local}")
print(f"UTC time: {birth_utc}")
This function takes a naive datetime object representing a local time and converts it to UTC by first determining the timezone at the given coordinates, then localizing the datetime to that timezone, and finally converting to UTC. This ensures that our astrological calculations use the correct moment in time regardless of where the user was born or where they currently are.
INTERFACING WITH LARGE LANGUAGE MODELS ACROSS PLATFORMS
The Oracle's responses are generated by Large Language Models, which can run either locally on the user's hardware or remotely through API services. Supporting both options provides flexibility for different use cases and resource constraints. Local models offer privacy and no per-query costs but require significant computational resources. Remote models provide access to larger and more capable models without hardware requirements but involve API costs and data privacy considerations.
For local model inference, we need to support multiple GPU architectures. Nvidia CUDA is the most common platform for deep learning, but AMD ROCm provides similar capabilities for AMD GPUs. Apple's Metal Performance Shaders enable GPU acceleration on Mac systems with Apple Silicon. Intel GPUs can be used through OpenVINO or oneAPI. The llama-cpp-python library provides a unified interface that can leverage different backends depending on the available hardware.
Here is how we set up a local model interface that detects and uses available GPU acceleration:
import platform
import subprocess
from typing import Optional
from llama_cpp import Llama
class LocalLLMInterface:
"""
Interface for running LLMs locally with automatic GPU detection.
"""
def __init__(self, model_path: str, context_length: int = 4096):
"""
Initialize the local LLM interface.
Args:
model_path: Path to the GGUF model file
context_length: Maximum context length in tokens
"""
self.model_path = model_path
self.context_length = context_length
self.model = None
self.gpu_layers = self._detect_gpu_capability()
def _detect_gpu_capability(self) -> int:
"""
Detect available GPU and determine how many layers to offload.
Returns:
Number of layers to offload to GPU (0 for CPU-only)
"""
system = platform.system()
# Check for Nvidia CUDA
try:
result = subprocess.run(['nvidia-smi'],
capture_output=True,
text=True,
timeout=5)
if result.returncode == 0:
print("Detected Nvidia GPU with CUDA support")
return 35 # Offload most layers to GPU
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
# Check for AMD ROCm
try:
result = subprocess.run(['rocm-smi'],
capture_output=True,
text=True,
timeout=5)
if result.returncode == 0:
print("Detected AMD GPU with ROCm support")
return 35
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
# Check for Apple Metal (M1/M2/M3)
if system == 'Darwin' and platform.processor() == 'arm':
print("Detected Apple Silicon with Metal support")
return 1 # Use Metal backend
# Check for Intel GPU
try:
if system == 'Linux':
with open('/proc/cpuinfo', 'r') as f:
if 'Intel' in f.read():
print("Detected Intel CPU, checking for GPU")
# Intel GPU detection would go here
# For now, fall back to CPU
except FileNotFoundError:
pass
print("No GPU detected, using CPU")
return 0
def load_model(self):
"""
Load the model with appropriate GPU settings.
"""
if self.gpu_layers > 0:
self.model = Llama(
model_path=self.model_path,
n_ctx=self.context_length,
n_gpu_layers=self.gpu_layers,
verbose=False
)
else:
self.model = Llama(
model_path=self.model_path,
n_ctx=self.context_length,
verbose=False
)
print(f"Model loaded with {self.gpu_layers} layers on GPU")
def generate_response(self, prompt: str, max_tokens: int = 500,
temperature: float = 0.8) -> str:
"""
Generate a response from the model.
Args:
prompt: The input prompt
max_tokens: Maximum number of tokens to generate
temperature: Sampling temperature (higher = more random)
Returns:
Generated text response
"""
if self.model is None:
self.load_model()
response = self.model(
prompt,
max_tokens=max_tokens,
temperature=temperature,
stop=["</s>", "Human:", "User:"],
echo=False
)
return response['choices'][0]['text'].strip()
This class encapsulates the complexity of GPU detection and model loading. The _detect_gpu_capability method checks for various GPU types by attempting to run their respective management tools. For Nvidia GPUs, we check for nvidia-smi. For AMD GPUs, we check for rocm-smi. For Apple Silicon, we check the platform and processor type. Based on what we detect, we set the number of layers to offload to the GPU. The load_model method then initializes the Llama object with the appropriate settings.
For remote model access, we need to support various API providers. OpenAI provides the most well-known API, but alternatives like Anthropic, Cohere, and open-source model hosting services also exist. We should create a unified interface that abstracts the differences between these providers.
import os
from typing import Dict, Any
from openai import OpenAI
from anthropic import Anthropic
class RemoteLLMInterface:
"""
Interface for accessing remote LLM APIs.
"""
def __init__(self, provider: str, api_key: Optional[str] = None,
model: Optional[str] = None):
"""
Initialize the remote LLM interface.
Args:
provider: API provider ('openai', 'anthropic', etc.)
api_key: API key (if None, reads from environment)
model: Model identifier (uses default if None)
"""
self.provider = provider.lower()
self.api_key = api_key or self._get_api_key_from_env()
self.model = model or self._get_default_model()
self.client = self._initialize_client()
def _get_api_key_from_env(self) -> str:
"""
Get API key from environment variables.
"""
env_var_map = {
'openai': 'OPENAI_API_KEY',
'anthropic': 'ANTHROPIC_API_KEY'
}
env_var = env_var_map.get(self.provider)
if env_var is None:
raise ValueError(f"Unknown provider: {self.provider}")
api_key = os.getenv(env_var)
if api_key is None:
raise ValueError(f"API key not found in environment: {env_var}")
return api_key
def _get_default_model(self) -> str:
"""
Get default model for the provider.
"""
defaults = {
'openai': 'gpt-4-turbo-preview',
'anthropic': 'claude-3-opus-20240229'
}
return defaults.get(self.provider, 'gpt-3.5-turbo')
def _initialize_client(self) -> Any:
"""
Initialize the API client for the provider.
"""
if self.provider == 'openai':
return OpenAI(api_key=self.api_key)
elif self.provider == 'anthropic':
return Anthropic(api_key=self.api_key)
else:
raise ValueError(f"Unsupported provider: {self.provider}")
def generate_response(self, prompt: str, max_tokens: int = 500,
temperature: float = 0.8) -> str:
"""
Generate a response using the remote API.
Args:
prompt: The input prompt
max_tokens: Maximum number of tokens to generate
temperature: Sampling temperature
Returns:
Generated text response
"""
if self.provider == 'openai':
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "You are the Oracle of Delphi."},
{"role": "user", "content": prompt}
],
max_tokens=max_tokens,
temperature=temperature
)
return response.choices[0].message.content
elif self.provider == 'anthropic':
response = self.client.messages.create(
model=self.model,
max_tokens=max_tokens,
temperature=temperature,
messages=[
{"role": "user", "content": prompt}
]
)
return response.content[0].text
else:
raise ValueError(f"Unsupported provider: {self.provider}")
This class provides a consistent interface for different remote API providers. The initialization method detects which provider is being used and creates the appropriate client object. The generate_response method translates our standard parameters into the format expected by each provider's API. This abstraction allows the rest of our Oracle system to work with any supported provider without modification.
CONSTRUCTING EFFECTIVE PROMPTS FOR ORACULAR RESPONSES
The quality of the Oracle's responses depends heavily on how we construct the prompts sent to the language model. A well-crafted prompt must accomplish several objectives simultaneously. It must convey the user's question clearly. It must provide the astrological context in a way the model can understand and incorporate. It must establish the voice and style of the ancient Oracle. It must guide the model toward metaphorical rather than literal responses. It must ensure the response actually addresses the question rather than being meaningless mysticism.
The prompt structure should begin with a system message that establishes the Oracle's identity and communication style. This message explains that the Oracle speaks in metaphors, symbols, and allegories drawn from nature, mythology, and the cosmos. It emphasizes that responses should be enigmatic yet meaningful, requiring contemplation but ultimately providing genuine insight. The system message should reference the Oracle's historical context at Delphi and the tradition of cryptic prophecies.
Following the system message, we provide the astrological context. This section describes the current positions of celestial bodies and, if available, the user's natal chart. We should present this information in natural language rather than raw numbers, as language models work better with descriptive text. For example, instead of saying "Sun at 28.5 degrees Gemini," we might say "The Sun travels through the final degrees of Gemini, approaching the threshold of Cancer, where the celestial messenger yields to the nurturing waters."
Here is a function that converts astrological data into descriptive text suitable for inclusion in prompts:
def describe_planetary_position(planet_name: str, longitude: float,
is_natal: bool = False) -> str:
"""
Convert a planetary position into descriptive text.
Args:
planet_name: Name of the planet or celestial body
longitude: Ecliptic longitude in degrees
is_natal: Whether this is a natal position or current transit
Returns:
Descriptive text about the planetary position
"""
zodiac_signs = [
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
]
sign_index = int(longitude / 30)
degree_in_sign = longitude % 30
sign = zodiac_signs[sign_index]
# Determine if planet is in early, middle, or late degrees
if degree_in_sign < 10:
position_desc = "early degrees"
elif degree_in_sign < 20:
position_desc = "middle degrees"
else:
position_desc = "late degrees"
# Create descriptive text
time_context = "was born with" if is_natal else "now finds"
descriptions = {
'Sun': f"The Sun {time_context} itself in the {position_desc} of {sign}",
'Moon': f"The Moon {time_context} herself in the {position_desc} of {sign}",
'Mercury': f"Swift Mercury {time_context} his place in the {position_desc} of {sign}",
'Venus': f"Fair Venus {time_context} her station in the {position_desc} of {sign}",
'Mars': f"Mighty Mars {time_context} his position in the {position_desc} of {sign}",
'Jupiter': f"Great Jupiter {time_context} his throne in the {position_desc} of {sign}",
'Saturn': f"Stern Saturn {time_context} his seat in the {position_desc} of {sign}",
'Uranus': f"Revolutionary Uranus {time_context} its place in the {position_desc} of {sign}",
'Neptune': f"Mystical Neptune {time_context} its realm in the {position_desc} of {sign}",
'Pluto': f"Transformative Pluto {time_context} its domain in the {position_desc} of {sign}"
}
return descriptions.get(planet_name,
f"{planet_name} {time_context} position in {sign}")
def create_astrological_context(natal_data: Dict, current_data: Dict) -> str:
"""
Create a narrative description of astrological context.
Args:
natal_data: Dictionary of natal planetary positions
current_data: Dictionary of current planetary positions
Returns:
Formatted text describing the astrological situation
"""
context_parts = []
# Describe natal chart if available
if natal_data:
context_parts.append("At the moment of your birth:")
for planet, longitude in natal_data.items():
context_parts.append(describe_planetary_position(planet,
longitude,
is_natal=True))
# Describe current transits
context_parts.append("\nIn this present moment:")
for planet, longitude in current_data.items():
context_parts.append(describe_planetary_position(planet,
longitude,
is_natal=False))
return ". ".join(context_parts) + "."
This function transforms numerical astrological data into flowing narrative text. It describes each planetary position using mythological language appropriate to each planet's traditional associations. The Sun is described with solar imagery, the Moon with lunar imagery, Mercury as swift and communicative, Venus as beautiful, Mars as mighty, and so forth. This narrative style helps the language model understand the symbolic significance of the positions and incorporate them naturally into its response.
The final component of the prompt is the user's actual question, introduced in a way that frames it as a query to the Oracle. We should preserve the user's exact wording but present it formally, as one might address an ancient oracle. The complete prompt structure looks like this:
def construct_oracle_prompt(question: str, astrological_context: str) -> str:
"""
Construct a complete prompt for the Oracle.
Args:
question: The user's question
astrological_context: Narrative astrological context
Returns:
Complete prompt for the language model
"""
system_message = """You are the Oracle of Delphi, the most sacred oracle
of ancient Greece. For over a thousand years, pilgrims traveled from across
the known world to seek your wisdom. You speak through the Pythia, the high
priestess who enters a divine trance to channel prophecy.
Your responses are never direct or literal. You speak in metaphors, symbols,
allegories, and riddles drawn from nature, mythology, the elements, and the
cosmos. Your words require contemplation and interpretation. Yet beneath the
enigmatic surface, your prophecies contain genuine wisdom and insight that
addresses the seeker's true question.
You weave the celestial movements and astrological significances into your
responses, for the positions of the heavenly bodies reflect the patterns of
fate and fortune. The stars and planets are not mere decorations in your
speech but integral to the meaning you convey.
Speak as the ancient Oracle spoke: mysterious yet meaningful, cryptic yet
ultimately helpful, poetic yet profound."""
prompt = f"""{system_message}
The celestial tapestry reveals the following:
{astrological_context}
A seeker approaches the sacred temple and poses this question:
"{question}"
Oracle, what wisdom do you offer this seeker?"""
return prompt
This prompt structure provides the language model with everything it needs to generate an appropriate response. The system message establishes the Oracle's character and communication style. The astrological context provides specific details to incorporate. The question gives the actual topic to address. Together, these elements guide the model toward responses that are both mystical in style and helpful in substance.
CALCULATING ASTROLOGICAL ASPECTS AND THEIR MEANINGS
Beyond simply knowing where planets are located, astrology derives meaning from the angular relationships between planets, called aspects. When two planets are separated by specific angles, they are said to form an aspect, which creates a particular type of energetic interaction between their symbolic meanings. The major aspects include the conjunction at zero degrees, the opposition at one hundred eighty degrees, the trine at one hundred twenty degrees, the square at ninety degrees, and the sextile at sixty degrees. Each aspect has traditional meanings that the Oracle can incorporate into its responses.
Calculating aspects requires comparing the positions of different planets and determining if their angular separation matches one of these significant angles within an allowed margin of error called an orb. For major aspects, astrologers typically use an orb of about eight to ten degrees, meaning a trine could be anywhere from one hundred ten to one hundred thirty degrees and still be considered valid. More precise aspects are generally considered stronger and more significant.
Here is a function that calculates aspects between planets:
from typing import List, Tuple
def calculate_aspect(position1: float, position2: float) -> Optional[Tuple[str, float]]:
"""
Determine if two planetary positions form a major aspect.
Args:
position1: Ecliptic longitude of first planet in degrees
position2: Ecliptic longitude of second planet in degrees
Returns:
Tuple of (aspect_name, exact_angle) or None if no aspect
"""
# Calculate the angular separation
separation = abs(position1 - position2)
# Normalize to 0-180 degrees
if separation > 180:
separation = 360 - separation
# Define aspects with their angles and orbs
aspects = {
'conjunction': (0, 10),
'sextile': (60, 6),
'square': (90, 8),
'trine': (120, 8),
'opposition': (180, 10)
}
# Check each aspect
for aspect_name, (angle, orb) in aspects.items():
if abs(separation - angle) <= orb:
return (aspect_name, separation)
return None
def find_all_aspects(planetary_positions: Dict[str, float]) -> List[Dict]:
"""
Find all major aspects in a set of planetary positions.
Args:
planetary_positions: Dictionary mapping planet names to longitudes
Returns:
List of dictionaries describing each aspect found
"""
aspects = []
planets = list(planetary_positions.keys())
# Compare each pair of planets
for i in range(len(planets)):
for j in range(i + 1, len(planets)):
planet1 = planets[i]
planet2 = planets[j]
aspect = calculate_aspect(planetary_positions[planet1],
planetary_positions[planet2])
if aspect is not None:
aspect_name, angle = aspect
aspects.append({
'planet1': planet1,
'planet2': planet2,
'aspect': aspect_name,
'angle': angle
})
return aspects
def describe_aspect(aspect_info: Dict) -> str:
"""
Create a narrative description of an astrological aspect.
Args:
aspect_info: Dictionary containing aspect details
Returns:
Descriptive text about the aspect
"""
planet1 = aspect_info['planet1']
planet2 = aspect_info['planet2']
aspect = aspect_info['aspect']
aspect_descriptions = {
'conjunction': f"{planet1} and {planet2} unite their energies in conjunction, "
"blending their natures into a singular force",
'opposition': f"{planet1} and {planet2} stand in opposition across the heavens, "
"creating tension between their contrary natures",
'trine': f"{planet1} and {planet2} form a harmonious trine, "
"their energies flowing together with ease and grace",
'square': f"{planet1} and {planet2} form a challenging square, "
"their energies creating friction that demands resolution",
'sextile': f"{planet1} and {planet2} form a supportive sextile, "
"offering opportunities for their energies to cooperate"
}
return aspect_descriptions.get(aspect,
f"{planet1} and {planet2} form a {aspect}")
These functions work together to identify and describe astrological aspects. The calculate_aspect function determines whether two positions form a major aspect by calculating their angular separation and checking if it falls within the allowed orb of any standard aspect. The find_all_aspects function compares every pair of planets in a chart to find all aspects present. The describe_aspect function converts the technical aspect information into narrative text that can be included in the Oracle's context.
When we have both natal and current positions, we can also calculate transits, which are aspects between current planetary positions and natal positions. Transits are particularly significant in astrological interpretation because they show how current cosmic conditions interact with the individual's birth chart. A transit of Saturn square natal Sun, for example, traditionally indicates a period of challenge and testing related to identity and life direction. The Oracle can weave these transit meanings into its metaphorical responses.
def calculate_transits(natal_positions: Dict[str, float],
current_positions: Dict[str, float]) -> List[Dict]:
"""
Calculate aspects between transiting and natal planets.
Args:
natal_positions: Dictionary of natal planetary positions
current_positions: Dictionary of current planetary positions
Returns:
List of transit aspects
"""
transits = []
for transiting_planet, transiting_pos in current_positions.items():
for natal_planet, natal_pos in natal_positions.items():
aspect = calculate_aspect(transiting_pos, natal_pos)
if aspect is not None:
aspect_name, angle = aspect
transits.append({
'transiting_planet': transiting_planet,
'natal_planet': natal_planet,
'aspect': aspect_name,
'angle': angle
})
return transits
def describe_transit(transit_info: Dict) -> str:
"""
Create a narrative description of a transit.
Args:
transit_info: Dictionary containing transit details
Returns:
Descriptive text about the transit
"""
trans_planet = transit_info['transiting_planet']
natal_planet = transit_info['natal_planet']
aspect = transit_info['aspect']
return f"The current position of {trans_planet} forms a {aspect} " \
f"to your natal {natal_planet}, activating themes from your birth"
These transit calculations add another layer of personalization to the Oracle's responses. When a user provides birth data, we can identify which current planetary movements are particularly relevant to them personally. This makes the Oracle's pronouncements feel more specifically tailored to the individual seeker rather than generic mystical statements.
INTEGRATING ALL COMPONENTS INTO THE ORACLE SYSTEM
With all the individual components developed, we now integrate them into a cohesive Oracle system. The main Oracle class coordinates between user input, location detection, astrological calculations, and language model interaction. It manages the workflow from receiving a question to delivering a prophetic response. The class should be designed to work with either local or remote language models and to handle cases where birth data is provided or omitted.
The Oracle initialization requires specifying which language model backend to use and providing any necessary configuration such as model paths or API keys. The Oracle should validate that the chosen backend is properly configured before accepting questions. For local models, this means checking that the model file exists and is readable. For remote models, this means verifying that API credentials are available.
When a user asks a question, the Oracle follows a multi-step process. First, it determines the user's current location, either through automatic detection or by asking the user.
Second, it calculates current planetary positions for that location. Third, if birth data is provided, it calculates the natal chart and transits. Fourth, it constructs the astrological context narrative. Fifth, it builds the complete prompt combining the system message, astrological context, and question. Sixth, it sends the prompt to the language model and receives the response. Finally, it formats and presents the response to the user.
Here is the core Oracle class that orchestrates this workflow:
from datetime import datetime
from typing import Optional, Dict, Union
import swisseph as swe
class OracleOfDelphi:
"""
The Oracle of Delphi system integrating astrology and language models.
"""
def __init__(self, llm_interface: Union[LocalLLMInterface, RemoteLLMInterface],
ephemeris_path: str = '/usr/share/ephe'):
"""
Initialize the Oracle system.
Args:
llm_interface: Either LocalLLMInterface or RemoteLLMInterface
ephemeris_path: Path to Swiss Ephemeris data files
"""
self.llm = llm_interface
swe.set_ephe_path(ephemeris_path)
def _calculate_planetary_positions(self, dt: datetime,
latitude: float,
longitude: float) -> Dict[str, float]:
"""
Calculate positions of all major planets for a given time and place.
Args:
dt: Datetime for calculation (should be in UTC)
latitude: Geographic latitude
longitude: Geographic longitude
Returns:
Dictionary mapping planet names to ecliptic longitudes
"""
# Convert datetime to Julian Day
jd = swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute / 60.0 + dt.second / 3600.0)
# Define planets to calculate
planets = {
'Sun': swe.SUN,
'Moon': swe.MOON,
'Mercury': swe.MERCURY,
'Venus': swe.VENUS,
'Mars': swe.MARS,
'Jupiter': swe.JUPITER,
'Saturn': swe.SATURN,
'Uranus': swe.URANUS,
'Neptune': swe.NEPTUNE,
'Pluto': swe.PLUTO
}
positions = {}
for planet_name, planet_id in planets.items():
result, flags = swe.calc_ut(jd, planet_id)
positions[planet_name] = result[0] # Ecliptic longitude
# Calculate Ascendant and Midheaven
houses, ascmc = swe.houses(jd, latitude, longitude, b'P')
positions['Ascendant'] = ascmc[0]
positions['Midheaven'] = ascmc[1]
return positions
def _build_astrological_narrative(self, current_positions: Dict[str, float],
natal_positions: Optional[Dict[str, float]] = None) -> str:
"""
Build a narrative description of the astrological situation.
Args:
current_positions: Current planetary positions
natal_positions: Natal planetary positions (optional)
Returns:
Narrative text describing the astrological context
"""
narrative_parts = []
# Describe current positions
narrative_parts.append("The heavens speak in their eternal dance:")
for planet in ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars',
'Jupiter', 'Saturn']:
if planet in current_positions:
desc = describe_planetary_position(planet,
current_positions[planet],
is_natal=False)
narrative_parts.append(desc)
# Find and describe current aspects
current_aspects = find_all_aspects(current_positions)
if current_aspects:
narrative_parts.append("\nThe celestial bodies form these sacred geometries:")
for aspect in current_aspects[:5]: # Limit to most significant
narrative_parts.append(describe_aspect(aspect))
# If natal data provided, describe transits
if natal_positions:
transits = calculate_transits(natal_positions, current_positions)
if transits:
narrative_parts.append("\nThe current heavens speak to your birth chart:")
for transit in transits[:5]: # Limit to most significant
narrative_parts.append(describe_transit(transit))
return ". ".join(narrative_parts) + "."
def consult_oracle(self, question: str,
birth_datetime: Optional[datetime] = None,
birth_location: Optional[str] = None,
current_location: Optional[str] = None) -> str:
"""
Consult the Oracle with a question.
Args:
question: The question to ask the Oracle
birth_datetime: Birth date and time (optional)
birth_location: Birth location name (optional)
current_location: Current location name (optional, uses birth location if not provided)
Returns:
The Oracle's response
"""
# Determine current location
if current_location:
location = geocode_location(current_location)
elif birth_location:
location = geocode_location(birth_location)
else:
# Default to Delphi, Greece if no location provided
location = {'latitude': 38.4824, 'longitude': 22.5010}
if location is None:
raise ValueError("Could not determine location for astrological calculations")
# Calculate current planetary positions
current_utc = datetime.utcnow()
current_positions = self._calculate_planetary_positions(
current_utc,
location['latitude'],
location['longitude']
)
# Calculate natal chart if birth data provided
natal_positions = None
if birth_datetime and birth_location:
birth_coords = geocode_location(birth_location)
if birth_coords:
birth_utc = convert_to_utc(birth_datetime,
birth_coords['latitude'],
birth_coords['longitude'])
natal_positions = self._calculate_planetary_positions(
birth_utc,
birth_coords['latitude'],
birth_coords['longitude']
)
# Build astrological narrative
astrological_context = self._build_astrological_narrative(
current_positions,
natal_positions
)
# Construct prompt
prompt = construct_oracle_prompt(question, astrological_context)
# Generate response
response = self.llm.generate_response(prompt,
max_tokens=600,
temperature=0.85)
return response
This Oracle class brings together all the components we have developed. It uses the Swiss Ephemeris to calculate planetary positions, the geocoding functions to determine locations, the aspect calculation functions to find significant planetary relationships, the narrative description functions to create readable astrological context, and the language model interface to generate responses. The consult_oracle method provides a simple interface where users provide their question and optional birth data, and receive back the Oracle's metaphorical response.
The class is designed to be flexible and robust. If no location is provided, it defaults to Delphi itself, the historical home of the Oracle. If birth data is incomplete, it simply omits the natal chart from the calculations. The astrological narrative is built progressively, adding layers of detail as more information is available. The language model receives a rich, contextual prompt that enables it to generate responses that are both mystically appropriate and personally relevant.
CREATING AN INTERACTIVE COMMAND-LINE INTERFACE
To make the Oracle accessible to users, we need an interface that collects input and displays responses. A command-line interface provides a simple yet effective way to interact with the Oracle. The interface should guide users through providing their question and optional birth data, handle errors gracefully, and present the Oracle's response in a format that enhances the mystical atmosphere.
The interface begins by welcoming the user and explaining how to interact with the Oracle. It then prompts for the question, which is the only required input. After receiving the question, it asks whether the user wants to provide birth data for a more personalized reading. If they choose to provide birth data, it collects the birth date, time, and location. It should validate all inputs to ensure they are in the correct format and represent valid data.
Once all inputs are collected, the interface displays a message indicating that the Oracle is consulting the celestial spheres. This creates a sense of anticipation and reinforces the mystical theme. After receiving the response from the Oracle system, it formats and displays the response with appropriate visual separation from the input prompts.
Here is an implementation of the command-line interface:
import sys
from datetime import datetime
from typing import Optional, Tuple
class OracleInterface:
"""
Command-line interface for the Oracle of Delphi.
"""
def __init__(self, oracle: OracleOfDelphi):
"""
Initialize the interface.
Args:
oracle: The OracleOfDelphi instance to use
"""
self.oracle = oracle
def print_banner(self):
"""
Print the welcome banner.
"""
banner = """
========================================================================
THE ORACLE OF DELPHI
========================================================================
Welcome, seeker of wisdom. You stand before the sacred Oracle of Delphi,
where the divine speaks through mortal lips. The Pythia awaits your
question, ready to channel the wisdom of Apollo himself.
The Oracle speaks not in plain words but in symbols and metaphors,
drawn from the eternal patterns of the cosmos. Contemplate the response
you receive, for truth often hides beneath veils of mystery.
========================================================================
"""
print(banner)
def get_question(self) -> str:
"""
Prompt for and validate the user's question.
Returns:
The user's question
"""
while True:
print("\nWhat question do you bring to the Oracle?")
print("(Enter your question, or 'quit' to exit)")
print("> ", end="")
question = input().strip()
if question.lower() == 'quit':
print("\nThe Oracle bids you farewell. May wisdom guide your path.")
sys.exit(0)
if len(question) < 10:
print("The Oracle requires a more substantial question.")
continue
return question
def get_birth_data(self) -> Tuple[Optional[datetime], Optional[str]]:
"""
Prompt for optional birth data.
Returns:
Tuple of (birth_datetime, birth_location) or (None, None)
"""
print("\nWould you like to provide your birth data for a more")
print("personalized reading? (yes/no)")
print("> ", end="")
response = input().strip().lower()
if response not in ['yes', 'y']:
return None, None
# Get birth date
while True:
print("\nEnter your birth date (YYYY-MM-DD):")
print("> ", end="")
date_str = input().strip()
try:
birth_date = datetime.strptime(date_str, "%Y-%m-%d")
break
except ValueError:
print("Invalid date format. Please use YYYY-MM-DD.")
# Get birth time
while True:
print("\nEnter your birth time (HH:MM in 24-hour format):")
print("(If unknown, enter 12:00)")
print("> ", end="")
time_str = input().strip()
try:
time_parts = time_str.split(':')
hour = int(time_parts[0])
minute = int(time_parts[1]) if len(time_parts) > 1 else 0
if 0 <= hour <= 23 and 0 <= minute <= 59:
birth_datetime = birth_date.replace(hour=hour, minute=minute)
break
else:
print("Invalid time. Hours must be 0-23, minutes 0-59.")
except (ValueError, IndexError):
print("Invalid time format. Please use HH:MM.")
# Get birth location
while True:
print("\nEnter your birth location (city, country):")
print("> ", end="")
location = input().strip()
if len(location) < 3:
print("Please enter a valid location.")
continue
# Verify location can be geocoded
coords = geocode_location(location)
if coords:
print(f"Location found: {coords.get('display_name', location)}")
return birth_datetime, location
else:
print("Could not find that location. Please try again.")
def display_response(self, response: str):
"""
Display the Oracle's response with formatting.
Args:
response: The Oracle's response text
"""
print("\n" + "=" * 72)
print("THE ORACLE SPEAKS:")
print("=" * 72)
print()
# Word wrap the response to 70 characters
words = response.split()
lines = []
current_line = []
current_length = 0
for word in words:
word_length = len(word)
if current_length + word_length + len(current_line) > 70:
lines.append(' '.join(current_line))
current_line = [word]
current_length = word_length
else:
current_line.append(word)
current_length += word_length
if current_line:
lines.append(' '.join(current_line))
for line in lines:
print(line)
print()
print("=" * 72)
print()
def run(self):
"""
Run the interactive Oracle interface.
"""
self.print_banner()
while True:
try:
# Get question
question = self.get_question()
# Get optional birth data
birth_datetime, birth_location = self.get_birth_data()
# Consult the Oracle
print("\nThe Pythia enters her trance...")
print("The Oracle consults the celestial spheres...")
print()
response = self.oracle.consult_oracle(
question=question,
birth_datetime=birth_datetime,
birth_location=birth_location
)
# Display response
self.display_response(response)
# Ask if user wants another consultation
print("Would you like to ask another question? (yes/no)")
print("> ", end="")
continue_response = input().strip().lower()
if continue_response not in ['yes', 'y']:
print("\nThe Oracle bids you farewell.")
print("May the wisdom you have received illuminate your path.")
break
except KeyboardInterrupt:
print("\n\nThe Oracle session has been interrupted.")
print("Farewell, seeker.")
break
except Exception as e:
print(f"\nAn error occurred: {e}")
print("The Oracle's connection to the divine has been disrupted.")
print("Please try again.")
This interface class provides a complete interactive experience. The print_banner method displays an atmospheric welcome message that sets the tone for the interaction. The get_question method prompts for the user's question and validates that it is substantial enough to warrant the Oracle's attention. The get_birth_data method guides users through providing optional birth information, validating each piece of data and confirming that locations can be geocoded. The display_response method formats the Oracle's response with visual separators and word wrapping for readability. The run method orchestrates the entire interaction loop, allowing users to ask multiple questions in a single session.
ENHANCING RESPONSES WITH SYMBOLIC INTERPRETATION
The Oracle's responses gain depth when they incorporate symbolic interpretations of astrological configurations. Rather than simply mentioning that Mars is in Aries, the Oracle might speak of "the warrior returning to his homeland, his strength renewed." These symbolic interpretations draw on the traditional meanings associated with planets, signs, and aspects in astrological tradition.
Each planet has a set of associated meanings and symbolism. The Sun represents identity, vitality, and life force. The Moon represents emotions, instincts, and the unconscious. Mercury governs communication, thought, and travel. Venus rules love, beauty, and values. Mars represents action, desire, and conflict. Jupiter signifies expansion, wisdom, and fortune. Saturn indicates structure, limitation, and responsibility. The outer planets Uranus, Neptune, and Pluto represent generational themes and transformative forces.
Each zodiac sign also carries symbolic meanings. Aries is the pioneer and warrior. Taurus is the builder and sensualist. Gemini is the communicator and thinker. Cancer is the nurturer and protector. Leo is the creator and performer. Virgo is the analyst and servant. Libra is the diplomat and artist. Scorpio is the transformer and investigator. Sagittarius is the philosopher and explorer. Capricorn is the achiever and authority. Aquarius is the innovator and humanitarian. Pisces is the mystic and dreamer.
We can create a symbolic interpretation system that maps astrological configurations to metaphorical language:
class SymbolicInterpreter:
"""
Interprets astrological configurations into symbolic language.
"""
def __init__(self):
"""
Initialize the symbolic interpreter with traditional meanings.
"""
self.planet_symbols = {
'Sun': {
'essence': 'the radiant source of life and identity',
'action': 'illuminates',
'domain': 'the realm of self and vitality'
},
'Moon': {
'essence': 'the silver mirror of emotion and instinct',
'action': 'reflects',
'domain': 'the tides of feeling and memory'
},
'Mercury': {
'essence': 'the swift messenger between worlds',
'action': 'communicates',
'domain': 'pathways of thought and speech'
},
'Venus': {
'essence': 'the morning star of beauty and desire',
'action': 'attracts',
'domain': 'gardens of love and value'
},
'Mars': {
'essence': 'the red warrior of will and courage',
'action': 'asserts',
'domain': 'battlefields of action and passion'
},
'Jupiter': {
'essence': 'the great benefactor and teacher',
'action': 'expands',
'domain': 'horizons of wisdom and fortune'
},
'Saturn': {
'essence': 'the stern architect of time and form',
'action': 'structures',
'domain': 'foundations of responsibility and mastery'
}
}
self.sign_symbols = {
'Aries': 'the forge where new beginnings are hammered into being',
'Taurus': 'the fertile garden where seeds become sustenance',
'Gemini': 'the crossroads where ideas meet and multiply',
'Cancer': 'the sheltering cove where emotions find harbor',
'Leo': 'the golden stage where the self performs its truth',
'Virgo': 'the sacred workshop where chaos becomes order',
'Libra': 'the scales where opposing forces find balance',
'Scorpio': 'the depths where transformation burns away the false',
'Sagittarius': 'the open road where meaning is sought beyond horizons',
'Capricorn': 'the mountain peak where ambition meets achievement',
'Aquarius': 'the lightning strike where innovation shatters convention',
'Pisces': 'the boundless ocean where all boundaries dissolve'
}
self.aspect_symbols = {
'conjunction': 'merges its essence with',
'opposition': 'stands in tension with',
'trine': 'flows harmoniously toward',
'square': 'challenges and spurs',
'sextile': 'offers opportunity to'
}
def interpret_placement(self, planet: str, sign: str) -> str:
"""
Create a symbolic interpretation of a planet in a sign.
Args:
planet: Planet name
sign: Zodiac sign name
Returns:
Symbolic interpretation text
"""
planet_info = self.planet_symbols.get(planet, {})
sign_symbol = self.sign_symbols.get(sign, f'the realm of {sign}')
essence = planet_info.get('essence', planet)
return f"{essence} dwells in {sign_symbol}"
def interpret_aspect(self, planet1: str, planet2: str, aspect: str) -> str:
"""
Create a symbolic interpretation of an aspect.
Args:
planet1: First planet name
planet2: Second planet name
aspect: Aspect type
Returns:
Symbolic interpretation text
"""
p1_essence = self.planet_symbols.get(planet1, {}).get('essence', planet1)
p2_essence = self.planet_symbols.get(planet2, {}).get('essence', planet2)
aspect_verb = self.aspect_symbols.get(aspect, 'relates to')
return f"{p1_essence} {aspect_verb} {p2_essence}"
def create_symbolic_context(self, positions: Dict[str, float],
aspects: List[Dict]) -> str:
"""
Create a rich symbolic narrative from astrological data.
Args:
positions: Dictionary of planetary positions
aspects: List of aspect dictionaries
Returns:
Symbolic narrative text
"""
zodiac_signs = [
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
]
narrative = []
# Interpret key planetary placements
for planet in ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars']:
if planet in positions:
sign_index = int(positions[planet] / 30)
sign = zodiac_signs[sign_index]
interpretation = self.interpret_placement(planet, sign)
narrative.append(interpretation)
# Interpret significant aspects
for aspect in aspects[:3]: # Focus on most significant
interpretation = self.interpret_aspect(
aspect['planet1'],
aspect['planet2'],
aspect['aspect']
)
narrative.append(interpretation)
return ". ".join(narrative) + "."
This symbolic interpreter adds a layer of poetic meaning to the astrological data. Instead of dry technical descriptions, it provides evocative metaphors that the language model can weave into its responses. The Oracle's pronouncements become richer and more atmospheric when they can reference "the swift messenger between worlds dwelling in the crossroads where ideas meet and multiply" rather than simply "Mercury in Gemini."
FULL PRODUCTION-READY IMPLEMENTATION
The following is a complete, production-ready implementation of the Oracle of Delphi system. This code integrates all components discussed above into a working application that supports both local and remote language models across multiple GPU architectures. The implementation includes comprehensive error handling, configuration management, logging, and a polished user interface.
#!/usr/bin/env python3
"""
Oracle of Delphi - A mystical interface combining astrology and AI
This system recreates the experience of consulting the ancient Oracle of Delphi
by combining astrological calculations with large language models to generate
metaphorical, personalized responses to user questions.
Supports:
- Local LLMs with GPU acceleration (CUDA, ROCm, Metal, Intel)
- Remote LLM APIs (OpenAI, Anthropic)
- Personalized astrological calculations
- Birth chart and transit analysis
"""
import os
import sys
import platform
import subprocess
import logging
from datetime import datetime
from typing import Optional, Dict, List, Tuple, Union, Any
import requests
import swisseph as swe
import pytz
from timezonefinder import TimezoneFinder
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('OracleOfDelphi')
class ConfigurationError(Exception):
"""Raised when there is a configuration problem."""
pass
class LocationError(Exception):
"""Raised when location cannot be determined or geocoded."""
pass
class AstrologyError(Exception):
"""Raised when astrological calculations fail."""
pass
class LocationService:
"""
Handles geocoding and timezone operations.
"""
def __init__(self, user_agent: str = 'OracleOfDelphi/1.0'):
"""
Initialize the location service.
Args:
user_agent: User agent string for API requests
"""
self.user_agent = user_agent
self.timezone_finder = TimezoneFinder()
def geocode(self, location_name: str) -> Dict[str, Any]:
"""
Convert a location name to geographic coordinates.
Args:
location_name: Name of the location
Returns:
Dictionary with latitude, longitude, and display_name
Raises:
LocationError: If location cannot be geocoded
"""
url = "https://nominatim.openstreetmap.org/search"
params = {
'q': location_name,
'format': 'json',
'limit': 1
}
headers = {'User-Agent': self.user_agent}
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()
results = response.json()
if not results:
raise LocationError(f"Location not found: {location_name}")
return {
'latitude': float(results[0]['lat']),
'longitude': float(results[0]['lon']),
'display_name': results[0]['display_name']
}
except requests.RequestException as e:
raise LocationError(f"Geocoding failed: {e}")
def get_timezone(self, latitude: float, longitude: float) -> str:
"""
Get timezone name for coordinates.
Args:
latitude: Geographic latitude
longitude: Geographic longitude
Returns:
Timezone name
Raises:
LocationError: If timezone cannot be determined
"""
timezone_str = self.timezone_finder.timezone_at(lat=latitude, lng=longitude)
if timezone_str is None:
raise LocationError("Could not determine timezone for coordinates")
return timezone_str
def convert_to_utc(self, local_datetime: datetime, latitude: float,
longitude: float) -> datetime:
"""
Convert local datetime to UTC.
Args:
local_datetime: Naive datetime in local time
latitude: Geographic latitude
longitude: Geographic longitude
Returns:
Datetime in UTC
"""
timezone_str = self.get_timezone(latitude, longitude)
local_tz = pytz.timezone(timezone_str)
localized_dt = local_tz.localize(local_datetime)
return localized_dt.astimezone(pytz.UTC)
class AstrologicalCalculator:
"""
Performs astrological calculations using Swiss Ephemeris.
"""
PLANETS = {
'Sun': swe.SUN,
'Moon': swe.MOON,
'Mercury': swe.MERCURY,
'Venus': swe.VENUS,
'Mars': swe.MARS,
'Jupiter': swe.JUPITER,
'Saturn': swe.SATURN,
'Uranus': swe.URANUS,
'Neptune': swe.NEPTUNE,
'Pluto': swe.PLUTO
}
ZODIAC_SIGNS = [
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
]
ASPECTS = {
'conjunction': (0, 10),
'sextile': (60, 6),
'square': (90, 8),
'trine': (120, 8),
'opposition': (180, 10)
}
def __init__(self, ephemeris_path: str = None):
"""
Initialize the astrological calculator.
Args:
ephemeris_path: Path to Swiss Ephemeris data files
"""
if ephemeris_path:
swe.set_ephe_path(ephemeris_path)
else:
# Try common locations
common_paths = [
'/usr/share/ephe',
'/usr/local/share/ephe',
os.path.expanduser('~/.ephe')
]
for path in common_paths:
if os.path.exists(path):
swe.set_ephe_path(path)
break
def calculate_positions(self, dt: datetime, latitude: float,
longitude: float) -> Dict[str, float]:
"""
Calculate planetary positions for a given time and place.
Args:
dt: Datetime in UTC
latitude: Geographic latitude
longitude: Geographic longitude
Returns:
Dictionary mapping planet names to ecliptic longitudes
Raises:
AstrologyError: If calculations fail
"""
try:
jd = swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute / 60.0 + dt.second / 3600.0)
positions = {}
for planet_name, planet_id in self.PLANETS.items():
result, flags = swe.calc_ut(jd, planet_id)
positions[planet_name] = result[0]
houses, ascmc = swe.houses(jd, latitude, longitude, b'P')
positions['Ascendant'] = ascmc[0]
positions['Midheaven'] = ascmc[1]
return positions
except Exception as e:
raise AstrologyError(f"Failed to calculate positions: {e}")
def get_sign(self, longitude: float) -> Tuple[str, float]:
"""
Get zodiac sign and degree for a longitude.
Args:
longitude: Ecliptic longitude in degrees
Returns:
Tuple of (sign_name, degree_in_sign)
"""
sign_index = int(longitude / 30)
degree_in_sign = longitude % 30
return self.ZODIAC_SIGNS[sign_index], degree_in_sign
def calculate_aspect(self, pos1: float, pos2: float) -> Optional[Tuple[str, float]]:
"""
Determine if two positions form a major aspect.
Args:
pos1: First ecliptic longitude
pos2: Second ecliptic longitude
Returns:
Tuple of (aspect_name, exact_angle) or None
"""
separation = abs(pos1 - pos2)
if separation > 180:
separation = 360 - separation
for aspect_name, (angle, orb) in self.ASPECTS.items():
if abs(separation - angle) <= orb:
return aspect_name, separation
return None
def find_aspects(self, positions: Dict[str, float]) -> List[Dict[str, Any]]:
"""
Find all major aspects in a set of positions.
Args:
positions: Dictionary of planetary positions
Returns:
List of aspect dictionaries
"""
aspects = []
planets = [p for p in self.PLANETS.keys() if p in positions]
for i in range(len(planets)):
for j in range(i + 1, len(planets)):
planet1 = planets[i]
planet2 = planets[j]
aspect = self.calculate_aspect(positions[planet1], positions[planet2])
if aspect:
aspects.append({
'planet1': planet1,
'planet2': planet2,
'aspect': aspect[0],
'angle': aspect[1]
})
return aspects
def calculate_transits(self, natal_positions: Dict[str, float],
current_positions: Dict[str, float]) -> List[Dict[str, Any]]:
"""
Calculate transits between current and natal positions.
Args:
natal_positions: Natal planetary positions
current_positions: Current planetary positions
Returns:
List of transit dictionaries
"""
transits = []
for trans_planet, trans_pos in current_positions.items():
if trans_planet not in self.PLANETS:
continue
for natal_planet, natal_pos in natal_positions.items():
if natal_planet not in self.PLANETS:
continue
aspect = self.calculate_aspect(trans_pos, natal_pos)
if aspect:
transits.append({
'transiting_planet': trans_planet,
'natal_planet': natal_planet,
'aspect': aspect[0],
'angle': aspect[1]
})
return transits
class SymbolicInterpreter:
"""
Interprets astrological data into symbolic, metaphorical language.
"""
PLANET_SYMBOLS = {
'Sun': {
'essence': 'the radiant source of life and identity',
'action': 'illuminates',
'domain': 'the realm of self and vitality'
},
'Moon': {
'essence': 'the silver mirror of emotion and instinct',
'action': 'reflects',
'domain': 'the tides of feeling and memory'
},
'Mercury': {
'essence': 'the swift messenger between worlds',
'action': 'communicates',
'domain': 'pathways of thought and speech'
},
'Venus': {
'essence': 'the morning star of beauty and desire',
'action': 'attracts',
'domain': 'gardens of love and value'
},
'Mars': {
'essence': 'the red warrior of will and courage',
'action': 'asserts',
'domain': 'battlefields of action and passion'
},
'Jupiter': {
'essence': 'the great benefactor and teacher',
'action': 'expands',
'domain': 'horizons of wisdom and fortune'
},
'Saturn': {
'essence': 'the stern architect of time and form',
'action': 'structures',
'domain': 'foundations of responsibility and mastery'
},
'Uranus': {
'essence': 'the lightning bolt of sudden change',
'action': 'revolutionizes',
'domain': 'frontiers of innovation and freedom'
},
'Neptune': {
'essence': 'the mystic veil between worlds',
'action': 'dissolves',
'domain': 'oceans of dreams and transcendence'
},
'Pluto': {
'essence': 'the transformer dwelling in shadow',
'action': 'regenerates',
'domain': 'underworld of death and rebirth'
}
}
SIGN_SYMBOLS = {
'Aries': 'the forge where new beginnings are hammered into being',
'Taurus': 'the fertile garden where seeds become sustenance',
'Gemini': 'the crossroads where ideas meet and multiply',
'Cancer': 'the sheltering cove where emotions find harbor',
'Leo': 'the golden stage where the self performs its truth',
'Virgo': 'the sacred workshop where chaos becomes order',
'Libra': 'the scales where opposing forces find balance',
'Scorpio': 'the depths where transformation burns away the false',
'Sagittarius': 'the open road where meaning is sought beyond horizons',
'Capricorn': 'the mountain peak where ambition meets achievement',
'Aquarius': 'the lightning strike where innovation shatters convention',
'Pisces': 'the boundless ocean where all boundaries dissolve'
}
ASPECT_VERBS = {
'conjunction': 'merges its essence with',
'opposition': 'stands in creative tension with',
'trine': 'flows harmoniously toward',
'square': 'challenges and spurs to growth',
'sextile': 'offers opportunity to'
}
def __init__(self, calculator: AstrologicalCalculator):
"""
Initialize the symbolic interpreter.
Args:
calculator: AstrologicalCalculator instance
"""
self.calculator = calculator
def describe_position(self, planet: str, longitude: float,
is_natal: bool = False) -> str:
"""
Create symbolic description of a planetary position.
Args:
planet: Planet name
longitude: Ecliptic longitude
is_natal: Whether this is a natal position
Returns:
Symbolic description
"""
sign, degree = self.calculator.get_sign(longitude)
planet_info = self.PLANET_SYMBOLS.get(planet, {})
essence = planet_info.get('essence', planet)
sign_symbol = self.SIGN_SYMBOLS.get(sign, f'the realm of {sign}')
if degree < 10:
position_desc = "newly entered"
elif degree < 20:
position_desc = "dwelling within"
else:
position_desc = "preparing to depart"
time_context = "was born with" if is_natal else "now finds"
return f"{essence} {time_context} {position_desc} {sign_symbol}"
def describe_aspect(self, aspect_info: Dict[str, Any]) -> str:
"""
Create symbolic description of an aspect.
Args:
aspect_info: Dictionary with aspect details
Returns:
Symbolic description
"""
p1 = aspect_info['planet1']
p2 = aspect_info['planet2']
aspect = aspect_info['aspect']
p1_essence = self.PLANET_SYMBOLS.get(p1, {}).get('essence', p1)
p2_essence = self.PLANET_SYMBOLS.get(p2, {}).get('essence', p2)
verb = self.ASPECT_VERBS.get(aspect, 'relates to')
return f"{p1_essence} {verb} {p2_essence}"
def describe_transit(self, transit_info: Dict[str, Any]) -> str:
"""
Create symbolic description of a transit.
Args:
transit_info: Dictionary with transit details
Returns:
Symbolic description
"""
trans_planet = transit_info['transiting_planet']
natal_planet = transit_info['natal_planet']
aspect = transit_info['aspect']
trans_essence = self.PLANET_SYMBOLS.get(trans_planet, {}).get('essence', trans_planet)
natal_essence = self.PLANET_SYMBOLS.get(natal_planet, {}).get('essence', natal_planet)
verb = self.ASPECT_VERBS.get(aspect, 'activates')
return f"The current dance of {trans_essence} {verb} your natal {natal_essence}"
def create_narrative(self, current_positions: Dict[str, float],
natal_positions: Optional[Dict[str, float]] = None) -> str:
"""
Create complete symbolic narrative of astrological situation.
Args:
current_positions: Current planetary positions
natal_positions: Optional natal positions
Returns:
Complete narrative text
"""
narrative_parts = []
narrative_parts.append("The celestial tapestry reveals:")
for planet in ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn']:
if planet in current_positions:
desc = self.describe_position(planet, current_positions[planet], False)
narrative_parts.append(desc)
current_aspects = self.calculator.find_aspects(current_positions)
if current_aspects:
narrative_parts.append("\nThe heavenly bodies form sacred geometries:")
for aspect in current_aspects[:5]:
narrative_parts.append(self.describe_aspect(aspect))
if natal_positions:
narrative_parts.append("\nAt your birth, the cosmos arranged itself thus:")
for planet in ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars']:
if planet in natal_positions:
desc = self.describe_position(planet, natal_positions[planet], True)
narrative_parts.append(desc)
transits = self.calculator.calculate_transits(natal_positions, current_positions)
if transits:
narrative_parts.append("\nThe current heavens speak to your birth pattern:")
for transit in transits[:5]:
narrative_parts.append(self.describe_transit(transit))
return ". ".join(narrative_parts) + "."
class LLMInterface:
"""
Base class for LLM interfaces.
"""
def generate_response(self, prompt: str, max_tokens: int = 600,
temperature: float = 0.85) -> str:
"""
Generate a response from the LLM.
Args:
prompt: Input prompt
max_tokens: Maximum tokens to generate
temperature: Sampling temperature
Returns:
Generated text
"""
raise NotImplementedError
class LocalLLMInterface(LLMInterface):
"""
Interface for local LLMs using llama-cpp-python.
"""
def __init__(self, model_path: str, context_length: int = 4096):
"""
Initialize local LLM interface.
Args:
model_path: Path to GGUF model file
context_length: Context window size
Raises:
ConfigurationError: If model cannot be loaded
"""
if not os.path.exists(model_path):
raise ConfigurationError(f"Model file not found: {model_path}")
self.model_path = model_path
self.context_length = context_length
self.model = None
self.gpu_layers = self._detect_gpu()
logger.info(f"Initializing local LLM with {self.gpu_layers} GPU layers")
def _detect_gpu(self) -> int:
"""
Detect available GPU and determine layer offloading.
Returns:
Number of layers to offload to GPU
"""
system = platform.system()
try:
result = subprocess.run(['nvidia-smi'], capture_output=True,
text=True, timeout=5)
if result.returncode == 0:
logger.info("Detected Nvidia GPU with CUDA")
return 35
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
try:
result = subprocess.run(['rocm-smi'], capture_output=True,
text=True, timeout=5)
if result.returncode == 0:
logger.info("Detected AMD GPU with ROCm")
return 35
except (subprocess.TimeoutExpired, FileNotFoundError):
pass
if system == 'Darwin' and platform.processor() == 'arm':
logger.info("Detected Apple Silicon with Metal")
return 1
logger.info("No GPU detected, using CPU")
return 0
def _load_model(self):
"""
Load the model if not already loaded.
"""
if self.model is not None:
return
try:
from llama_cpp import Llama
self.model = Llama(
model_path=self.model_path,
n_ctx=self.context_length,
n_gpu_layers=self.gpu_layers,
verbose=False
)
logger.info("Model loaded successfully")
except ImportError:
raise ConfigurationError("llama-cpp-python not installed")
except Exception as e:
raise ConfigurationError(f"Failed to load model: {e}")
def generate_response(self, prompt: str, max_tokens: int = 600,
temperature: float = 0.85) -> str:
"""
Generate response using local model.
"""
self._load_model()
try:
response = self.model(
prompt,
max_tokens=max_tokens,
temperature=temperature,
stop=["</s>", "Human:", "User:", "\n\n\n"],
echo=False
)
return response['choices'][0]['text'].strip()
except Exception as e:
logger.error(f"Generation failed: {e}")
raise
class RemoteLLMInterface(LLMInterface):
"""
Interface for remote LLM APIs.
"""
def __init__(self, provider: str, api_key: Optional[str] = None,
model: Optional[str] = None):
"""
Initialize remote LLM interface.
Args:
provider: API provider name
api_key: API key (reads from environment if None)
model: Model identifier
Raises:
ConfigurationError: If configuration is invalid
"""
self.provider = provider.lower()
self.api_key = api_key or self._get_api_key()
self.model = model or self._get_default_model()
self.client = self._initialize_client()
logger.info(f"Initialized {self.provider} API with model {self.model}")
def _get_api_key(self) -> str:
"""
Get API key from environment.
"""
env_vars = {
'openai': 'OPENAI_API_KEY',
'anthropic': 'ANTHROPIC_API_KEY'
}
env_var = env_vars.get(self.provider)
if not env_var:
raise ConfigurationError(f"Unknown provider: {self.provider}")
api_key = os.getenv(env_var)
if not api_key:
raise ConfigurationError(f"API key not found: {env_var}")
return api_key
def _get_default_model(self) -> str:
"""
Get default model for provider.
"""
defaults = {
'openai': 'gpt-4-turbo-preview',
'anthropic': 'claude-3-opus-20240229'
}
return defaults.get(self.provider, 'gpt-3.5-turbo')
def _initialize_client(self) -> Any:
"""
Initialize API client.
"""
try:
if self.provider == 'openai':
from openai import OpenAI
return OpenAI(api_key=self.api_key)
elif self.provider == 'anthropic':
from anthropic import Anthropic
return Anthropic(api_key=self.api_key)
else:
raise ConfigurationError(f"Unsupported provider: {self.provider}")
except ImportError as e:
raise ConfigurationError(f"Required library not installed: {e}")
def generate_response(self, prompt: str, max_tokens: int = 600,
temperature: float = 0.85) -> str:
"""
Generate response using remote API.
"""
try:
if self.provider == 'openai':
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "You are the Oracle of Delphi."},
{"role": "user", "content": prompt}
],
max_tokens=max_tokens,
temperature=temperature
)
return response.choices[0].message.content
elif self.provider == 'anthropic':
response = self.client.messages.create(
model=self.model,
max_tokens=max_tokens,
temperature=temperature,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except Exception as e:
logger.error(f"API call failed: {e}")
raise
class OracleOfDelphi:
"""
Main Oracle system coordinating all components.
"""
SYSTEM_MESSAGE = """You are the Oracle of Delphi, the most sacred oracle of ancient Greece.
For over a thousand years, pilgrims traveled from across the known world to seek your wisdom.
You speak through the Pythia, the high priestess who enters a divine trance to channel prophecy.
Your responses are never direct or literal. You speak in metaphors, symbols, allegories, and
riddles drawn from nature, mythology, the elements, and the cosmos. Your words require
contemplation and interpretation. Yet beneath the enigmatic surface, your prophecies contain
genuine wisdom and insight that addresses the seeker's true question.
You weave the celestial movements and astrological significances into your responses, for the
positions of the heavenly bodies reflect the patterns of fate and fortune. The stars and planets
are not mere decorations in your speech but integral to the meaning you convey.
Speak as the ancient Oracle spoke: mysterious yet meaningful, cryptic yet ultimately helpful,
poetic yet profound. Draw upon the astrological context provided to create responses that are
both personally relevant and universally resonant."""
def __init__(self, llm_interface: LLMInterface,
location_service: Optional[LocationService] = None,
calculator: Optional[AstrologicalCalculator] = None):
"""
Initialize the Oracle system.
Args:
llm_interface: LLM interface to use
location_service: Location service (creates default if None)
calculator: Astrological calculator (creates default if None)
"""
self.llm = llm_interface
self.location_service = location_service or LocationService()
self.calculator = calculator or AstrologicalCalculator()
self.interpreter = SymbolicInterpreter(self.calculator)
logger.info("Oracle of Delphi initialized")
def _construct_prompt(self, question: str, astrological_context: str) -> str:
"""
Construct complete prompt for the LLM.
Args:
question: User's question
astrological_context: Astrological narrative
Returns:
Complete prompt
"""
return f"""{self.SYSTEM_MESSAGE}
{astrological_context}
A seeker approaches the sacred temple and poses this question:
"{question}"
Oracle, what wisdom do you offer this seeker?"""
def consult(self, question: str,
birth_datetime: Optional[datetime] = None,
birth_location: Optional[str] = None,
current_location: Optional[str] = None) -> str:
"""
Consult the Oracle with a question.
Args:
question: The question to ask
birth_datetime: Birth date and time (naive datetime)
birth_location: Birth location name
current_location: Current location name
Returns:
The Oracle's response
Raises:
LocationError: If location cannot be determined
AstrologyError: If calculations fail
"""
logger.info(f"Consultation requested: {question[:50]}...")
if current_location:
location = self.location_service.geocode(current_location)
elif birth_location:
location = self.location_service.geocode(birth_location)
else:
location = {'latitude': 38.4824, 'longitude': 22.5010}
logger.info("Using default location: Delphi, Greece")
current_utc = datetime.utcnow()
current_positions = self.calculator.calculate_positions(
current_utc,
location['latitude'],
location['longitude']
)
natal_positions = None
if birth_datetime and birth_location:
try:
birth_coords = self.location_service.geocode(birth_location)
birth_utc = self.location_service.convert_to_utc(
birth_datetime,
birth_coords['latitude'],
birth_coords['longitude']
)
natal_positions = self.calculator.calculate_positions(
birth_utc,
birth_coords['latitude'],
birth_coords['longitude']
)
logger.info("Calculated natal chart")
except Exception as e:
logger.warning(f"Could not calculate natal chart: {e}")
astrological_context = self.interpreter.create_narrative(
current_positions,
natal_positions
)
prompt = self._construct_prompt(question, astrological_context)
logger.info("Generating Oracle response")
response = self.llm.generate_response(prompt)
return response
class OracleInterface:
"""
Interactive command-line interface for the Oracle.
"""
BANNER = """
========================================================================
THE ORACLE OF DELPHI
========================================================================
Welcome, seeker of wisdom. You stand before the sacred Oracle of Delphi,
where the divine speaks through mortal lips. The Pythia awaits your
question, ready to channel the wisdom of Apollo himself.
The Oracle speaks not in plain words but in symbols and metaphors,
drawn from the eternal patterns of the cosmos. Contemplate the response
you receive, for truth often hides beneath veils of mystery.
========================================================================
"""
def __init__(self, oracle: OracleOfDelphi):
"""
Initialize the interface.
Args:
oracle: OracleOfDelphi instance
"""
self.oracle = oracle
def print_banner(self):
"""Print welcome banner."""
print(self.BANNER)
def get_question(self) -> str:
"""
Prompt for user's question.
Returns:
The question
"""
while True:
print("\nWhat question do you bring to the Oracle?")
print("(Enter your question, or 'quit' to exit)")
print("> ", end="")
question = input().strip()
if question.lower() == 'quit':
print("\nThe Oracle bids you farewell. May wisdom guide your path.")
sys.exit(0)
if len(question) < 10:
print("The Oracle requires a more substantial question.")
continue
return question
def get_birth_data(self) -> Tuple[Optional[datetime], Optional[str]]:
"""
Prompt for optional birth data.
Returns:
Tuple of (birth_datetime, birth_location)
"""
print("\nWould you like to provide your birth data for a more")
print("personalized reading? (yes/no)")
print("> ", end="")
if input().strip().lower() not in ['yes', 'y']:
return None, None
while True:
print("\nEnter your birth date (YYYY-MM-DD):")
print("> ", end="")
date_str = input().strip()
try:
birth_date = datetime.strptime(date_str, "%Y-%m-%d")
break
except ValueError:
print("Invalid date format. Please use YYYY-MM-DD.")
while True:
print("\nEnter your birth time (HH:MM in 24-hour format):")
print("(If unknown, enter 12:00)")
print("> ", end="")
time_str = input().strip()
try:
parts = time_str.split(':')
hour = int(parts[0])
minute = int(parts[1]) if len(parts) > 1 else 0
if 0 <= hour <= 23 and 0 <= minute <= 59:
birth_datetime = birth_date.replace(hour=hour, minute=minute)
break
else:
print("Invalid time. Hours: 0-23, minutes: 0-59.")
except (ValueError, IndexError):
print("Invalid time format. Please use HH:MM.")
while True:
print("\nEnter your birth location (city, country):")
print("> ", end="")
location = input().strip()
if len(location) < 3:
print("Please enter a valid location.")
continue
try:
coords = self.oracle.location_service.geocode(location)
print(f"Location found: {coords['display_name']}")
return birth_datetime, location
except LocationError:
print("Could not find that location. Please try again.")
def display_response(self, response: str):
"""
Display the Oracle's response.
Args:
response: Response text
"""
print("\n" + "=" * 72)
print("THE ORACLE SPEAKS:")
print("=" * 72)
print()
words = response.split()
lines = []
current_line = []
current_length = 0
for word in words:
if current_length + len(word) + len(current_line) > 70:
lines.append(' '.join(current_line))
current_line = [word]
current_length = len(word)
else:
current_line.append(word)
current_length += len(word)
if current_line:
lines.append(' '.join(current_line))
for line in lines:
print(line)
print()
print("=" * 72)
print()
def run(self):
"""
Run the interactive interface.
"""
self.print_banner()
while True:
try:
question = self.get_question()
birth_datetime, birth_location = self.get_birth_data()
print("\nThe Pythia enters her trance...")
print("The Oracle consults the celestial spheres...")
print()
response = self.oracle.consult(
question=question,
birth_datetime=birth_datetime,
birth_location=birth_location
)
self.display_response(response)
print("Would you like to ask another question? (yes/no)")
print("> ", end="")
if input().strip().lower() not in ['yes', 'y']:
print("\nThe Oracle bids you farewell.")
print("May the wisdom you have received illuminate your path.")
break
except KeyboardInterrupt:
print("\n\nThe Oracle session has been interrupted.")
print("Farewell, seeker.")
break
except Exception as e:
logger.error(f"Error during consultation: {e}", exc_info=True)
print(f"\nAn error occurred: {e}")
print("The Oracle's connection has been disrupted.")
print("Please try again.")
def main():
"""
Main entry point for the Oracle application.
"""
import argparse
parser = argparse.ArgumentParser(
description='Oracle of Delphi - Mystical AI-powered divination'
)
parser.add_argument(
'--mode',
choices=['local', 'remote'],
default='local',
help='LLM mode: local or remote API'
)
parser.add_argument(
'--model-path',
help='Path to local GGUF model file (for local mode)'
)
parser.add_argument(
'--provider',
choices=['openai', 'anthropic'],
help='API provider (for remote mode)'
)
parser.add_argument(
'--api-key',
help='API key (reads from environment if not provided)'
)
parser.add_argument(
'--ephemeris-path',
help='Path to Swiss Ephemeris data files'
)
parser.add_argument(
'--verbose',
action='store_true',
help='Enable verbose logging'
)
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
try:
if args.mode == 'local':
if not args.model_path:
print("Error: --model-path required for local mode")
sys.exit(1)
llm = LocalLLMInterface(args.model_path)
else:
if not args.provider:
print("Error: --provider required for remote mode")
sys.exit(1)
llm = RemoteLLMInterface(args.provider, args.api_key)
calculator = AstrologicalCalculator(args.ephemeris_path)
oracle = OracleOfDelphi(llm, calculator=calculator)
interface = OracleInterface(oracle)
interface.run()
except ConfigurationError as e:
print(f"Configuration error: {e}")
sys.exit(1)
except Exception as e:
logger.error(f"Fatal error: {e}", exc_info=True)
print(f"Fatal error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
This complete implementation provides a fully functional Oracle of Delphi system. It supports both local and remote language models with automatic GPU detection for Nvidia CUDA, AMD ROCm, Apple Metal, and Intel architectures. The system performs accurate astrological calculations using the Swiss Ephemeris library, converts locations to coordinates using the Nominatim geocoding service, handles timezone conversions properly, calculates planetary aspects and transits, generates rich symbolic interpretations of astrological data, constructs sophisticated prompts that guide the language model toward appropriate responses, and provides an interactive command-line interface with comprehensive error handling.
To use this system, you would install the required dependencies including swisseph for astrological calculations, pytz and timezonefinder for timezone handling, requests for geocoding, and either llama-cpp-python for local models or the openai and anthropic libraries for remote APIs. You would also need to download Swiss Ephemeris data files and place them in an accessible location. For local models, you would need a GGUF format model file. For remote APIs, you would need valid API keys set in environment variables.
The system can be invoked from the command line with various options to specify whether to use local or remote models, which model or API provider to use, and where to find necessary data files. Once running, it presents an interactive interface where users can ask questions, optionally provide birth data, and receive metaphorical responses that incorporate both the astrological context and the specific question asked. The responses maintain the enigmatic yet meaningful style of the ancient Oracle while providing genuine insight into the user's inquiry.
####################################
INTERFACING WITH LARGE LANGUAGE MODELS ACROSS PLATFORMS
The Oracle's prophetic voice must be channeled through large language models, whether they reside locally on the user's machine or exist as remote services accessible through the internet. This section explores the technical architecture required to support both deployment models while maintaining compatibility across diverse hardware platforms including Nvidia CUDA, AMD ROCm, Intel architectures, and Apple Metal Performance Shaders.
The fundamental challenge lies in creating an abstraction layer that allows the Oracle to speak through any available model without requiring significant code changes. This abstraction must handle the peculiarities of different inference engines, manage resource allocation appropriately for the underlying hardware, and provide a consistent interface to the higher-level Oracle logic.
When working with local models, the system must first detect the available hardware acceleration capabilities. Modern deep learning frameworks provide mechanisms to query the system for GPU availability and type. The detection logic forms the foundation upon which all subsequent model loading and inference operations are built.
import torch
import platform
import subprocess
class HardwareDetector:
"""Detects available hardware acceleration for LLM inference."""
def __init__(self):
self.device_type = None
self.device_name = None
self.available_memory = None
self._detect_hardware()
def _detect_hardware(self):
"""Performs comprehensive hardware detection across platforms."""
# Check for NVIDIA CUDA support
if torch.cuda.is_available():
self.device_type = "cuda"
self.device_name = torch.cuda.get_device_name(0)
self.available_memory = torch.cuda.get_device_properties(0).total_memory
return
# Check for AMD ROCm support
if hasattr(torch.version, 'hip') and torch.version.hip is not None:
self.device_type = "rocm"
# ROCm uses CUDA-compatible API in PyTorch
if torch.cuda.is_available():
self.device_name = torch.cuda.get_device_name(0)
self.available_memory = torch.cuda.get_device_properties(0).total_memory
return
# Check for Apple Metal Performance Shaders
if platform.system() == "Darwin" and hasattr(torch.backends, 'mps'):
if torch.backends.mps.is_available():
self.device_type = "mps"
self.device_name = "Apple Silicon GPU"
# MPS does not expose memory directly, estimate based on system
self.available_memory = self._estimate_mps_memory()
return
# Check for Intel GPU support (oneAPI/IPEX)
try:
import intel_extension_for_pytorch as ipex
if ipex.xpu.is_available():
self.device_type = "xpu"
self.device_name = "Intel GPU"
self.available_memory = ipex.xpu.get_device_properties(0).total_memory
return
except ImportError:
pass
# Fallback to CPU
self.device_type = "cpu"
self.device_name = platform.processor()
self.available_memory = self._get_system_memory()
def _estimate_mps_memory(self):
"""Estimates available memory for MPS devices."""
try:
# Use sysctl to get hardware memory on macOS
result = subprocess.run(['sysctl', 'hw.memsize'],
capture_output=True, text=True)
if result.returncode == 0:
memory_bytes = int(result.stdout.split(':')[1].strip())
# Assume 70% of system memory available for GPU operations
return int(memory_bytes * 0.7)
except Exception:
pass
return 8 * 1024 * 1024 * 1024 # Default to 8GB estimate
def _get_system_memory(self):
"""Gets total system RAM for CPU inference."""
import psutil
return psutil.virtual_memory().total
def get_torch_device(self):
"""Returns the appropriate torch device string."""
if self.device_type == "cuda" or self.device_type == "rocm":
return "cuda:0"
elif self.device_type == "mps":
return "mps"
elif self.device_type == "xpu":
return "xpu:0"
else:
return "cpu"
def get_recommended_precision(self):
"""Recommends precision based on hardware capabilities."""
if self.device_type in ["cuda", "rocm"]:
# Modern NVIDIA and AMD GPUs support bfloat16 well
return "bfloat16"
elif self.device_type == "mps":
# Apple Silicon works well with float16
return "float16"
elif self.device_type == "xpu":
# Intel GPUs prefer bfloat16
return "bfloat16"
else:
# CPU inference typically uses float32
return "float32"
The hardware detector provides essential information about the computational resources available to the Oracle. This information guides subsequent decisions about model selection, quantization levels, and batch sizes. The detector encapsulates platform-specific quirks, such as the fact that AMD ROCm uses the same CUDA-compatible API in PyTorch, or that Apple's MPS backend does not directly expose memory information.
With hardware detection in place, the next challenge involves actually loading and managing language models. The ecosystem of LLM inference engines is diverse, with different tools optimizing for different use cases. For local inference, several popular options exist including Hugging Face Transformers, llama.cpp through Python bindings, and vLLM for high-throughput scenarios.
The model manager must abstract these differences while providing a unified interface. It handles model downloading, caching, loading into memory, and preparing the model for inference on the detected hardware. The manager also implements lazy loading strategies to minimize memory consumption when multiple models might be configured but only one is actively used.
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from typing import Optional, Dict, Any
import os
class LocalModelManager:
"""Manages loading and lifecycle of local LLM models."""
def __init__(self, hardware_detector: HardwareDetector):
self.hardware = hardware_detector
self.model = None
self.tokenizer = None
self.model_name = None
self.cache_dir = os.path.expanduser("~/.cache/oracle_models")
os.makedirs(self.cache_dir, exist_ok=True)
def load_model(self, model_name: str, quantization: Optional[str] = None):
"""Loads a model with appropriate configuration for detected hardware.
Args:
model_name: HuggingFace model identifier or local path
quantization: Quantization scheme (4bit, 8bit, or None)
"""
self.model_name = model_name
device = self.hardware.get_torch_device()
# Configure quantization if requested and supported
model_kwargs = {
"cache_dir": self.cache_dir,
"low_cpu_mem_usage": True,
}
if quantization == "4bit" and self.hardware.device_type in ["cuda", "rocm"]:
# 4-bit quantization using bitsandbytes (CUDA/ROCm only)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model_kwargs["quantization_config"] = bnb_config
model_kwargs["device_map"] = "auto"
elif quantization == "8bit" and self.hardware.device_type in ["cuda", "rocm"]:
# 8-bit quantization using bitsandbytes
bnb_config = BitsAndBytesConfig(load_in_8bit=True)
model_kwargs["quantization_config"] = bnb_config
model_kwargs["device_map"] = "auto"
else:
# Full precision or platform-specific precision
precision = self.hardware.get_recommended_precision()
if precision == "bfloat16":
model_kwargs["torch_dtype"] = torch.bfloat16
elif precision == "float16":
model_kwargs["torch_dtype"] = torch.float16
else:
model_kwargs["torch_dtype"] = torch.float32
# Load the model
print(f"Loading model {model_name} on {device}...")
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
**model_kwargs
)
# Move model to device if not using device_map auto
if "device_map" not in model_kwargs:
self.model = self.model.to(device)
# Enable platform-specific optimizations
if self.hardware.device_type == "xpu":
try:
import intel_extension_for_pytorch as ipex
self.model = ipex.optimize(self.model)
except ImportError:
pass
# Load tokenizer
self.tokenizer = AutoTokenizer.from_pretrained(
model_name,
cache_dir=self.cache_dir
)
# Ensure tokenizer has pad token
if self.tokenizer.pad_token is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
print(f"Model loaded successfully on {device}")
def generate(self, prompt: str, max_new_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the loaded model.
Args:
prompt: Input text prompt
max_new_tokens: Maximum number of tokens to generate
temperature: Sampling temperature for randomness
top_p: Nucleus sampling parameter
Returns:
Generated text response
"""
if self.model is None or self.tokenizer is None:
raise RuntimeError("Model not loaded. Call load_model first.")
# Tokenize input
inputs = self.tokenizer(prompt, return_tensors="pt", padding=True)
device = self.hardware.get_torch_device()
inputs = {k: v.to(device) for k, v in inputs.items()}
# Generate with appropriate parameters
with torch.no_grad():
outputs = self.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_p=top_p,
do_sample=True,
pad_token_id=self.tokenizer.pad_token_id,
eos_token_id=self.tokenizer.eos_token_id
)
# Decode and return only the generated portion
full_text = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
generated_text = full_text[len(prompt):].strip()
return generated_text
def unload_model(self):
"""Releases model from memory."""
if self.model is not None:
del self.model
self.model = None
if self.tokenizer is not None:
del self.tokenizer
self.tokenizer = None
# Clear GPU cache if applicable
if self.hardware.device_type in ["cuda", "rocm"]:
torch.cuda.empty_cache()
elif self.hardware.device_type == "mps":
torch.mps.empty_cache()
The local model manager handles the complexity of loading models across different hardware platforms. It automatically configures quantization when beneficial and supported, applies platform-specific optimizations such as Intel's IPEX extension, and manages the model lifecycle including proper cleanup to free GPU memory.
For remote model access, the Oracle must interface with various API providers including OpenAI, Anthropic, Cohere, and others. Each provider has its own API conventions, authentication mechanisms, and response formats. A remote model manager abstracts these differences behind a common interface.
import requests
import json
from typing import List, Dict
from abc import ABC, abstractmethod
class RemoteModelProvider(ABC):
"""Abstract base class for remote LLM providers."""
@abstractmethod
def generate(self, prompt: str, max_tokens: int,
temperature: float, top_p: float) -> str:
"""Generates text using the remote model."""
pass
class OpenAIProvider(RemoteModelProvider):
"""Provider for OpenAI API (GPT models)."""
def __init__(self, api_key: str, model: str = "gpt-3.5-turbo"):
self.api_key = api_key
self.model = model
self.base_url = "https://api.openai.com/v1/chat/completions"
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using OpenAI's chat completion API."""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
data = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p
}
response = requests.post(self.base_url, headers=headers,
json=data, timeout=60)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
class AnthropicProvider(RemoteModelProvider):
"""Provider for Anthropic API (Claude models)."""
def __init__(self, api_key: str, model: str = "claude-3-sonnet-20240229"):
self.api_key = api_key
self.model = model
self.base_url = "https://api.anthropic.com/v1/messages"
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using Anthropic's messages API."""
headers = {
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json"
}
data = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p
}
response = requests.post(self.base_url, headers=headers,
json=data, timeout=60)
response.raise_for_status()
result = response.json()
return result["content"][0]["text"]
class OllamaProvider(RemoteModelProvider):
"""Provider for local Ollama server."""
def __init__(self, base_url: str = "http://localhost:11434",
model: str = "llama2"):
self.base_url = base_url
self.model = model
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using Ollama's generate API."""
url = f"{self.base_url}/api/generate"
data = {
"model": self.model,
"prompt": prompt,
"stream": False,
"options": {
"num_predict": max_tokens,
"temperature": temperature,
"top_p": top_p
}
}
response = requests.post(url, json=data, timeout=120)
response.raise_for_status()
result = response.json()
return result["response"]
class RemoteModelManager:
"""Manages remote LLM providers with unified interface."""
def __init__(self):
self.providers: Dict[str, RemoteModelProvider] = {}
self.active_provider: Optional[str] = None
def register_provider(self, name: str, provider: RemoteModelProvider):
"""Registers a new remote provider.
Args:
name: Identifier for this provider
provider: Provider instance
"""
self.providers[name] = provider
if self.active_provider is None:
self.active_provider = name
def set_active_provider(self, name: str):
"""Sets the active provider for generation.
Args:
name: Name of registered provider to activate
"""
if name not in self.providers:
raise ValueError(f"Provider {name} not registered")
self.active_provider = name
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the active provider.
Args:
prompt: Input text prompt
max_tokens: Maximum tokens to generate
temperature: Sampling temperature
top_p: Nucleus sampling parameter
Returns:
Generated text response
"""
if self.active_provider is None:
raise RuntimeError("No active provider set")
provider = self.providers[self.active_provider]
return provider.generate(prompt, max_tokens, temperature, top_p)
The remote model manager provides a plugin architecture where different API providers can be registered and switched between seamlessly. This design allows the Oracle to work with any combination of local and remote models, choosing the most appropriate one based on availability, cost considerations, or specific capabilities required for a given prophecy.
With both local and remote model management in place, the Oracle requires a unified interface that can work with either backend transparently. This abstraction layer allows the higher-level Oracle logic to remain agnostic about whether it is consulting a local model running on the user's GPU or a remote API endpoint.
from enum import Enum
from typing import Optional
class ModelBackend(Enum):
"""Enumeration of supported model backends."""
LOCAL = "local"
REMOTE = "remote"
class UnifiedModelInterface:
"""Unified interface for both local and remote LLM backends."""
def __init__(self):
self.backend_type: Optional[ModelBackend] = None
self.local_manager: Optional[LocalModelManager] = None
self.remote_manager: Optional[RemoteModelManager] = None
self.hardware_detector = HardwareDetector()
def initialize_local(self, model_name: str, quantization: Optional[str] = None):
"""Initializes local model backend.
Args:
model_name: HuggingFace model identifier
quantization: Optional quantization (4bit, 8bit)
"""
self.local_manager = LocalModelManager(self.hardware_detector)
self.local_manager.load_model(model_name, quantization)
self.backend_type = ModelBackend.LOCAL
print(f"Initialized local backend with {model_name}")
def initialize_remote(self, provider_name: str, provider: RemoteModelProvider):
"""Initializes remote model backend.
Args:
provider_name: Identifier for the provider
provider: RemoteModelProvider instance
"""
if self.remote_manager is None:
self.remote_manager = RemoteModelManager()
self.remote_manager.register_provider(provider_name, provider)
self.remote_manager.set_active_provider(provider_name)
self.backend_type = ModelBackend.REMOTE
print(f"Initialized remote backend with {provider_name}")
def generate_response(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the active backend.
Args:
prompt: Input prompt
max_tokens: Maximum tokens to generate
temperature: Sampling temperature
top_p: Nucleus sampling parameter
Returns:
Generated text
"""
if self.backend_type is None:
raise RuntimeError("No backend initialized")
if self.backend_type == ModelBackend.LOCAL:
return self.local_manager.generate(
prompt, max_tokens, temperature, top_p
)
else:
return self.remote_manager.generate(
prompt, max_tokens, temperature, top_p
)
def get_backend_info(self) -> Dict[str, Any]:
"""Returns information about the active backend.
Returns:
Dictionary containing backend details
"""
info = {"backend_type": self.backend_type.value if self.backend_type else None}
if self.backend_type == ModelBackend.LOCAL:
info.update({
"model_name": self.local_manager.model_name,
"device": self.hardware_detector.device_type,
"device_name": self.hardware_detector.device_name
})
elif self.backend_type == ModelBackend.REMOTE:
info.update({
"active_provider": self.remote_manager.active_provider
})
return info
def cleanup(self):
"""Releases resources from active backend."""
if self.backend_type == ModelBackend.LOCAL and self.local_manager:
self.local_manager.unload_model()
The unified model interface provides a single point of interaction for the Oracle's prophecy generation system. It handles initialization of either local or remote backends, manages the generation process through a consistent API, and provides introspection capabilities so the Oracle can inform users about which model is currently channeling the divine wisdom.
This architecture ensures that the Oracle can adapt to different deployment scenarios. A user with a powerful GPU might run a local Llama model for complete privacy and offline operation. Another user might prefer the convenience and power of Claude or GPT-4 through remote APIs. The Oracle's core logic remains unchanged regardless of this choice.
The model interface also enables sophisticated fallback strategies. If a local model fails to load due to insufficient memory, the system could automatically fall back to a remote provider. If a remote API becomes unavailable, the Oracle could switch to a local model. These resilience patterns ensure that the Oracle remains available to seekers of wisdom even when individual components fail.
CONSTRUCTING THE ASTROLOGICAL CALCULATION ENGINE
The Oracle of Delphi must ground its prophecies in the celestial mechanics that govern the cosmos. Astrology, whether one believes in its metaphysical significance or views it as a rich symbolic system, provides a structured framework for generating personalized and contextually relevant responses. The astrological calculation engine forms the mystical heart of our Oracle, transforming birth data and current time into a detailed celestial snapshot.
The fundamental task of the astrological engine involves calculating the positions of celestial bodies at specific moments in time. This requires astronomical algorithms that can determine where the Sun, Moon, and planets appear in the sky as viewed from Earth. While professional astrological software uses highly accurate ephemerides, our Oracle can achieve sufficient precision using the Swiss Ephemeris library, which provides positions for all major celestial bodies.
The Swiss Ephemeris, developed by Astrodienst, offers a Python interface through the pyswisseph library. This library calculates planetary positions with remarkable accuracy, handling the complexities of orbital mechanics, precession, and various astrological house systems. The engine must first establish the seeker's birth chart, then calculate the current transits, and finally determine the aspects between natal and transiting planets.
import swisseph as swe
from datetime import datetime, timezone
from typing import Dict, List, Tuple, Optional
import math
class AstrologyEngine:
"""Calculates astrological data for birth charts and transits."""
# Planet identifiers in Swiss Ephemeris
PLANETS = {
'Sun': swe.SUN,
'Moon': swe.MOON,
'Mercury': swe.MERCURY,
'Venus': swe.VENUS,
'Mars': swe.MARS,
'Jupiter': swe.JUPITER,
'Saturn': swe.SATURN,
'Uranus': swe.URANUS,
'Neptune': swe.NEPTUNE,
'Pluto': swe.PLUTO
}
# Zodiac signs
SIGNS = [
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
]
# Aspect definitions (angle, orb, name, nature)
ASPECTS = [
(0, 8, 'Conjunction', 'neutral'),
(60, 6, 'Sextile', 'harmonious'),
(90, 8, 'Square', 'challenging'),
(120, 8, 'Trine', 'harmonious'),
(180, 8, 'Opposition', 'challenging')
]
def __init__(self):
"""Initializes the astrology engine."""
# Set ephemeris path (uses default if not set)
swe.set_ephe_path('')
def datetime_to_julian(self, dt: datetime) -> float:
"""Converts datetime to Julian Day Number.
Args:
dt: Datetime object (should be in UTC)
Returns:
Julian Day Number as float
"""
# Ensure datetime is in UTC
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
# Swiss Ephemeris expects Gregorian calendar
jd = swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute / 60.0 + dt.second / 3600.0)
return jd
def calculate_planet_position(self, planet_id: int,
julian_day: float) -> Dict[str, float]:
"""Calculates position of a planet at given time.
Args:
planet_id: Swiss Ephemeris planet identifier
julian_day: Julian Day Number
Returns:
Dictionary with longitude, latitude, distance, speed
"""
# Calculate position (flag 0 = Swiss Ephemeris, geocentric)
result, ret_flag = swe.calc_ut(julian_day, planet_id, swe.FLG_SWIEPH)
return {
'longitude': result[0], # Ecliptic longitude
'latitude': result[1], # Ecliptic latitude
'distance': result[2], # Distance from Earth in AU
'speed': result[3] # Speed in longitude (degrees/day)
}
def get_zodiac_sign(self, longitude: float) -> Tuple[str, float]:
"""Determines zodiac sign and degree within sign.
Args:
longitude: Ecliptic longitude in degrees (0-360)
Returns:
Tuple of (sign name, degree within sign)
"""
# Normalize longitude to 0-360 range
longitude = longitude % 360
# Each sign is 30 degrees
sign_index = int(longitude / 30)
degree_in_sign = longitude % 30
return self.SIGNS[sign_index], degree_in_sign
def calculate_houses(self, julian_day: float, latitude: float,
longitude: float, house_system: str = 'P') -> Dict:
"""Calculates astrological houses for a given location and time.
Args:
julian_day: Julian Day Number
latitude: Geographic latitude in degrees
longitude: Geographic longitude in degrees
house_system: House system code (P=Placidus, K=Koch, etc.)
Returns:
Dictionary with house cusps and angles
"""
# Calculate houses
cusps, ascmc = swe.houses(julian_day, latitude, longitude,
house_system.encode('ascii'))
return {
'cusps': list(cusps), # 12 house cusps
'ascendant': ascmc[0], # Ascendant (1st house cusp)
'mc': ascmc[1], # Midheaven (10th house cusp)
'armc': ascmc[2], # ARMC (sidereal time)
'vertex': ascmc[3], # Vertex
'equatorial_ascendant': ascmc[4],
'co_ascendant_koch': ascmc[5]
}
def calculate_aspect(self, pos1: float, pos2: float) -> Optional[Dict]:
"""Determines if two positions form an aspect.
Args:
pos1: First planet's longitude
pos2: Second planet's longitude
Returns:
Dictionary with aspect info if aspect exists, None otherwise
"""
# Calculate angular distance
diff = abs(pos1 - pos2)
if diff > 180:
diff = 360 - diff
# Check each aspect type
for angle, orb, name, nature in self.ASPECTS:
if abs(diff - angle) <= orb:
return {
'name': name,
'angle': angle,
'actual_angle': diff,
'orb': abs(diff - angle),
'nature': nature,
'applying': self._is_applying(pos1, pos2, angle)
}
return None
def _is_applying(self, pos1: float, pos2: float, aspect_angle: float) -> bool:
"""Determines if an aspect is applying or separating.
This simplified version assumes slower-moving planet is first.
A complete implementation would check actual speeds.
Args:
pos1: First planet longitude
pos2: Second planet longitude
aspect_angle: Target aspect angle
Returns:
True if aspect is applying, False if separating
"""
diff = (pos2 - pos1) % 360
return diff < aspect_angle or diff > (360 - aspect_angle)
def generate_birth_chart(self, birth_datetime: datetime,
latitude: float, longitude: float) -> Dict:
"""Generates complete birth chart data.
Args:
birth_datetime: Date and time of birth (UTC)
latitude: Birth location latitude
longitude: Birth location longitude
Returns:
Dictionary containing complete birth chart
"""
jd = self.datetime_to_julian(birth_datetime)
# Calculate all planet positions
planets = {}
for name, planet_id in self.PLANETS.items():
position = self.calculate_planet_position(planet_id, jd)
sign, degree = self.get_zodiac_sign(position['longitude'])
planets[name] = {
'longitude': position['longitude'],
'sign': sign,
'degree': degree,
'speed': position['speed'],
'retrograde': position['speed'] < 0
}
# Calculate houses
houses = self.calculate_houses(jd, latitude, longitude)
# Add Ascendant and MC to chart
asc_sign, asc_degree = self.get_zodiac_sign(houses['ascendant'])
mc_sign, mc_degree = self.get_zodiac_sign(houses['mc'])
return {
'planets': planets,
'houses': houses,
'ascendant': {
'longitude': houses['ascendant'],
'sign': asc_sign,
'degree': asc_degree
},
'midheaven': {
'longitude': houses['mc'],
'sign': mc_sign,
'degree': mc_degree
},
'birth_time': birth_datetime.isoformat(),
'location': {'latitude': latitude, 'longitude': longitude}
}
def calculate_transits(self, current_datetime: datetime) -> Dict:
"""Calculates current planetary positions (transits).
Args:
current_datetime: Current date and time (UTC)
Returns:
Dictionary of current planet positions
"""
jd = self.datetime_to_julian(current_datetime)
transits = {}
for name, planet_id in self.PLANETS.items():
position = self.calculate_planet_position(planet_id, jd)
sign, degree = self.get_zodiac_sign(position['longitude'])
transits[name] = {
'longitude': position['longitude'],
'sign': sign,
'degree': degree,
'speed': position['speed'],
'retrograde': position['speed'] < 0
}
return transits
def find_transit_aspects(self, birth_chart: Dict,
transits: Dict) -> List[Dict]:
"""Finds aspects between natal planets and transiting planets.
Args:
birth_chart: Birth chart data from generate_birth_chart
transits: Transit data from calculate_transits
Returns:
List of aspect dictionaries
"""
aspects = []
for natal_planet, natal_data in birth_chart['planets'].items():
for transit_planet, transit_data in transits.items():
aspect = self.calculate_aspect(
natal_data['longitude'],
transit_data['longitude']
)
if aspect:
aspects.append({
'natal_planet': natal_planet,
'transit_planet': transit_planet,
'aspect': aspect['name'],
'orb': aspect['orb'],
'nature': aspect['nature'],
'applying': aspect['applying']
})
return aspects
The astrology engine provides comprehensive calculation capabilities that transform astronomical data into astrologically meaningful information. It calculates planetary positions using the Swiss Ephemeris, determines zodiac signs and degrees, computes astrological houses using various house systems, and identifies aspects between planets both in the natal chart and between natal and transiting positions.
The engine handles the complexities of coordinate systems, converting between standard datetime representations and the Julian Day Numbers required by astronomical calculations. It accounts for retrograde motion by examining planetary speeds, and it determines whether aspects are applying or separating, which carries significance in astrological interpretation.
For the Oracle to provide geographically relevant prophecies, it must determine the seeker's location when not explicitly provided. Modern web services offer geolocation based on IP addresses, though this provides only approximate locations. For more precise work, the seeker can provide their coordinates directly.
import requests
from typing import Tuple, Optional
class LocationService:
"""Handles geographic location determination and timezone conversion."""
def __init__(self):
self.default_location = (51.5074, -0.1278) # London as fallback
def get_location_from_ip(self, ip_address: Optional[str] = None) -> Tuple[float, float, str]:
"""Determines location from IP address.
Args:
ip_address: IP address to lookup, None for automatic detection
Returns:
Tuple of (latitude, longitude, city_name)
"""
try:
# Use ip-api.com for geolocation (free tier, no key required)
if ip_address:
url = f"http://ip-api.com/json/{ip_address}"
else:
url = "http://ip-api.com/json/"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
if data['status'] == 'success':
return (
data['lat'],
data['lon'],
f"{data['city']}, {data['country']}"
)
except Exception as e:
print(f"Location lookup failed: {e}, using default")
return (*self.default_location, "Unknown Location")
def get_timezone_offset(self, latitude: float, longitude: float,
dt: datetime) -> int:
"""Gets timezone offset for a location at a specific time.
Args:
latitude: Location latitude
longitude: Location longitude
dt: Datetime to check
Returns:
Timezone offset in hours from UTC
"""
try:
# Use timezonefinder to get timezone name
from timezonefinder import TimezoneFinder
import pytz
tf = TimezoneFinder()
tz_name = tf.timezone_at(lat=latitude, lng=longitude)
if tz_name:
tz = pytz.timezone(tz_name)
offset = tz.utcoffset(dt).total_seconds() / 3600
return int(offset)
except Exception as e:
print(f"Timezone lookup failed: {e}")
return 0 # Default to UTC
def parse_birth_location(self, location_string: str) -> Tuple[float, float]:
"""Parses a location string into coordinates.
This is a simplified version. Production code would use
a geocoding service like OpenStreetMap Nominatim.
Args:
location_string: City name or address
Returns:
Tuple of (latitude, longitude)
"""
try:
# Use Nominatim for geocoding
url = "https://nominatim.openstreetmap.org/search"
params = {
'q': location_string,
'format': 'json',
'limit': 1
}
headers = {
'User-Agent': 'OracleOfDelphi/1.0'
}
response = requests.get(url, params=params, headers=headers, timeout=5)
response.raise_for_status()
data = response.json()
if data:
return (float(data[0]['lat']), float(data[0]['lon']))
except Exception as e:
print(f"Geocoding failed: {e}")
return self.default_location
The location service bridges the gap between the digital realm and physical geography. It translates IP addresses into approximate coordinates, handles timezone conversions necessary for accurate astronomical calculations, and can geocode location names into precise latitude and longitude values. This geographic awareness ensures that the Oracle's astrological calculations reflect the seeker's actual celestial environment.
With the astrological engine and location service in place, the Oracle can now generate rich celestial context for any query. The birth chart reveals the seeker's fundamental cosmic signature, while current transits show the dynamic celestial weather affecting them at the moment of inquiry. The aspects between natal and transiting planets highlight specific areas of tension, opportunity, or transformation.
CRAFTING THE PROPHETIC VOICE
The Oracle's power lies not merely in calculating planetary positions or generating text, but in weaving these elements together into prophecies that resonate with the mystical ambiguity and poetic depth of the ancient Pythia. The prophetic voice module transforms astrological data and user questions into responses that honor the Delphic tradition of enigmatic wisdom.
The historical Oracle of Delphi rarely provided direct answers. Instead, the Pythia spoke in riddles, metaphors, and symbolic language that required interpretation. This approach served multiple purposes. It protected the Oracle from accusations of false prophecy when events unfolded differently than expected. It engaged the seeker in active interpretation, making them a participant in creating meaning. And it acknowledged the fundamental uncertainty and multiplicity of possible futures.
Our digital Oracle must capture this same quality. The prophetic voice generator takes structured astrological data and transforms it into metaphorical language. It identifies the most significant astrological factors relevant to the seeker's question, translates these into symbolic themes, and instructs the language model to weave these themes into a response that feels both ancient and personally meaningful.
from typing import List, Dict, Any
from datetime import datetime
class PropheticVoiceGenerator:
"""Generates prompts for the LLM that create Delphic-style prophecies."""
# Symbolic associations for planets
PLANET_SYMBOLISM = {
'Sun': {
'themes': ['identity', 'vitality', 'consciousness', 'authority', 'creative power'],
'metaphors': ['the golden chariot', 'the eternal flame', 'the sovereign']
},
'Moon': {
'themes': ['emotions', 'intuition', 'cycles', 'memory', 'the unconscious'],
'metaphors': ['the silver mirror', 'the tidal force', 'the hidden face']
},
'Mercury': {
'themes': ['communication', 'intellect', 'commerce', 'travel', 'trickery'],
'metaphors': ['the winged messenger', 'the crossroads', 'the swift tongue']
},
'Venus': {
'themes': ['love', 'beauty', 'harmony', 'values', 'attraction'],
'metaphors': ['the rose garden', 'the morning star', 'the golden apple']
},
'Mars': {
'themes': ['action', 'desire', 'conflict', 'courage', 'assertion'],
'metaphors': ['the red blade', 'the warrior', 'the forge fire']
},
'Jupiter': {
'themes': ['expansion', 'wisdom', 'abundance', 'faith', 'justice'],
'metaphors': ['the beneficent king', 'the great fortune', 'the oak tree']
},
'Saturn': {
'themes': ['structure', 'limitation', 'responsibility', 'time', 'mastery'],
'metaphors': ['the taskmaster', 'the mountain', 'the hourglass']
},
'Uranus': {
'themes': ['revolution', 'innovation', 'freedom', 'awakening', 'chaos'],
'metaphors': ['the lightning bolt', 'the rebel', 'the sudden wind']
},
'Neptune': {
'themes': ['dreams', 'illusion', 'spirituality', 'dissolution', 'compassion'],
'metaphors': ['the ocean depths', 'the veil', 'the mystic']
},
'Pluto': {
'themes': ['transformation', 'power', 'death and rebirth', 'the shadow', 'intensity'],
'metaphors': ['the underworld', 'the phoenix', 'the hidden treasure']
}
}
# Symbolic associations for zodiac signs
SIGN_SYMBOLISM = {
'Aries': {'element': 'fire', 'quality': 'cardinal', 'symbol': 'the ram',
'themes': ['initiation', 'courage', 'independence']},
'Taurus': {'element': 'earth', 'quality': 'fixed', 'symbol': 'the bull',
'themes': ['stability', 'sensuality', 'persistence']},
'Gemini': {'element': 'air', 'quality': 'mutable', 'symbol': 'the twins',
'themes': ['duality', 'communication', 'versatility']},
'Cancer': {'element': 'water', 'quality': 'cardinal', 'symbol': 'the crab',
'themes': ['nurturing', 'protection', 'emotion']},
'Leo': {'element': 'fire', 'quality': 'fixed', 'symbol': 'the lion',
'themes': ['creativity', 'leadership', 'pride']},
'Virgo': {'element': 'earth', 'quality': 'mutable', 'symbol': 'the maiden',
'themes': ['analysis', 'service', 'perfection']},
'Libra': {'element': 'air', 'quality': 'cardinal', 'symbol': 'the scales',
'themes': ['balance', 'partnership', 'justice']},
'Scorpio': {'element': 'water', 'quality': 'fixed', 'symbol': 'the scorpion',
'themes': ['transformation', 'intensity', 'mystery']},
'Sagittarius': {'element': 'fire', 'quality': 'mutable', 'symbol': 'the archer',
'themes': ['exploration', 'philosophy', 'freedom']},
'Capricorn': {'element': 'earth', 'quality': 'cardinal', 'symbol': 'the goat',
'themes': ['ambition', 'discipline', 'achievement']},
'Aquarius': {'element': 'air', 'quality': 'fixed', 'symbol': 'the water bearer',
'themes': ['innovation', 'community', 'idealism']},
'Pisces': {'element': 'water', 'quality': 'mutable', 'symbol': 'the fish',
'themes': ['compassion', 'imagination', 'transcendence']}
}
# Aspect interpretations
ASPECT_MEANINGS = {
'Conjunction': 'merging of energies, intensification, new beginnings',
'Sextile': 'opportunity, ease, harmonious flow',
'Square': 'tension, challenge, dynamic action required',
'Trine': 'natural talent, grace, fortunate circumstances',
'Opposition': 'awareness through contrast, need for balance, culmination'
}
def __init__(self):
"""Initializes the prophetic voice generator."""
pass
def analyze_astrological_significance(self, birth_chart: Dict,
transits: Dict,
aspects: List[Dict]) -> Dict[str, Any]:
"""Analyzes astrological data to identify most significant factors.
Args:
birth_chart: Birth chart data
transits: Current transit data
aspects: List of transit-to-natal aspects
Returns:
Dictionary of significant astrological factors
"""
significance = {
'major_aspects': [],
'emphasized_planets': [],
'emphasized_signs': [],
'dominant_themes': []
}
# Identify major aspects (tight orbs, outer planets involved)
for aspect in aspects:
if aspect['orb'] < 3: # Tight orb
# Outer planets carry more weight
outer_planets = ['Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']
if (aspect['transit_planet'] in outer_planets or
aspect['natal_planet'] in outer_planets):
significance['major_aspects'].append(aspect)
# Sort by orb (tightest first)
significance['major_aspects'].sort(key=lambda x: x['orb'])
# Identify emphasized planets (involved in multiple aspects)
planet_counts = {}
for aspect in aspects:
planet_counts[aspect['natal_planet']] = planet_counts.get(aspect['natal_planet'], 0) + 1
planet_counts[aspect['transit_planet']] = planet_counts.get(aspect['transit_planet'], 0) + 1
# Get top 3 most aspected planets
sorted_planets = sorted(planet_counts.items(), key=lambda x: x[1], reverse=True)
significance['emphasized_planets'] = [p[0] for p in sorted_planets[:3]]
# Identify emphasized signs (multiple planets transiting or natal)
sign_counts = {}
for planet_data in birth_chart['planets'].values():
sign = planet_data['sign']
sign_counts[sign] = sign_counts.get(sign, 0) + 1
for planet_data in transits.values():
sign = planet_data['sign']
sign_counts[sign] = sign_counts.get(sign, 0) + 1
sorted_signs = sorted(sign_counts.items(), key=lambda x: x[1], reverse=True)
significance['emphasized_signs'] = [s[0] for s in sorted_signs[:2]]
# Extract dominant themes from emphasized planets and signs
themes = set()
for planet in significance['emphasized_planets']:
if planet in self.PLANET_SYMBOLISM:
themes.update(self.PLANET_SYMBOLISM[planet]['themes'][:2])
for sign in significance['emphasized_signs']:
if sign in self.SIGN_SYMBOLISM:
themes.update(self.SIGN_SYMBOLISM[sign]['themes'][:2])
significance['dominant_themes'] = list(themes)
return significance
def generate_symbolic_context(self, significance: Dict[str, Any]) -> str:
"""Generates symbolic context description for the LLM prompt.
Args:
significance: Astrological significance data
Returns:
String describing symbolic context
"""
context_parts = []
# Describe major aspects using metaphorical language
for aspect in significance['major_aspects'][:3]: # Top 3 aspects
natal_planet = aspect['natal_planet']
transit_planet = aspect['transit_planet']
aspect_type = aspect['aspect']
if natal_planet in self.PLANET_SYMBOLISM and transit_planet in self.PLANET_SYMBOLISM:
natal_metaphor = self.PLANET_SYMBOLISM[natal_planet]['metaphors'][0]
transit_metaphor = self.PLANET_SYMBOLISM[transit_planet]['metaphors'][0]
aspect_meaning = self.ASPECT_MEANINGS.get(aspect_type, 'interaction')
context_parts.append(
f"{transit_metaphor} forms a {aspect_type.lower()} with {natal_metaphor}, "
f"signifying {aspect_meaning}"
)
# Describe emphasized themes
if significance['dominant_themes']:
themes_str = ', '.join(significance['dominant_themes'][:4])
context_parts.append(f"The dominant themes are {themes_str}")
# Describe emphasized signs
if significance['emphasized_signs']:
signs_str = ' and '.join(significance['emphasized_signs'])
context_parts.append(f"The signs of {signs_str} hold particular power")
return '. '.join(context_parts) + '.'
def create_oracle_prompt(self, user_question: str, birth_chart: Dict,
transits: Dict, aspects: List[Dict],
user_name: Optional[str] = None) -> str:
"""Creates the complete prompt for the LLM to generate a prophecy.
Args:
user_question: The seeker's question
birth_chart: Birth chart data
transits: Current transits
aspects: Transit-to-natal aspects
user_name: Optional name of the seeker
Returns:
Complete prompt string for the LLM
"""
# Analyze astrological significance
significance = self.analyze_astrological_significance(
birth_chart, transits, aspects
)
# Generate symbolic context
symbolic_context = self.generate_symbolic_context(significance)
# Build the prompt
seeker_address = f"O {user_name}" if user_name else "O seeker"
prompt = f"""You are the Oracle of Delphi, the ancient Pythia who speaks the wisdom of Apollo.
A seeker has come to your temple with a question. You must answer in the style of the ancient Oracle:
metaphorical, enigmatic, poetic, and profound. Do not give direct advice. Instead, speak in symbols,
riddles, and imagery that the seeker must interpret. Draw upon the celestial signs and the cosmic patterns.
The seeker's question: "{user_question}"
The celestial configuration at this moment reveals: {symbolic_context}
{seeker_address}, the Oracle speaks:"""
return prompt
def create_simple_oracle_prompt(self, user_question: str) -> str:
"""Creates a prophecy prompt without astrological data.
For cases where birth data is not provided.
Args:
user_question: The seeker's question
Returns:
Prompt string for the LLM
"""
prompt = f"""You are the Oracle of Delphi, the ancient Pythia who speaks the wisdom of Apollo.
A seeker has come to your temple with a question. You must answer in the style of the ancient Oracle:
metaphorical, enigmatic, poetic, and profound. Do not give direct advice. Instead, speak in symbols,
riddles, and imagery that the seeker must interpret.
The seeker's question: "{user_question}"
O seeker, the Oracle speaks:"""
return prompt
The prophetic voice generator serves as the bridge between cold astronomical calculations and warm human meaning. It analyzes the astrological data to identify the most significant factors, those planetary configurations most likely to resonate with the seeker's current life situation. It then translates these factors into symbolic language, drawing on traditional astrological correspondences but expressing them through metaphor rather than technical jargon.
The generator creates prompts that instruct the language model to adopt the voice of the ancient Pythia. These prompts include the seeker's question, the symbolic interpretation of their astrological situation, and explicit instructions about the style and tone of the response. The language model, with its vast training on mythological and poetic texts, can then generate responses that feel authentically oracular.
The system supports both astrologically-informed prophecies and simpler responses for seekers who do not provide birth data. This flexibility ensures that the Oracle remains accessible to all who seek wisdom, regardless of whether they know their birth time or believe in astrology. The core value lies not in astrological accuracy but in the reflective process the Oracle facilitates.
ORCHESTRATING THE COMPLETE ORACLE SYSTEM
With all components developed, the final task involves orchestrating these pieces into a cohesive system that can receive questions, process them through the astrological and linguistic engines, and deliver prophecies. The Oracle coordinator manages the workflow, handles errors gracefully, and provides a clean interface for users to interact with the system.
The coordinator must handle several scenarios. A user might provide complete birth data including date, time, and location. They might provide only a birth date without time. They might provide no birth data at all. The system should gracefully adapt to each scenario, using whatever information is available to enrich the prophecy while still functioning when data is minimal.
from typing import Optional
from datetime import datetime
import json
class OracleCoordinator:
"""Coordinates all components to deliver complete prophecies."""
def __init__(self, model_interface: UnifiedModelInterface):
"""Initializes the Oracle coordinator.
Args:
model_interface: Initialized model interface (local or remote)
"""
self.model = model_interface
self.astrology = AstrologyEngine()
self.location_service = LocationService()
self.voice_generator = PropheticVoiceGenerator()
self.default_location = (51.5074, -0.1278) # London
def consult_oracle(self, question: str,
birth_date: Optional[str] = None,
birth_time: Optional[str] = None,
birth_location: Optional[str] = None,
user_name: Optional[str] = None,
current_location: Optional[str] = None) -> Dict[str, Any]:
"""Consults the Oracle with a question and optional birth data.
Args:
question: The seeker's question
birth_date: Birth date in YYYY-MM-DD format
birth_time: Birth time in HH:MM format (24-hour)
birth_location: Birth location (city name or coordinates)
user_name: Optional name of the seeker
current_location: Current location for transit calculations
Returns:
Dictionary containing prophecy and metadata
"""
result = {
'question': question,
'prophecy': '',
'astrological_data': None,
'timestamp': datetime.now(timezone.utc).isoformat(),
'model_info': self.model.get_backend_info()
}
try:
# Determine if we have enough data for astrological analysis
has_birth_data = birth_date is not None
if has_birth_data:
# Parse birth data and calculate chart
birth_chart, transits, aspects = self._calculate_astrological_context(
birth_date, birth_time, birth_location, current_location
)
result['astrological_data'] = {
'birth_chart': birth_chart,
'transits': transits,
'aspects': aspects
}
# Generate astrologically-informed prompt
prompt = self.voice_generator.create_oracle_prompt(
question, birth_chart, transits, aspects, user_name
)
else:
# Generate simple prompt without astrology
prompt = self.voice_generator.create_simple_oracle_prompt(question)
# Generate prophecy using the model
prophecy = self.model.generate_response(
prompt,
max_tokens=400,
temperature=0.8, # Higher temperature for more creative responses
top_p=0.9
)
result['prophecy'] = prophecy.strip()
result['success'] = True
except Exception as e:
result['success'] = False
result['error'] = str(e)
result['prophecy'] = (
"The Oracle's vision is clouded. The sacred vapors do not rise. "
"Return when the stars are more favorable."
)
return result
def _calculate_astrological_context(self, birth_date: str,
birth_time: Optional[str],
birth_location: Optional[str],
current_location: Optional[str]) -> tuple:
"""Calculates complete astrological context.
Args:
birth_date: Birth date string
birth_time: Optional birth time string
birth_location: Optional birth location
current_location: Optional current location
Returns:
Tuple of (birth_chart, transits, aspects)
"""
# Parse birth date
birth_dt = datetime.strptime(birth_date, '%Y-%m-%d')
# Add birth time if provided, otherwise use noon
if birth_time:
time_parts = birth_time.split(':')
birth_dt = birth_dt.replace(
hour=int(time_parts[0]),
minute=int(time_parts[1])
)
else:
birth_dt = birth_dt.replace(hour=12, minute=0)
# Determine birth location coordinates
if birth_location:
birth_lat, birth_lon = self.location_service.parse_birth_location(birth_location)
else:
birth_lat, birth_lon = self.default_location
# Convert to UTC if necessary
if birth_time:
tz_offset = self.location_service.get_timezone_offset(
birth_lat, birth_lon, birth_dt
)
birth_dt = birth_dt.replace(tzinfo=timezone.utc) - timedelta(hours=tz_offset)
else:
birth_dt = birth_dt.replace(tzinfo=timezone.utc)
# Calculate birth chart
birth_chart = self.astrology.generate_birth_chart(
birth_dt, birth_lat, birth_lon
)
# Calculate current transits
current_dt = datetime.now(timezone.utc)
transits = self.astrology.calculate_transits(current_dt)
# Find aspects between transits and natal planets
aspects = self.astrology.find_transit_aspects(birth_chart, transits)
return birth_chart, transits, aspects
def format_prophecy_for_display(self, result: Dict[str, Any]) -> str:
"""Formats prophecy result for human-readable display.
Args:
result: Result dictionary from consult_oracle
Returns:
Formatted string for display
"""
output = []
output.append("=" * 70)
output.append("THE ORACLE OF DELPHI SPEAKS")
output.append("=" * 70)
output.append("")
output.append(f"Question: {result['question']}")
output.append("")
output.append("Prophecy:")
output.append("-" * 70)
output.append(result['prophecy'])
output.append("-" * 70)
output.append("")
if result.get('astrological_data'):
output.append("Celestial Configuration:")
astro_data = result['astrological_data']
# Show key natal placements
birth_chart = astro_data['birth_chart']
output.append(f" Sun in {birth_chart['planets']['Sun']['sign']}")
output.append(f" Moon in {birth_chart['planets']['Moon']['sign']}")
output.append(f" Ascendant in {birth_chart['ascendant']['sign']}")
# Show significant transits
if astro_data['aspects']:
output.append("")
output.append(" Significant Transits:")
for aspect in astro_data['aspects'][:3]:
output.append(
f" {aspect['transit_planet']} {aspect['aspect']} "
f"natal {aspect['natal_planet']}"
)
output.append("")
output.append(f"Consulted: {result['timestamp']}")
output.append("=" * 70)
return '\n'.join(output)
The Oracle coordinator brings together all the pieces into a working system. It handles the parsing of user input, determines what level of astrological analysis is possible given the provided data, calculates the necessary astronomical positions, generates appropriate prompts for the language model, and formats the results for presentation.
The coordinator implements graceful degradation. If complete birth data is available, it performs full astrological analysis. If only a birth date is provided, it uses a noon chart. If no birth data exists, it generates a prophecy based solely on the question and the Oracle's general wisdom. This flexibility ensures the system remains useful across a wide range of scenarios.
Error handling is crucial for a production system. The coordinator wraps the entire consultation process in exception handling, ensuring that even if something goes wrong in the astrological calculations or model inference, the user receives a response rather than a crash. The fallback prophecy maintains the Oracle's voice while acknowledging the technical difficulty.
COMPLETE RUNNING EXAMPLE
The following complete implementation brings together all components into a production-ready Oracle system. This code supports both local and remote language models, handles astrological calculations across all major platforms, and provides a command-line interface for consulting the Oracle.
#!/usr/bin/env python3
"""
Oracle of Delphi - A Complete Implementation
Supports local and remote LLMs with cross-platform GPU acceleration
"""
import torch
import swisseph as swe
import requests
import platform
import subprocess
import os
import json
import argparse
from datetime import datetime, timezone, timedelta
from typing import Dict, List, Tuple, Optional, Any
from abc import ABC, abstractmethod
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import warnings
warnings.filterwarnings('ignore')
class HardwareDetector:
"""Detects available hardware acceleration for LLM inference."""
def __init__(self):
self.device_type = None
self.device_name = None
self.available_memory = None
self._detect_hardware()
def _detect_hardware(self):
"""Performs comprehensive hardware detection across platforms."""
if torch.cuda.is_available():
self.device_type = "cuda"
self.device_name = torch.cuda.get_device_name(0)
self.available_memory = torch.cuda.get_device_properties(0).total_memory
return
if hasattr(torch.version, 'hip') and torch.version.hip is not None:
self.device_type = "rocm"
if torch.cuda.is_available():
self.device_name = torch.cuda.get_device_name(0)
self.available_memory = torch.cuda.get_device_properties(0).total_memory
return
if platform.system() == "Darwin" and hasattr(torch.backends, 'mps'):
if torch.backends.mps.is_available():
self.device_type = "mps"
self.device_name = "Apple Silicon GPU"
self.available_memory = self._estimate_mps_memory()
return
try:
import intel_extension_for_pytorch as ipex
if ipex.xpu.is_available():
self.device_type = "xpu"
self.device_name = "Intel GPU"
self.available_memory = ipex.xpu.get_device_properties(0).total_memory
return
except ImportError:
pass
self.device_type = "cpu"
self.device_name = platform.processor()
self.available_memory = self._get_system_memory()
def _estimate_mps_memory(self):
"""Estimates available memory for MPS devices."""
try:
result = subprocess.run(['sysctl', 'hw.memsize'],
capture_output=True, text=True)
if result.returncode == 0:
memory_bytes = int(result.stdout.split(':')[1].strip())
return int(memory_bytes * 0.7)
except Exception:
pass
return 8 * 1024 * 1024 * 1024
def _get_system_memory(self):
"""Gets total system RAM for CPU inference."""
try:
import psutil
return psutil.virtual_memory().total
except ImportError:
return 16 * 1024 * 1024 * 1024
def get_torch_device(self):
"""Returns the appropriate torch device string."""
if self.device_type == "cuda" or self.device_type == "rocm":
return "cuda:0"
elif self.device_type == "mps":
return "mps"
elif self.device_type == "xpu":
return "xpu:0"
else:
return "cpu"
def get_recommended_precision(self):
"""Recommends precision based on hardware capabilities."""
if self.device_type in ["cuda", "rocm"]:
return "bfloat16"
elif self.device_type == "mps":
return "float16"
elif self.device_type == "xpu":
return "bfloat16"
else:
return "float32"
class LocalModelManager:
"""Manages loading and lifecycle of local LLM models."""
def __init__(self, hardware_detector: HardwareDetector):
self.hardware = hardware_detector
self.model = None
self.tokenizer = None
self.model_name = None
self.cache_dir = os.path.expanduser("~/.cache/oracle_models")
os.makedirs(self.cache_dir, exist_ok=True)
def load_model(self, model_name: str, quantization: Optional[str] = None):
"""Loads a model with appropriate configuration for detected hardware."""
self.model_name = model_name
device = self.hardware.get_torch_device()
model_kwargs = {
"cache_dir": self.cache_dir,
"low_cpu_mem_usage": True,
}
if quantization == "4bit" and self.hardware.device_type in ["cuda", "rocm"]:
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model_kwargs["quantization_config"] = bnb_config
model_kwargs["device_map"] = "auto"
elif quantization == "8bit" and self.hardware.device_type in ["cuda", "rocm"]:
bnb_config = BitsAndBytesConfig(load_in_8bit=True)
model_kwargs["quantization_config"] = bnb_config
model_kwargs["device_map"] = "auto"
else:
precision = self.hardware.get_recommended_precision()
if precision == "bfloat16":
model_kwargs["torch_dtype"] = torch.bfloat16
elif precision == "float16":
model_kwargs["torch_dtype"] = torch.float16
else:
model_kwargs["torch_dtype"] = torch.float32
print(f"Loading model {model_name} on {device}...")
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
**model_kwargs
)
if "device_map" not in model_kwargs:
self.model = self.model.to(device)
if self.hardware.device_type == "xpu":
try:
import intel_extension_for_pytorch as ipex
self.model = ipex.optimize(self.model)
except ImportError:
pass
self.tokenizer = AutoTokenizer.from_pretrained(
model_name,
cache_dir=self.cache_dir
)
if self.tokenizer.pad_token is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
print(f"Model loaded successfully on {device}")
def generate(self, prompt: str, max_new_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the loaded model."""
if self.model is None or self.tokenizer is None:
raise RuntimeError("Model not loaded. Call load_model first.")
inputs = self.tokenizer(prompt, return_tensors="pt", padding=True)
device = self.hardware.get_torch_device()
inputs = {k: v.to(device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_p=top_p,
do_sample=True,
pad_token_id=self.tokenizer.pad_token_id,
eos_token_id=self.tokenizer.eos_token_id
)
full_text = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
generated_text = full_text[len(prompt):].strip()
return generated_text
def unload_model(self):
"""Releases model from memory."""
if self.model is not None:
del self.model
self.model = None
if self.tokenizer is not None:
del self.tokenizer
self.tokenizer = None
if self.hardware.device_type in ["cuda", "rocm"]:
torch.cuda.empty_cache()
elif self.hardware.device_type == "mps":
torch.mps.empty_cache()
class RemoteModelProvider(ABC):
"""Abstract base class for remote LLM providers."""
@abstractmethod
def generate(self, prompt: str, max_tokens: int,
temperature: float, top_p: float) -> str:
"""Generates text using the remote model."""
pass
class OpenAIProvider(RemoteModelProvider):
"""Provider for OpenAI API (GPT models)."""
def __init__(self, api_key: str, model: str = "gpt-3.5-turbo"):
self.api_key = api_key
self.model = model
self.base_url = "https://api.openai.com/v1/chat/completions"
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using OpenAI's chat completion API."""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
data = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p
}
response = requests.post(self.base_url, headers=headers,
json=data, timeout=60)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
class AnthropicProvider(RemoteModelProvider):
"""Provider for Anthropic API (Claude models)."""
def __init__(self, api_key: str, model: str = "claude-3-sonnet-20240229"):
self.api_key = api_key
self.model = model
self.base_url = "https://api.anthropic.com/v1/messages"
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using Anthropic's messages API."""
headers = {
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json"
}
data = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p
}
response = requests.post(self.base_url, headers=headers,
json=data, timeout=60)
response.raise_for_status()
result = response.json()
return result["content"][0]["text"]
class OllamaProvider(RemoteModelProvider):
"""Provider for local Ollama server."""
def __init__(self, base_url: str = "http://localhost:11434",
model: str = "llama2"):
self.base_url = base_url
self.model = model
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using Ollama's generate API."""
url = f"{self.base_url}/api/generate"
data = {
"model": self.model,
"prompt": prompt,
"stream": False,
"options": {
"num_predict": max_tokens,
"temperature": temperature,
"top_p": top_p
}
}
response = requests.post(url, json=data, timeout=120)
response.raise_for_status()
result = response.json()
return result["response"]
class RemoteModelManager:
"""Manages remote LLM providers with unified interface."""
def __init__(self):
self.providers: Dict[str, RemoteModelProvider] = {}
self.active_provider: Optional[str] = None
def register_provider(self, name: str, provider: RemoteModelProvider):
"""Registers a new remote provider."""
self.providers[name] = provider
if self.active_provider is None:
self.active_provider = name
def set_active_provider(self, name: str):
"""Sets the active provider for generation."""
if name not in self.providers:
raise ValueError(f"Provider {name} not registered")
self.active_provider = name
def generate(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the active provider."""
if self.active_provider is None:
raise RuntimeError("No active provider set")
provider = self.providers[self.active_provider]
return provider.generate(prompt, max_tokens, temperature, top_p)
class ModelBackend:
"""Enumeration of supported model backends."""
LOCAL = "local"
REMOTE = "remote"
class UnifiedModelInterface:
"""Unified interface for both local and remote LLM backends."""
def __init__(self):
self.backend_type: Optional[str] = None
self.local_manager: Optional[LocalModelManager] = None
self.remote_manager: Optional[RemoteModelManager] = None
self.hardware_detector = HardwareDetector()
def initialize_local(self, model_name: str, quantization: Optional[str] = None):
"""Initializes local model backend."""
self.local_manager = LocalModelManager(self.hardware_detector)
self.local_manager.load_model(model_name, quantization)
self.backend_type = ModelBackend.LOCAL
print(f"Initialized local backend with {model_name}")
def initialize_remote(self, provider_name: str, provider: RemoteModelProvider):
"""Initializes remote model backend."""
if self.remote_manager is None:
self.remote_manager = RemoteModelManager()
self.remote_manager.register_provider(provider_name, provider)
self.remote_manager.set_active_provider(provider_name)
self.backend_type = ModelBackend.REMOTE
print(f"Initialized remote backend with {provider_name}")
def generate_response(self, prompt: str, max_tokens: int = 512,
temperature: float = 0.7, top_p: float = 0.9) -> str:
"""Generates text using the active backend."""
if self.backend_type is None:
raise RuntimeError("No backend initialized")
if self.backend_type == ModelBackend.LOCAL:
return self.local_manager.generate(
prompt, max_tokens, temperature, top_p
)
else:
return self.remote_manager.generate(
prompt, max_tokens, temperature, top_p
)
def get_backend_info(self) -> Dict[str, Any]:
"""Returns information about the active backend."""
info = {"backend_type": self.backend_type if self.backend_type else None}
if self.backend_type == ModelBackend.LOCAL:
info.update({
"model_name": self.local_manager.model_name,
"device": self.hardware_detector.device_type,
"device_name": self.hardware_detector.device_name
})
elif self.backend_type == ModelBackend.REMOTE:
info.update({
"active_provider": self.remote_manager.active_provider
})
return info
def cleanup(self):
"""Releases resources from active backend."""
if self.backend_type == ModelBackend.LOCAL and self.local_manager:
self.local_manager.unload_model()
class AstrologyEngine:
"""Calculates astrological data for birth charts and transits."""
PLANETS = {
'Sun': swe.SUN,
'Moon': swe.MOON,
'Mercury': swe.MERCURY,
'Venus': swe.VENUS,
'Mars': swe.MARS,
'Jupiter': swe.JUPITER,
'Saturn': swe.SATURN,
'Uranus': swe.URANUS,
'Neptune': swe.NEPTUNE,
'Pluto': swe.PLUTO
}
SIGNS = [
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
]
ASPECTS = [
(0, 8, 'Conjunction', 'neutral'),
(60, 6, 'Sextile', 'harmonious'),
(90, 8, 'Square', 'challenging'),
(120, 8, 'Trine', 'harmonious'),
(180, 8, 'Opposition', 'challenging')
]
def __init__(self):
"""Initializes the astrology engine."""
swe.set_ephe_path('')
def datetime_to_julian(self, dt: datetime) -> float:
"""Converts datetime to Julian Day Number."""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
jd = swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute / 60.0 + dt.second / 3600.0)
return jd
def calculate_planet_position(self, planet_id: int,
julian_day: float) -> Dict[str, float]:
"""Calculates position of a planet at given time."""
result, ret_flag = swe.calc_ut(julian_day, planet_id, swe.FLG_SWIEPH)
return {
'longitude': result[0],
'latitude': result[1],
'distance': result[2],
'speed': result[3]
}
def get_zodiac_sign(self, longitude: float) -> Tuple[str, float]:
"""Determines zodiac sign and degree within sign."""
longitude = longitude % 360
sign_index = int(longitude / 30)
degree_in_sign = longitude % 30
return self.SIGNS[sign_index], degree_in_sign
def calculate_houses(self, julian_day: float, latitude: float,
longitude: float, house_system: str = 'P') -> Dict:
"""Calculates astrological houses for a given location and time."""
cusps, ascmc = swe.houses(julian_day, latitude, longitude,
house_system.encode('ascii'))
return {
'cusps': list(cusps),
'ascendant': ascmc[0],
'mc': ascmc[1],
'armc': ascmc[2],
'vertex': ascmc[3],
'equatorial_ascendant': ascmc[4],
'co_ascendant_koch': ascmc[5]
}
def calculate_aspect(self, pos1: float, pos2: float) -> Optional[Dict]:
"""Determines if two positions form an aspect."""
diff = abs(pos1 - pos2)
if diff > 180:
diff = 360 - diff
for angle, orb, name, nature in self.ASPECTS:
if abs(diff - angle) <= orb:
return {
'name': name,
'angle': angle,
'actual_angle': diff,
'orb': abs(diff - angle),
'nature': nature,
'applying': self._is_applying(pos1, pos2, angle)
}
return None
def _is_applying(self, pos1: float, pos2: float, aspect_angle: float) -> bool:
"""Determines if an aspect is applying or separating."""
diff = (pos2 - pos1) % 360
return diff < aspect_angle or diff > (360 - aspect_angle)
def generate_birth_chart(self, birth_datetime: datetime,
latitude: float, longitude: float) -> Dict:
"""Generates complete birth chart data."""
jd = self.datetime_to_julian(birth_datetime)
planets = {}
for name, planet_id in self.PLANETS.items():
position = self.calculate_planet_position(planet_id, jd)
sign, degree = self.get_zodiac_sign(position['longitude'])
planets[name] = {
'longitude': position['longitude'],
'sign': sign,
'degree': degree,
'speed': position['speed'],
'retrograde': position['speed'] < 0
}
houses = self.calculate_houses(jd, latitude, longitude)
asc_sign, asc_degree = self.get_zodiac_sign(houses['ascendant'])
mc_sign, mc_degree = self.get_zodiac_sign(houses['mc'])
return {
'planets': planets,
'houses': houses,
'ascendant': {
'longitude': houses['ascendant'],
'sign': asc_sign,
'degree': asc_degree
},
'midheaven': {
'longitude': houses['mc'],
'sign': mc_sign,
'degree': mc_degree
},
'birth_time': birth_datetime.isoformat(),
'location': {'latitude': latitude, 'longitude': longitude}
}
def calculate_transits(self, current_datetime: datetime) -> Dict:
"""Calculates current planetary positions (transits)."""
jd = self.datetime_to_julian(current_datetime)
transits = {}
for name, planet_id in self.PLANETS.items():
position = self.calculate_planet_position(planet_id, jd)
sign, degree = self.get_zodiac_sign(position['longitude'])
transits[name] = {
'longitude': position['longitude'],
'sign': sign,
'degree': degree,
'speed': position['speed'],
'retrograde': position['speed'] < 0
}
return transits
def find_transit_aspects(self, birth_chart: Dict,
transits: Dict) -> List[Dict]:
"""Finds aspects between natal planets and transiting planets."""
aspects = []
for natal_planet, natal_data in birth_chart['planets'].items():
for transit_planet, transit_data in transits.items():
aspect = self.calculate_aspect(
natal_data['longitude'],
transit_data['longitude']
)
if aspect:
aspects.append({
'natal_planet': natal_planet,
'transit_planet': transit_planet,
'aspect': aspect['name'],
'orb': aspect['orb'],
'nature': aspect['nature'],
'applying': aspect['applying']
})
return aspects
class LocationService:
"""Handles geographic location determination and timezone conversion."""
def __init__(self):
self.default_location = (51.5074, -0.1278)
def get_location_from_ip(self, ip_address: Optional[str] = None) -> Tuple[float, float, str]:
"""Determines location from IP address."""
try:
if ip_address:
url = f"http://ip-api.com/json/{ip_address}"
else:
url = "http://ip-api.com/json/"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
if data['status'] == 'success':
return (
data['lat'],
data['lon'],
f"{data['city']}, {data['country']}"
)
except Exception as e:
print(f"Location lookup failed: {e}, using default")
return (*self.default_location, "Unknown Location")
def get_timezone_offset(self, latitude: float, longitude: float,
dt: datetime) -> int:
"""Gets timezone offset for a location at a specific time."""
try:
from timezonefinder import TimezoneFinder
import pytz
tf = TimezoneFinder()
tz_name = tf.timezone_at(lat=latitude, lng=longitude)
if tz_name:
tz = pytz.timezone(tz_name)
offset = tz.utcoffset(dt).total_seconds() / 3600
return int(offset)
except Exception as e:
print(f"Timezone lookup failed: {e}")
return 0
def parse_birth_location(self, location_string: str) -> Tuple[float, float]:
"""Parses a location string into coordinates."""
try:
url = "https://nominatim.openstreetmap.org/search"
params = {
'q': location_string,
'format': 'json',
'limit': 1
}
headers = {
'User-Agent': 'OracleOfDelphi/1.0'
}
response = requests.get(url, params=params, headers=headers, timeout=5)
response.raise_for_status()
data = response.json()
if data:
return (float(data[0]['lat']), float(data[0]['lon']))
except Exception as e:
print(f"Geocoding failed: {e}")
return self.default_location
class PropheticVoiceGenerator:
"""Generates prompts for the LLM that create Delphic-style prophecies."""
PLANET_SYMBOLISM = {
'Sun': {
'themes': ['identity', 'vitality', 'consciousness', 'authority', 'creative power'],
'metaphors': ['the golden chariot', 'the eternal flame', 'the sovereign']
},
'Moon': {
'themes': ['emotions', 'intuition', 'cycles', 'memory', 'the unconscious'],
'metaphors': ['the silver mirror', 'the tidal force', 'the hidden face']
},
'Mercury': {
'themes': ['communication', 'intellect', 'commerce', 'travel', 'trickery'],
'metaphors': ['the winged messenger', 'the crossroads', 'the swift tongue']
},
'Venus': {
'themes': ['love', 'beauty', 'harmony', 'values', 'attraction'],
'metaphors': ['the rose garden', 'the morning star', 'the golden apple']
},
'Mars': {
'themes': ['action', 'desire', 'conflict', 'courage', 'assertion'],
'metaphors': ['the red blade', 'the warrior', 'the forge fire']
},
'Jupiter': {
'themes': ['expansion', 'wisdom', 'abundance', 'faith', 'justice'],
'metaphors': ['the beneficent king', 'the great fortune', 'the oak tree']
},
'Saturn': {
'themes': ['structure', 'limitation', 'responsibility', 'time', 'mastery'],
'metaphors': ['the taskmaster', 'the mountain', 'the hourglass']
},
'Uranus': {
'themes': ['revolution', 'innovation', 'freedom', 'awakening', 'chaos'],
'metaphors': ['the lightning bolt', 'the rebel', 'the sudden wind']
},
'Neptune': {
'themes': ['dreams', 'illusion', 'spirituality', 'dissolution', 'compassion'],
'metaphors': ['the ocean depths', 'the veil', 'the mystic']
},
'Pluto': {
'themes': ['transformation', 'power', 'death and rebirth', 'the shadow', 'intensity'],
'metaphors': ['the underworld', 'the phoenix', 'the hidden treasure']
}
}
SIGN_SYMBOLISM = {
'Aries': {'element': 'fire', 'quality': 'cardinal', 'symbol': 'the ram',
'themes': ['initiation', 'courage', 'independence']},
'Taurus': {'element': 'earth', 'quality': 'fixed', 'symbol': 'the bull',
'themes': ['stability', 'sensuality', 'persistence']},
'Gemini': {'element': 'air', 'quality': 'mutable', 'symbol': 'the twins',
'themes': ['duality', 'communication', 'versatility']},
'Cancer': {'element': 'water', 'quality': 'cardinal', 'symbol': 'the crab',
'themes': ['nurturing', 'protection', 'emotion']},
'Leo': {'element': 'fire', 'quality': 'fixed', 'symbol': 'the lion',
'themes': ['creativity', 'leadership', 'pride']},
'Virgo': {'element': 'earth', 'quality': 'mutable', 'symbol': 'the maiden',
'themes': ['analysis', 'service', 'perfection']},
'Libra': {'element': 'air', 'quality': 'cardinal', 'symbol': 'the scales',
'themes': ['balance', 'partnership', 'justice']},
'Scorpio': {'element': 'water', 'quality': 'fixed', 'symbol': 'the scorpion',
'themes': ['transformation', 'intensity', 'mystery']},
'Sagittarius': {'element': 'fire', 'quality': 'mutable', 'symbol': 'the archer',
'themes': ['exploration', 'philosophy', 'freedom']},
'Capricorn': {'element': 'earth', 'quality': 'cardinal', 'symbol': 'the goat',
'themes': ['ambition', 'discipline', 'achievement']},
'Aquarius': {'element': 'air', 'quality': 'fixed', 'symbol': 'the water bearer',
'themes': ['innovation', 'community', 'idealism']},
'Pisces': {'element': 'water', 'quality': 'mutable', 'symbol': 'the fish',
'themes': ['compassion', 'imagination', 'transcendence']}
}
ASPECT_MEANINGS = {
'Conjunction': 'merging of energies, intensification, new beginnings',
'Sextile': 'opportunity, ease, harmonious flow',
'Square': 'tension, challenge, dynamic action required',
'Trine': 'natural talent, grace, fortunate circumstances',
'Opposition': 'awareness through contrast, need for balance, culmination'
}
def __init__(self):
"""Initializes the prophetic voice generator."""
pass
def analyze_astrological_significance(self, birth_chart: Dict,
transits: Dict,
aspects: List[Dict]) -> Dict[str, Any]:
"""Analyzes astrological data to identify most significant factors."""
significance = {
'major_aspects': [],
'emphasized_planets': [],
'emphasized_signs': [],
'dominant_themes': []
}
for aspect in aspects:
if aspect['orb'] < 3:
outer_planets = ['Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']
if (aspect['transit_planet'] in outer_planets or
aspect['natal_planet'] in outer_planets):
significance['major_aspects'].append(aspect)
significance['major_aspects'].sort(key=lambda x: x['orb'])
planet_counts = {}
for aspect in aspects:
planet_counts[aspect['natal_planet']] = planet_counts.get(aspect['natal_planet'], 0) + 1
planet_counts[aspect['transit_planet']] = planet_counts.get(aspect['transit_planet'], 0) + 1
sorted_planets = sorted(planet_counts.items(), key=lambda x: x[1], reverse=True)
significance['emphasized_planets'] = [p[0] for p in sorted_planets[:3]]
sign_counts = {}
for planet_data in birth_chart['planets'].values():
sign = planet_data['sign']
sign_counts[sign] = sign_counts.get(sign, 0) + 1
for planet_data in transits.values():
sign = planet_data['sign']
sign_counts[sign] = sign_counts.get(sign, 0) + 1
sorted_signs = sorted(sign_counts.items(), key=lambda x: x[1], reverse=True)
significance['emphasized_signs'] = [s[0] for s in sorted_signs[:2]]
themes = set()
for planet in significance['emphasized_planets']:
if planet in self.PLANET_SYMBOLISM:
themes.update(self.PLANET_SYMBOLISM[planet]['themes'][:2])
for sign in significance['emphasized_signs']:
if sign in self.SIGN_SYMBOLISM:
themes.update(self.SIGN_SYMBOLISM[sign]['themes'][:2])
significance['dominant_themes'] = list(themes)
return significance
def generate_symbolic_context(self, significance: Dict[str, Any]) -> str:
"""Generates symbolic context description for the LLM prompt."""
context_parts = []
for aspect in significance['major_aspects'][:3]:
natal_planet = aspect['natal_planet']
transit_planet = aspect['transit_planet']
aspect_type = aspect['aspect']
if natal_planet in self.PLANET_SYMBOLISM and transit_planet in self.PLANET_SYMBOLISM:
natal_metaphor = self.PLANET_SYMBOLISM[natal_planet]['metaphors'][0]
transit_metaphor = self.PLANET_SYMBOLISM[transit_planet]['metaphors'][0]
aspect_meaning = self.ASPECT_MEANINGS.get(aspect_type, 'interaction')
context_parts.append(
f"{transit_metaphor} forms a {aspect_type.lower()} with {natal_metaphor}, "
f"signifying {aspect_meaning}"
)
if significance['dominant_themes']:
themes_str = ', '.join(significance['dominant_themes'][:4])
context_parts.append(f"The dominant themes are {themes_str}")
if significance['emphasized_signs']:
signs_str = ' and '.join(significance['emphasized_signs'])
context_parts.append(f"The signs of {signs_str} hold particular power")
return '. '.join(context_parts) + '.'
def create_oracle_prompt(self, user_question: str, birth_chart: Dict,
transits: Dict, aspects: List[Dict],
user_name: Optional[str] = None) -> str:
"""Creates the complete prompt for the LLM to generate a prophecy."""
significance = self.analyze_astrological_significance(
birth_chart, transits, aspects
)
symbolic_context = self.generate_symbolic_context(significance)
seeker_address = f"O {user_name}" if user_name else "O seeker"
prompt = f"""You are the Oracle of Delphi, the ancient Pythia who speaks the wisdom of Apollo.
A seeker has come to your temple with a question. You must answer in the style of the ancient Oracle:
metaphorical, enigmatic, poetic, and profound. Do not give direct advice. Instead, speak in symbols,
riddles, and imagery that the seeker must interpret. Draw upon the celestial signs and the cosmic patterns.
The seeker's question: "{user_question}"
The celestial configuration at this moment reveals: {symbolic_context}
{seeker_address}, the Oracle speaks:"""
return prompt
def create_simple_oracle_prompt(self, user_question: str) -> str:
"""Creates a prophecy prompt without astrological data."""
prompt = f"""You are the Oracle of Delphi, the ancient Pythia who speaks the wisdom of Apollo.
A seeker has come to your temple with a question. You must answer in the style of the ancient Oracle:
metaphorical, enigmatic, poetic, and profound. Do not give direct advice. Instead, speak in symbols,
riddles, and imagery that the seeker must interpret.
The seeker's question: "{user_question}"
O seeker, the Oracle speaks:"""
return prompt
class OracleCoordinator:
"""Coordinates all components to deliver complete prophecies."""
def __init__(self, model_interface: UnifiedModelInterface):
"""Initializes the Oracle coordinator."""
self.model = model_interface
self.astrology = AstrologyEngine()
self.location_service = LocationService()
self.voice_generator = PropheticVoiceGenerator()
self.default_location = (51.5074, -0.1278)
def consult_oracle(self, question: str,
birth_date: Optional[str] = None,
birth_time: Optional[str] = None,
birth_location: Optional[str] = None,
user_name: Optional[str] = None,
current_location: Optional[str] = None) -> Dict[str, Any]:
"""Consults the Oracle with a question and optional birth data."""
result = {
'question': question,
'prophecy': '',
'astrological_data': None,
'timestamp': datetime.now(timezone.utc).isoformat(),
'model_info': self.model.get_backend_info()
}
try:
has_birth_data = birth_date is not None
if has_birth_data:
birth_chart, transits, aspects = self._calculate_astrological_context(
birth_date, birth_time, birth_location, current_location
)
result['astrological_data'] = {
'birth_chart': birth_chart,
'transits': transits,
'aspects': aspects
}
prompt = self.voice_generator.create_oracle_prompt(
question, birth_chart, transits, aspects, user_name
)
else:
prompt = self.voice_generator.create_simple_oracle_prompt(question)
prophecy = self.model.generate_response(
prompt,
max_tokens=400,
temperature=0.8,
top_p=0.9
)
result['prophecy'] = prophecy.strip()
result['success'] = True
except Exception as e:
result['success'] = False
result['error'] = str(e)
result['prophecy'] = (
"The Oracle's vision is clouded. The sacred vapors do not rise. "
"Return when the stars are more favorable."
)
return result
def _calculate_astrological_context(self, birth_date: str,
birth_time: Optional[str],
birth_location: Optional[str],
current_location: Optional[str]) -> tuple:
"""Calculates complete astrological context."""
birth_dt = datetime.strptime(birth_date, '%Y-%m-%d')
if birth_time:
time_parts = birth_time.split(':')
birth_dt = birth_dt.replace(
hour=int(time_parts[0]),
minute=int(time_parts[1])
)
else:
birth_dt = birth_dt.replace(hour=12, minute=0)
if birth_location:
birth_lat, birth_lon = self.location_service.parse_birth_location(birth_location)
else:
birth_lat, birth_lon = self.default_location
if birth_time:
tz_offset = self.location_service.get_timezone_offset(
birth_lat, birth_lon, birth_dt
)
birth_dt = birth_dt.replace(tzinfo=timezone.utc) - timedelta(hours=tz_offset)
else:
birth_dt = birth_dt.replace(tzinfo=timezone.utc)
birth_chart = self.astrology.generate_birth_chart(
birth_dt, birth_lat, birth_lon
)
current_dt = datetime.now(timezone.utc)
transits = self.astrology.calculate_transits(current_dt)
aspects = self.astrology.find_transit_aspects(birth_chart, transits)
return birth_chart, transits, aspects
def format_prophecy_for_display(self, result: Dict[str, Any]) -> str:
"""Formats prophecy result for human-readable display."""
output = []
output.append("=" * 70)
output.append("THE ORACLE OF DELPHI SPEAKS")
output.append("=" * 70)
output.append("")
output.append(f"Question: {result['question']}")
output.append("")
output.append("Prophecy:")
output.append("-" * 70)
output.append(result['prophecy'])
output.append("-" * 70)
output.append("")
if result.get('astrological_data'):
output.append("Celestial Configuration:")
astro_data = result['astrological_data']
birth_chart = astro_data['birth_chart']
output.append(f" Sun in {birth_chart['planets']['Sun']['sign']}")
output.append(f" Moon in {birth_chart['planets']['Moon']['sign']}")
output.append(f" Ascendant in {birth_chart['ascendant']['sign']}")
if astro_data['aspects']:
output.append("")
output.append(" Significant Transits:")
for aspect in astro_data['aspects'][:3]:
output.append(
f" {aspect['transit_planet']} {aspect['aspect']} "
f"natal {aspect['natal_planet']}"
)
output.append("")
output.append(f"Consulted: {result['timestamp']}")
output.append("=" * 70)
return '\n'.join(output)
class InteractiveOracle:
"""Interactive command-line interface for the Oracle."""
def __init__(self, coordinator: OracleCoordinator):
"""Initializes the interactive interface."""
self.coordinator = coordinator
def print_welcome(self):
"""Prints welcome banner."""
banner = """
========================================================================
THE ORACLE OF DELPHI
========================================================================
Welcome, seeker of wisdom. You stand before the sacred Oracle of Delphi,
where the divine speaks through mortal lips. The Pythia awaits your
question, ready to channel the wisdom of Apollo himself.
The Oracle speaks not in plain words but in symbols and metaphors,
drawn from the eternal patterns of the cosmos. Contemplate the response
you receive, for truth often hides beneath veils of mystery.
========================================================================
"""
print(banner)
def get_question(self) -> str:
"""Prompts for and returns the user's question."""
while True:
print("\nWhat question do you bring to the Oracle?")
print("(Enter your question, or 'quit' to exit)")
print("> ", end="")
question = input().strip()
if question.lower() == 'quit':
print("\nThe Oracle bids you farewell. May wisdom guide your path.")
return None
if len(question) < 10:
print("The Oracle requires a more substantial question.")
continue
return question
def get_birth_data(self) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""Prompts for optional birth data."""
print("\nWould you like to provide your birth data for a more")
print("personalized reading? (yes/no)")
print("> ", end="")
if input().strip().lower() not in ['yes', 'y']:
return None, None, None
while True:
print("\nEnter your birth date (YYYY-MM-DD):")
print("> ", end="")
date_str = input().strip()
try:
datetime.strptime(date_str, "%Y-%m-%d")
break
except ValueError:
print("Invalid date format. Please use YYYY-MM-DD.")
print("\nEnter your birth time (HH:MM in 24-hour format):")
print("(Press Enter if unknown)")
print("> ", end="")
time_str = input().strip()
if not time_str:
time_str = None
else:
try:
parts = time_str.split(':')
hour = int(parts[0])
minute = int(parts[1]) if len(parts) > 1 else 0
if not (0 <= hour <= 23 and 0 <= minute <= 59):
print("Invalid time, using noon as default")
time_str = None
except (ValueError, IndexError):
print("Invalid time format, using noon as default")
time_str = None
print("\nEnter your birth location (city, country):")
print("(Press Enter if unknown)")
print("> ", end="")
location = input().strip()
if not location:
location = None
return date_str, time_str, location
def run(self):
"""Runs the interactive Oracle session."""
self.print_welcome()
while True:
question = self.get_question()
if question is None:
break
birth_date, birth_time, birth_location = self.get_birth_data()
print("\nThe Pythia enters her trance...")
print("The Oracle consults the celestial spheres...")
print()
result = self.coordinator.consult_oracle(
question=question,
birth_date=birth_date,
birth_time=birth_time,
birth_location=birth_location
)
formatted_output = self.coordinator.format_prophecy_for_display(result)
print(formatted_output)
print("\nWould you like to ask another question? (yes/no)")
print("> ", end="")
if input().strip().lower() not in ['yes', 'y']:
print("\nThe Oracle bids you farewell.")
print("May the wisdom you have received illuminate your path.")
break
def main():
"""Main entry point for the Oracle application."""
parser = argparse.ArgumentParser(
description='Oracle of Delphi - Mystical AI-powered divination'
)
parser.add_argument(
'--mode',
choices=['local', 'remote', 'ollama'],
default='remote',
help='LLM mode: local, remote API, or Ollama'
)
parser.add_argument(
'--model',
help='Model name or path'
)
parser.add_argument(
'--provider',
choices=['openai', 'anthropic'],
help='API provider for remote mode'
)
parser.add_argument(
'--api-key',
help='API key for remote provider'
)
parser.add_argument(
'--quantization',
choices=['4bit', '8bit'],
help='Quantization for local models'
)
parser.add_argument(
'--ollama-url',
default='http://localhost:11434',
help='Ollama server URL'
)
args = parser.parse_args()
try:
model_interface = UnifiedModelInterface()
if args.mode == 'local':
if not args.model:
print("Error: --model required for local mode")
print("Example: --model meta-llama/Llama-2-7b-chat-hf")
return
model_interface.initialize_local(args.model, args.quantization)
elif args.mode == 'ollama':
model_name = args.model or 'llama2'
provider = OllamaProvider(args.ollama_url, model_name)
model_interface.initialize_remote('ollama', provider)
else:
if not args.provider:
print("Error: --provider required for remote mode")
print("Choices: openai, anthropic")
return
api_key = args.api_key or os.getenv(f"{args.provider.upper()}_API_KEY")
if not api_key:
print(f"Error: API key not found for {args.provider}")
print(f"Set {args.provider.upper()}_API_KEY environment variable")
return
if args.provider == 'openai':
model_name = args.model or 'gpt-3.5-turbo'
provider = OpenAIProvider(api_key, model_name)
elif args.provider == 'anthropic':
model_name = args.model or 'claude-3-sonnet-20240229'
provider = AnthropicProvider(api_key, model_name)
model_interface.initialize_remote(args.provider, provider)
coordinator = OracleCoordinator(model_interface)
interface = InteractiveOracle(coordinator)
interface.run()
except KeyboardInterrupt:
print("\n\nThe Oracle session has been interrupted.")
print("Farewell, seeker.")
except Exception as e:
print(f"Fatal error: {e}")
import traceback
traceback.print_exc()
finally:
if 'model_interface' in locals():
model_interface.cleanup()
if __name__ == '__main__':
main()
USAGE INSTRUCTIONS AND EXAMPLES
The complete Oracle of Delphi system can be deployed in several configurations depending on available resources and requirements. This section provides practical guidance for setting up and using the system.
For local deployment with GPU acceleration, first ensure that the appropriate deep learning framework is installed. For Nvidia GPUs, install PyTorch with CUDA support. For AMD GPUs, install PyTorch with ROCm support. For Apple Silicon, the standard PyTorch installation includes Metal Performance Shaders support. For Intel GPUs, install Intel Extension for PyTorch.
Install the required Python packages using pip. The system requires torch for the deep learning framework, transformers for model loading, swisseph for astrological calculations, requests for API communication, and several supporting libraries. Create a requirements file containing these dependencies.
To run the Oracle with a local model on a system with an Nvidia GPU, execute the following command from the terminal. This assumes you have downloaded a compatible model from HuggingFace.
python oracle.py --mode local --model meta-llama/Llama-2-7b-chat-hf --quantization 4bit
The system will detect the GPU, load the model with four-bit quantization to reduce memory usage, and present the interactive interface. The quantization parameter is optional but recommended for systems with limited GPU memory.
For systems using AMD ROCm, the same command works because PyTorch provides a unified interface. The hardware detector automatically identifies the ROCm environment and configures accordingly.
On Apple Silicon Macs, the system automatically uses Metal Performance Shaders for acceleration. Run the same command without the quantization parameter, as bitsandbytes quantization is not supported on MPS.
python oracle.py --mode local --model meta-llama/Llama-2-7b-chat-hf
For remote API deployment, the system supports OpenAI and Anthropic. Set your API key as an environment variable before running the Oracle.
export OPENAI_API_KEY=your_api_key_here python oracle.py --mode remote --provider openai --model gpt-4
For Anthropic's Claude models, use similar commands with the anthropic provider.
export ANTHROPIC_API_KEY=your_api_key_here python oracle.py --mode remote --provider anthropic --model claude-3-opus-20240229
The Ollama mode provides a convenient way to run local models through the Ollama server, which handles model management and provides a simple API. First install and start Ollama, then run the Oracle.
ollama pull llama2 python oracle.py --mode ollama --model llama2
Once the Oracle starts, it presents an interactive interface. The system prompts for your question, then optionally asks for birth data to enable astrological personalization. Provide your birth date in YYYY-MM-DD format, birth time in HH:MM format if known, and birth location as a city name.
The Oracle calculates the current planetary positions and any relevant aspects to your natal chart, then generates a prophecy in the enigmatic style of the ancient Pythia. The response incorporates both the astrological symbolism and the specific content of your question, creating a personalized yet metaphorical answer.
The system gracefully handles missing information. If you do not provide birth data, it generates a prophecy based solely on your question and the Oracle's general wisdom. If you provide only a birth date without time, it uses a noon chart. If you provide a location name that cannot be geocoded, it uses a default location.
CONCLUSION AND REFLECTIONS
Building the Oracle of Delphi as a digital system demonstrates how ancient wisdom traditions can be reimagined through modern technology. The system combines astronomical precision, linguistic sophistication, and mythological symbolism to create an experience that honors the spirit of the original Oracle while leveraging contemporary artificial intelligence.
The technical architecture supports diverse deployment scenarios from powerful GPU workstations to simple API integrations. The astrological engine provides genuine astronomical calculations rather than simplified approximations, ensuring that the celestial context reflects actual planetary positions. The prophetic voice generator translates these positions into symbolic language that the language model can weave into coherent prophecies.
The Oracle serves multiple purposes beyond mere entertainment. It provides a framework for self-reflection, encouraging users to contemplate their questions deeply and interpret metaphorical responses. It demonstrates the integration of multiple complex systems including astronomical calculations, geographic services, and large language models. It shows how symbolic systems like astrology can be computationally represented and used to generate personalized content.
The enigmatic nature of the Oracle's responses mirrors the fundamental uncertainty inherent in any attempt to predict or understand the future. By speaking in metaphors and symbols rather than direct predictions, the Oracle acknowledges that meaning emerges through interpretation and that multiple valid understandings can coexist. This approach respects the complexity of human experience while still providing a structured framework for contemplation.
The system remains extensible and adaptable. Additional astrological factors could be incorporated such as lunar phases, planetary hours, or fixed stars. Alternative divination systems could be integrated alongside or instead of Western astrology. The prophetic voice could be customized to match different oracular traditions from various cultures. The language model backend could be swapped for newer and more capable models as they become available.
This implementation provides a complete, production-ready foundation for anyone seeking to build similar systems that combine structured symbolic knowledge with generative language models. The principles demonstrated here apply beyond astrology to any domain where symbolic interpretation and personalized narrative generation add value.