Spaces:
Running
Running
| """ | |
| HERMES Voice Agent Integration | |
| Using ElevenLabs Conversational AI for interactive astrology teaching | |
| """ | |
| import os | |
| from typing import Dict, Optional, List | |
| from elevenlabs import VoiceSettings | |
| from elevenlabs.client import ElevenLabs | |
| # Initialize ElevenLabs client | |
| ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY", "") | |
| class AstrologyVoiceAgent: | |
| """Voice-enabled teaching assistant for Hellenistic astrology""" | |
| def __init__(self, agent_type: str = "teaching"): | |
| """ | |
| Initialize voice agent | |
| Args: | |
| agent_type: Either 'teaching' (Thrasyllus) or 'consultation' (Valentia) | |
| """ | |
| self.client = ElevenLabs(api_key=ELEVENLABS_API_KEY) if ELEVENLABS_API_KEY else None | |
| self.agent_type = agent_type | |
| # Voice configurations | |
| self.voices = { | |
| "teaching": { | |
| "name": "Thrasyllus", | |
| "description": "Professional pedagogical voice for explanations and historical context", | |
| "voice_id": None, # Will be set when voice is created | |
| "settings": VoiceSettings( | |
| stability=0.7, | |
| similarity_boost=0.8, | |
| style=0.5, | |
| use_speaker_boost=True | |
| ) | |
| }, | |
| "consultation": { | |
| "name": "Valentia", | |
| "description": "Warm interpretive voice for delineations and practice", | |
| "voice_id": None, | |
| "settings": VoiceSettings( | |
| stability=0.6, | |
| similarity_boost=0.75, | |
| style=0.6, | |
| use_speaker_boost=True | |
| ) | |
| } | |
| } | |
| def create_teaching_prompt(self) -> str: | |
| """System prompt for teaching mode (Thrasyllus)""" | |
| return """You are Thrasyllus, a Hellenistic astrology teacher in the tradition of the ancient Greek astrologers. | |
| Your teaching style: | |
| - Explain techniques step-by-step with historical context | |
| - Cite specific ancient sources (Valens, Ptolemy, Dorotheus) | |
| - Compare different authors' approaches when they disagree | |
| - Use concrete examples from practice | |
| - Encourage critical thinking about methods | |
| - Speak clearly and pedagogically | |
| When teaching: | |
| 1. Start with the fundamental concept | |
| 2. Provide historical background | |
| 3. Walk through calculation methods | |
| 4. Give practical examples | |
| 5. Note variations between authors | |
| Always consider sect (day/night chart) in delineations. | |
| Reference whole sign houses unless specified otherwise. | |
| Distinguish between essential dignities and accidental dignities. | |
| You are patient, thorough, and passionate about preserving traditional methods.""" | |
| def create_consultation_prompt(self) -> str: | |
| """System prompt for consultation mode (Valentia)""" | |
| return """You are Valentia, a Hellenistic astrology consultant offering interpretive insights. | |
| Your consultation style: | |
| - Warm and empathetic while maintaining traditional rigor | |
| - Focus on practical life applications | |
| - Consider timing techniques (profections, ZR, firdaria) | |
| - Emphasize planetary condition and sect | |
| - Offer nuanced interpretations, not fatalistic pronouncements | |
| When analyzing charts: | |
| 1. Assess overall chart condition (sect, dignities) | |
| 2. Identify the most important planets | |
| 3. Consider time-lord activations | |
| 4. Synthesize traditional significations with timing | |
| 5. Provide actionable guidance | |
| Remember: "The stars incline, they do not compel." | |
| Traditional astrology empowers through knowledge, not fear. | |
| You speak conversationally but with depth and accuracy.""" | |
| def explain_technique(self, technique: str, context: Dict = None) -> str: | |
| """ | |
| Generate audio explanation of a Hellenistic technique | |
| Args: | |
| technique: Name of technique (e.g., "annual profections", "sect theory") | |
| context: Optional context (chart data, specific question) | |
| Returns: | |
| Text response (will be converted to audio) | |
| """ | |
| explanations = { | |
| "annual_profections": """ | |
| Let me walk you through annual profections, one of the most practical time-lord | |
| techniques in Hellenistic astrology. | |
| The method comes from Valens, Book 4 of his Anthology. It's beautifully simple: | |
| each year of life corresponds to one house, counted from the Ascendant. | |
| At birth, you're in your 1st house year. At age 1, you move to the 2nd house. | |
| At 12, you return to the 1st house - your first profection return. This continues | |
| throughout life. | |
| The ruler of the profected house becomes your "Lord of the Year." This planet | |
| is activated for that entire year. Pay attention to transits to it, where it's | |
| located in your natal chart, and its essential condition. | |
| For example, if you're 25 years old: 25 divided by 12 is 2 with remainder 1. | |
| So you add 1, giving you the 2nd house. The ruler of your natal 2nd house is | |
| your Lord of the Year at age 25. | |
| Paulus Alexandrinus adds that any planets in the profected house are also | |
| activated that year. This is brilliant for prediction - it tells you which | |
| parts of your chart are "turned on." | |
| """, | |
| "sect_theory": """ | |
| Sect - or hairesis in Greek - is absolutely fundamental to Hellenistic astrology. | |
| It distinguishes between day and night charts. | |
| Valens is quite explicit about this in Book 3. If the Sun is above the horizon | |
| at birth, you have a diurnal or day chart. Sun below the horizon? Nocturnal or | |
| night chart. | |
| This matters enormously for delineation. In a day chart, the benefics are | |
| Jupiter and Saturn. The malefics are Mars (worse) and Saturn (less difficult). | |
| In a night chart, it reverses: Venus and the Moon are the benefics, while | |
| Saturn becomes the worse malefic. | |
| Ptolemy in the Tetrabiblos emphasizes that planets in their proper sect are | |
| strengthened. A day chart with Jupiter angular is far more favorable than the | |
| same position for Saturn. | |
| When you assess planetary condition, you must always consider sect. A planet | |
| in its proper sect and well-dignified can mitigate many difficulties. One | |
| contrary to sect and poorly placed? That's when you see the harder significations | |
| manifest. | |
| """, | |
| "zodiacal_releasing": """ | |
| Zodiacal Releasing is perhaps the most sophisticated time-lord system we have | |
| from the Hellenistic tradition. Valens calls it "Loosing of the Bond." | |
| It uses a fixed planetary order through the signs: Cancer, Leo, Virgo, and so on. | |
| Each sign has a set number of years - Cancer gives 25 years, Leo 19, Virgo 20, | |
| and so forth. | |
| You usually start from the Lot of Fortune for general life circumstances, or the | |
| Lot of Spirit for career and reputation. The sign where your lot falls begins | |
| the first period. | |
| What makes this powerful is the layering - you have major periods, minor periods | |
| within those, and even sub-minor periods. When multiple levels activate the same | |
| sign, that's when significant events tend to manifest. | |
| Peak periods - when you shift into the sign opposite your Fortune or Spirit - | |
| often mark major life turning points. Schmidt's work on this through Project | |
| Hindsight really illuminated how to use it in practice. | |
| """ | |
| } | |
| return explanations.get( | |
| technique.lower().replace(" ", "_"), | |
| f"I'd be happy to explain {technique}. This is a {self.agent_type} mode explanation." | |
| ) | |
| def delineate_planetary_condition(self, planet_data: Dict) -> str: | |
| """ | |
| Provide voice delineation of a planet's condition | |
| Args: | |
| planet_data: Dict with planet, sign, dignities, sect info | |
| """ | |
| planet = planet_data.get("planet", "") | |
| sign = planet_data.get("sign", "") | |
| dignities = planet_data.get("dignities", []) | |
| sect = planet_data.get("sect", "day") | |
| response = f"Let's look at {planet} in {sign}. " | |
| if "Domicile" in str(dignities): | |
| response += f"{planet} is in its domicile here - this is its home sign. " | |
| response += "The planet has maximum autonomy and can express its nature freely. " | |
| elif "Exaltation" in str(dignities): | |
| response += f"{planet} is exalted in {sign}. " | |
| response += "This is an honored guest position, able to act at its best. " | |
| elif dignities: | |
| response += f"{planet} has some dignity through {', '.join(dignities)}. " | |
| else: | |
| response += f"{planet} is peregrine here - no essential dignity. " | |
| response += "The planet is like a stranger in a foreign land. " | |
| # Sect consideration | |
| if sect == "day": | |
| response += "In this day chart, " | |
| else: | |
| response += "In this night chart, " | |
| if planet in ["Jupiter", "Sun", "Saturn"] and sect == "day": | |
| response += f"{planet} is in its proper sect, which strengthens it. " | |
| elif planet in ["Venus", "Moon", "Mars"] and sect == "night": | |
| response += f"{planet} is in its proper sect, which strengthens it. " | |
| else: | |
| response += f"{planet} is contrary to sect, which weakens its expression. " | |
| return response | |
| def create_quiz_interaction(self, topic: str) -> List[Dict]: | |
| """ | |
| Generate interactive quiz for learning | |
| Args: | |
| topic: Quiz topic (e.g., "bounds", "triplicities", "lots") | |
| Returns: | |
| List of Q&A pairs for interactive learning | |
| """ | |
| quizzes = { | |
| "bounds": [ | |
| { | |
| "question": "In the Egyptian system, which planet rules the first 6 degrees of Aries?", | |
| "answer": "Jupiter", | |
| "explanation": "Jupiter rules 0-6 degrees of Aries in the Egyptian bounds. This is interesting because Mars rules Aries by domicile, but Jupiter - the greater benefic - gets the first term." | |
| }, | |
| { | |
| "question": "How many dignity points do bounds contribute to a planet's essential dignity?", | |
| "answer": "2 points", | |
| "explanation": "Bounds give 2 points in the 5-point dignity system. It's less than domicile (5) or exaltation (4), but more than decan (1)." | |
| } | |
| ], | |
| "triplicities": [ | |
| { | |
| "question": "Which planet is the day ruler of the fire triplicity?", | |
| "answer": "The Sun", | |
| "explanation": "The Sun rules fire by day, Jupiter by night, with Saturn as the participating ruler. This makes sense - fire is solar in nature." | |
| }, | |
| { | |
| "question": "What are the three modes of triplicity rulership?", | |
| "answer": "Day ruler, night ruler, and participating ruler", | |
| "explanation": "Triplicities have three rulers: one for day charts, one for night charts, and a participating ruler that works in both. This tri-partite system is unique to triplicities." | |
| } | |
| ] | |
| } | |
| return quizzes.get(topic, [ | |
| { | |
| "question": f"Tell me about {topic} in Hellenistic astrology", | |
| "answer": "This is a fundamental concept worth exploring.", | |
| "explanation": f"Let's dive deep into {topic} together." | |
| } | |
| ]) | |
| def generate_audio(self, text: str, voice_type: str = None) -> bytes: | |
| """ | |
| Convert text to speech using ElevenLabs | |
| Args: | |
| text: Text to convert | |
| voice_type: Override default voice type | |
| Returns: | |
| Audio bytes | |
| """ | |
| if not self.client: | |
| return b"" # Return empty bytes if no API key | |
| voice_config = self.voices.get(voice_type or self.agent_type) | |
| # This will be implemented with actual ElevenLabs API calls | |
| # For now, placeholder | |
| try: | |
| audio = self.client.generate( | |
| text=text, | |
| voice=voice_config["voice_id"], | |
| model="eleven_monolingual_v1", | |
| voice_settings=voice_config["settings"] | |
| ) | |
| return audio | |
| except Exception as e: | |
| print(f"Voice generation error: {e}") | |
| return b"" | |
| # Example usage functions | |
| def demo_teaching_session(): | |
| """Demonstrate teaching mode""" | |
| agent = AstrologyVoiceAgent(agent_type="teaching") | |
| # Explain annual profections | |
| explanation = agent.explain_technique("annual profections") | |
| print("Teaching Explanation:") | |
| print(explanation) | |
| # Quiz on bounds | |
| quiz = agent.create_quiz_interaction("bounds") | |
| print("\nInteractive Quiz:") | |
| for q in quiz: | |
| print(f"Q: {q['question']}") | |
| print(f"A: {q['answer']}") | |
| print(f"Explanation: {q['explanation']}\n") | |
| def demo_consultation_session(): | |
| """Demonstrate consultation mode""" | |
| agent = AstrologyVoiceAgent(agent_type="consultation") | |
| # Delineate a planetary condition | |
| planet_data = { | |
| "planet": "Jupiter", | |
| "sign": "Cancer", | |
| "dignities": ["Exaltation (+4)", "Triplicity Ruler - Day (+3)"], | |
| "sect": "day" | |
| } | |
| delineation = agent.delineate_planetary_condition(planet_data) | |
| print("Consultation Delineation:") | |
| print(delineation) | |
| if __name__ == "__main__": | |
| print("HERMES Voice Agent Demo\n") | |
| print("=" * 50) | |
| print("\n1. Teaching Mode (Thrasyllus):\n") | |
| demo_teaching_session() | |
| print("\n" + "=" * 50) | |
| print("\n2. Consultation Mode (Valentia):\n") | |
| demo_consultation_session() | |