Sunday, June 14, 2026

BUILDING THE DIGITAL ORACLE OF DELPHI: A MYSTICAL INTERFACE TO LARGE LANGUAGE MODELS

 



 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.