HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //opt/af/src/calendar_agent/agent.py
import os
import logging
import pandas as pd
from datetime import datetime
import json
from agno.agent import Agent as AgnoAgent
from agno.tools import tool
from agno.models.openai import OpenAIChat
from dotenv import load_dotenv

# Lade Umgebungsvariablen
load_dotenv()

# Konfiguriere Logging
log = logging.getLogger(__name__)

# --- Definition der Kalender-spezifischen Tools ---

@tool
def get_today_date_formatted() -> str:
    """
    Gibt das heutige Datum im Format YYYYMMDD zurück.
    
    Returns:
        str: Das heutige Datum im Format YYYYMMDD
    """
    today = datetime.now().strftime("%Y%m%d")
    log.info(f"Heutiges Datum (YYYYMMDD): {today}")
    return today

@tool
def list_calendar_files(search_date: str = None) -> str:
    """
    Listet die verfügbaren Kalenderdateien auf, optional gefiltert nach Datum.
    
    Args:
        search_date (str, optional): Das Datum im Format YYYYMMDD, nach dem gefiltert werden soll.
    
    Returns:
        str: Eine Liste der gefundenen Kalenderdateien
    """
    calendar_dir = os.path.join(os.getenv('DATA_DIR', 'data'), 'calendar')
    log.info(f"Suche im Kalender-Verzeichnis: {calendar_dir}")
    
    if not os.path.exists(calendar_dir):
        return f"Fehler: Kalender-Verzeichnis {calendar_dir} nicht gefunden."
    
    try:
        files = os.listdir(calendar_dir)
        calendar_files = [f for f in files if f.endswith('.csv')]
        
        if search_date:
            calendar_files = [f for f in calendar_files if search_date in f]
            log.info(f"Gefilterte Kalenderdateien für Datum {search_date}: {len(calendar_files)} gefunden")
        else:
            log.info(f"Gefundene Kalenderdateien: {len(calendar_files)}")
        
        if not calendar_files:
            return f"Keine Kalenderdateien{' für ' + search_date if search_date else ''} gefunden."
        
        return "\n".join(sorted(calendar_files))
    except Exception as e:
        log.error(f"Fehler beim Auflisten der Kalenderdateien: {e}", exc_info=True)
        return f"Fehler beim Auflisten der Kalenderdateien: {str(e)}"

@tool
def read_calendar_file(date: str, weekly: bool = False) -> str:
    """
    Liest eine Kalenderdatei für ein bestimmtes Datum.
    
    Args:
        date (str): Das Datum im Format YYYYMMDD
        weekly (bool, optional): Ob die wöchentliche Version (mit 'w' am Ende) gelesen werden soll.
    
    Returns:
        str: Der Inhalt der Kalenderdatei als formatierte Markdown-Tabelle
    """
    filename = f"{date}{'w' if weekly else ''}.csv"
    calendar_dir = os.path.join(os.getenv('DATA_DIR', 'data'), 'calendar')
    filepath = os.path.join(calendar_dir, filename)
    
    log.info(f"Versuche Kalenderdatei zu lesen: {filepath}")
    
    if not os.path.exists(filepath):
        return f"Fehler: Kalenderdatei für {date}{'w' if weekly else ''} nicht gefunden."
    
    try:
        # Lese die CSV-Datei in einen DataFrame
        df = pd.read_csv(filepath, header=None)
        
        # Setze Spaltennamen basierend auf der Anzahl der Spalten
        # Format der Datei: Uhrzeit,Währung,Ereignis,Aktuell,Prognose,Vorherig,[Revision]
        if weekly:
            columns = ['Uhrzeit', 'Währung', 'Ereignis', 'Prognose', 'Vorherig']
        else:
            if df.shape[1] == 6:
                columns = ['Uhrzeit', 'Währung', 'Ereignis', 'Aktuell', 'Prognose', 'Vorherig']
            elif df.shape[1] == 7:
                columns = ['Uhrzeit', 'Währung', 'Ereignis', 'Aktuell', 'Prognose', 'Vorherig', 'Revision']
            else:
                # Fallback für unbekannte Spaltenanzahl
                columns = [f"Spalte_{i}" for i in range(df.shape[1])]
        
        df.columns = columns
        
        log.info(f"Kalenderdatei für {date}{'w' if weekly else ''} erfolgreich gelesen. Shape: {df.shape}")
        
        # Konvertiere zu Markdown und gib zurück
        return df.to_markdown(index=False)
    except Exception as e:
        log.error(f"Fehler beim Lesen der Kalenderdatei {filename}: {e}", exc_info=True)
        return f"Fehler beim Lesen der Kalenderdatei {filename}: {str(e)}"

@tool
def get_todays_events() -> str:
    """
    Gibt alle Termine für heute zurück.
    
    Returns:
        str: Eine Markdown-Tabelle mit allen Terminen für heute
    """
    today = datetime.now().strftime("%Y%m%d")
    log.info(f"Hole Termine für heute ({today})")
    
    # Versuche zuerst die normale Datei
    result = read_calendar_file(today)
    
    # Wenn das nicht funktioniert, versuche die wöchentliche Datei
    if "Fehler" in result:
        result = read_calendar_file(today, weekly=True)
        if "Fehler" in result:
            return f"Keine Termineinträge für heute ({today}) gefunden."
    
    return result

# --- Agentenklasse ---

class CalendarAgent:
    def __init__(self):
        """
        Initialisiert einen spezialisierten Agenten für Kalenderdaten mit Agno.
        """
        self.agent = None
        try:
            # Definiere die Liste der Tools für den Agenten
            agent_tools = [
                get_today_date_formatted,
                list_calendar_files,
                read_calendar_file,
                get_todays_events
            ]
            
            # Lade API-Key aus der Umgebung
            openai_api_key = os.getenv('OPENAI_API_KEY')
            if not openai_api_key:
                raise ValueError("OPENAI_API_KEY nicht in der Umgebung gefunden.")
            
            self.agent = AgnoAgent(
                name="CalendarAgent",
                model=OpenAIChat(
                    api_key=openai_api_key,
                    id="gpt-4o"
                ),
                tools=agent_tools,
                markdown=True,
                show_tool_calls=True,
                instructions=[
                    "Du bist ein spezialisierter Assistent für Kalenderdaten von ActionForex.",
                    "Deine Aufgabe ist es, Wirtschaftstermine und Finanzereignisse aus den Kalenderdateien abzurufen.",
                    "Die Kalenderdateien sind im Format YYYYMMDD.csv oder YYYYMMDDw.csv gespeichert.",
                    "Jede Zeile enthält: Uhrzeit, Währung, Ereignis, Aktueller Wert, Prognose, Vorheriger Wert (und manchmal Revision).",
                    "Nutze das Tool get_todays_events() als erste Wahl, um Termine von heute zu sehen.",
                    "Wenn nach spezifischen Daten gefragt wird, nutze read_calendar_file(date) mit dem Format YYYYMMDD.",
                    "Präsentiere die Ergebnisse immer als übersichtliche Tabelle mit passender Formatierung."
                ]
            )
            log.info("Calendar Agent erfolgreich mit Tools initialisiert.")
        except Exception as e:
            log.error(f"Fehler bei der Initialisierung des Calendar Agents: {e}", exc_info=True)
    
    def process_query(self, query: str) -> str:
        """
        Verarbeitet eine Anfrage an den Kalender-Agenten.
        
        Args:
            query (str): Die Benutzeranfrage.
            
        Returns:
            str: Die Antwort des Agenten.
        """
        if self.agent is None:
            log.error("Versuch, process_query aufzurufen, aber Agent ist nicht initialisiert.")
            return "Fehler: Der Calendar Agent konnte nicht korrekt initialisiert werden."
        
        try:
            log.info(f"Sende Anfrage an Calendar Agent: {query}")
            response = self.agent.run(query)
            log.info(f"Calendar Agent hat geantwortet. Typ der Antwort: {type(response)}")
            
            # Detaillierte Logging für das Response-Objekt
            log.info(f"Response Attribute: {dir(response)}")
            log.info(f"Response Representation: {repr(response)}")
            
            # Versuche verschiedene Attribute je nach Agno-Version
            content = None
            
            if hasattr(response, 'output'):
                content = response.output
                log.info(f"Extrahierte Antwort aus 'output'-Attribut: {type(content)}")
            elif hasattr(response, 'content'):
                content = response.content
                log.info(f"Extrahierte Antwort aus 'content'-Attribut: {type(content)}")
            elif hasattr(response, 'response'):
                content = response.response
                log.info(f"Extrahierte Antwort aus 'response'-Attribut: {type(content)}")
            elif isinstance(response, str):
                content = response
                log.info("Response ist bereits ein String")
            else:
                # Fallback: Direktes String-Casting
                content = str(response)
                log.info(f"Fallback: String-Konvertierung der Response: {type(content)}")
            
            if content is None:
                log.error(f"Konnte keine Ausgabe aus der Antwort extrahieren: {response}")
                return f"Fehler: Konnte keine verständliche Antwort vom Agenten extrahieren."
            
            log.info(f"Endgültige Antwort (Anfang): {str(content)[:100]}...")
            return content
        except Exception as e:
            log.error(f"Fehler bei der Verarbeitung der Anfrage: {e}", exc_info=True)
            return f"Fehler bei der Verarbeitung der Anfrage: {str(e)}"