full dynamic configurable ai agents chat

This commit is contained in:
valueon 2025-03-20 00:21:51 +01:00
parent 3094007d6b
commit 7184192621
15 changed files with 1713 additions and 695 deletions

View file

@ -11,9 +11,9 @@ import configload
# Get Config Data
config=configload.load_config()
SECRET_KEY = config.get('Access', 'SECRET_KEY')
ALGORITHM = config.get('Access', 'ALGORITHM')
ACCESS_TOKEN_EXPIRE_MINUTES = int(config.get('Access', 'ACCESS_TOKEN_EXPIRE_MINUTES'))
SECRET_KEY = config.get('Auth', 'SECRET_KEY')
ALGORITHM = config.get('Auth', 'ALGORITHM')
ACCESS_TOKEN_EXPIRE_MINUTES = int(config.get('Auth', 'ACCESS_TOKEN_EXPIRE_MINUTES'))
# OAuth2 Setup

View file

@ -1,20 +1,31 @@
[Application]
DEBUG = True
UPLOAD_DIR = ./_uploads
RESULTS_DIR = ./_results
[Access]
[Auth]
SECRET_KEY = dein-geheimer-schlüssel
ALGORITHM = HS256
ACCESS_TOKEN_EXPIRE_MINUTES = 300
[OpenAI]
[Module_AgentserviceInterface]
DEBUG = True
UPLOAD_DIR = ./_uploads
RESULTS_DIR = ./_results
MAX_HISTORY = 50
AI_PROVIDER = anthropic # Mögliche Werte: "openai" oder "anthropic"
[Connector_AiOpenai]
API_KEY = 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
API_URL = https://api.openai.com/v1/chat/completions
MODEL_NAME = gpt-4o
TEMPERATURE = 0.2
MAX_TOKENS = 2000
[WebScraping]
[Connector_AiWebscraping]
TIMEOUT = 10
MAX_URLS = 3
MAX_CONTENT_LENGTH = 3000
USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
[Connector_AiAnthropic]
API_KEY = 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
API_URL = https://api.anthropic.com/v1/messages
MODEL_NAME = claude-3-opus-20240229
TEMPERATURE = 0.2
MAX_TOKENS = 2000

View file

@ -0,0 +1,273 @@
import os
import json
import logging
import httpx
import base64
import mimetypes
from typing import Dict, Any, List, Optional
from fastapi import HTTPException
import configload as configload
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Konfigurationsdaten laden
def load_config_data():
config = configload.load_config()
return {
"api_key": config.get('Connector_AiAnthropic', 'API_KEY'),
"api_url": config.get('Connector_AiAnthropic', 'API_URL', fallback="https://api.anthropic.com/v1/messages"),
"model_name": config.get('Connector_AiAnthropic', 'MODEL_NAME', fallback="claude-3-opus-20240229"),
"temperature": float(config.get('Connector_AiAnthropic', 'TEMPERATURE', fallback="0.2")),
"max_tokens": int(config.get('Connector_AiAnthropic', 'MAX_TOKENS', fallback="2000"))
}
class ChatService:
"""
Connector für die Kommunikation mit der Anthropic API.
"""
def __init__(self):
# Konfiguration laden
self.config = load_config_data()
self.api_key = self.config["api_key"]
self.api_url = self.config["api_url"]
self.model_name = self.config["model_name"]
# HttpClient für API-Aufrufe
self.http_client = httpx.AsyncClient(
timeout=120.0, # Längeres Timeout für komplexe Anfragen
headers={
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01", # Anthropic API Version
"Content-Type": "application/json"
}
)
logger.info(f"Anthropic Connector initialisiert mit Modell: {self.model_name}")
async def call_api(self, messages: List[Dict[str, Any]], temperature: float = None, max_tokens: int = None) -> Dict[str, Any]:
"""
Ruft die Anthropic API mit den gegebenen Nachrichten auf.
Args:
messages: Liste von Nachrichten im OpenAI-Format (role, content)
temperature: Temperatur für die Antwortgenerierung (0.0-1.0)
max_tokens: Maximale Anzahl der Token in der Antwort
Returns:
Die Antwort umgewandelt ins OpenAI-Format
Raises:
HTTPException: Bei Fehlern in der API-Kommunikation
"""
try:
# OpenAI-Format in Anthropic-Format umwandeln
formatted_messages = self._convert_to_anthropic_format(messages)
# Verwende Parameter aus der Konfiguration, falls keine überschrieben wurden
if temperature is None:
temperature = self.config.get("temperature", 0.2)
if max_tokens is None:
max_tokens = self.config.get("max_tokens", 2000)
# Anthropic API Payload erstellen
payload = {
"model": self.model_name,
"messages": formatted_messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = await self.http_client.post(
self.api_url,
json=payload
)
if response.status_code != 200:
logger.error(f"Anthropic API-Fehler: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Fehler bei der Kommunikation mit Anthropic API")
# Antwort im Anthropic-Format in OpenAI-Format umwandeln
anthropic_response = response.json()
openai_formatted_response = self._convert_to_openai_format(anthropic_response)
return openai_formatted_response
except Exception as e:
logger.error(f"Fehler beim Aufruf der Anthropic API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Fehler beim Aufruf der Anthropic API: {str(e)}")
def _convert_to_anthropic_format(self, openai_messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Konvertiert Nachrichten vom OpenAI-Format ins Anthropic-Format.
OpenAI verwendet:
[{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."}]
Anthropic verwendet:
[{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."}]
Anmerkung: Anthropic hat kein direktes System-Message-Äquivalent,
daher fügen wir System-Nachrichten in die erste User-Nachricht ein.
"""
anthropic_messages = []
system_content = ""
# Extrahiere zuerst alle System-Nachrichten
for msg in openai_messages:
if msg.get("role") == "system":
system_content += msg.get("content", "") + "\n\n"
# Konvertiere die restlichen Nachrichten
for i, msg in enumerate(openai_messages):
role = msg.get("role")
content = msg.get("content", "")
# System-Nachrichten überspringen (bereits extrahiert)
if role == "system":
continue
# Für die erste User-Nachricht: System-Inhalte voranstellen, falls vorhanden
if role == "user" and system_content and not any(m.get("role") == "user" for m in anthropic_messages):
if isinstance(content, str):
content = system_content + content
elif isinstance(content, list):
# Wenn content ein Array ist (für Multimodal-Nachrichten)
text_parts = []
for part in content:
if part.get("type") == "text":
text_parts.append(part)
if text_parts:
text_parts[0]["text"] = system_content + text_parts[0].get("text", "")
# Anthropic unterstützt nur "user" und "assistant" als Rollen
if role not in ["user", "assistant"]:
role = "user"
anthropic_messages.append({"role": role, "content": content})
return anthropic_messages
def _convert_to_openai_format(self, anthropic_response: Dict[str, Any]) -> Dict[str, Any]:
"""
Konvertiert eine Antwort vom Anthropic-Format ins OpenAI-Format.
Anthropic gibt zurück:
{
"id": "msg_...",
"content": [{"type": "text", "text": "Antworttext"}],
"model": "claude-...",
...
}
OpenAI gibt zurück:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"choices": [
{
"message": {
"role": "assistant",
"content": "Antworttext"
},
"index": 0,
"finish_reason": "stop"
}
],
"model": "gpt-...",
...
}
"""
# Extrahiere Inhalt aus Anthropic-Antwort
content = ""
if "content" in anthropic_response:
if isinstance(anthropic_response["content"], list):
# Inhalt ist eine Liste von Teilen (bei neueren API-Versionen)
for part in anthropic_response["content"]:
if part.get("type") == "text":
content += part.get("text", "")
else:
# Direkter Inhalt als String (bei älteren API-Versionen)
content = anthropic_response["content"]
# Erstelle OpenAI-formatierte Antwort
return {
"id": anthropic_response.get("id", ""),
"object": "chat.completion",
"created": anthropic_response.get("created", 0),
"model": anthropic_response.get("model", self.model_name),
"choices": [
{
"message": {
"role": "assistant",
"content": content
},
"index": 0,
"finish_reason": "stop"
}
]
}
def prepare_file_message_content(self, prompt_text: str, file_paths: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Bereitet eine Nachricht mit Dateien für Anthropic API vor.
Args:
prompt_text: Der Text-Prompt
file_paths: Liste von Dateipfaden mit Metadaten (Dict mit id, name, type, path)
Returns:
Eine für Anthropic-API formatierte content-Liste
"""
message_content = [
{
"type": "text",
"text": prompt_text
}
]
# Füge Dateien als Anhänge hinzu
for file_info in file_paths:
file_path = file_info.get("path", "")
if file_path and os.path.exists(file_path):
try:
# Datei als Base64 codieren
with open(file_path, "rb") as f:
file_data = f.read()
base64_data = base64.b64encode(file_data).decode('utf-8')
# MIME-Typ bestimmen
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type:
mime_type = "application/octet-stream"
# Content-Type bestimmen (image oder document)
content_type = "image" if mime_type.startswith("image/") else "document"
# Füge die Datei als Anhang hinzu
message_content.append({
"type": content_type,
"source": {
"type": "base64",
"media_type": mime_type,
"data": base64_data
}
})
logger.info(f"Datei {file_info.get('name', 'Unbekannt')} als {content_type} hinzugefügt")
except Exception as e:
logger.error(f"Fehler beim Hinzufügen der Datei {file_info.get('name', 'Unbekannt')}: {str(e)}")
return message_content
async def close(self):
"""Schließt den HTTP-Client beim Beenden der Anwendung"""
await self.http_client.aclose()

View file

@ -0,0 +1,146 @@
import os
import json
import logging
import httpx
import base64
import mimetypes
from typing import Dict, Any, List, Optional
from fastapi import HTTPException
import configload as configload
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Konfigurationsdaten laden
def load_config_data():
config = configload.load_config()
return {
"api_key": config.get('Connector_AiOpenai', 'API_KEY'),
"api_url": config.get('Connector_AiOpenai', 'API_URL', fallback="https://api.openai.com/v1/chat/completions"),
"model_name": config.get('Connector_AiOpenai', 'MODEL_NAME', fallback="gpt-4o"),
"temperature": float(config.get('Connector_AiOpenai', 'TEMPERATURE', fallback="0.2")),
"max_tokens": int(config.get('Connector_AiOpenai', 'MAX_TOKENS', fallback="2000"))
}
class ChatService:
"""
Connector für die Kommunikation mit der OpenAI API.
"""
def __init__(self):
# Konfiguration laden
self.config = load_config_data()
self.api_key = self.config["api_key"]
self.api_url = self.config["api_url"]
self.model_name = self.config["model_name"]
# HttpClient für API-Aufrufe
self.http_client = httpx.AsyncClient(
timeout=120.0, # Längeres Timeout für komplexe Anfragen
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
)
logger.info(f"OpenAI Connector initialisiert mit Modell: {self.model_name}")
async def call_api(self, messages: List[Dict[str, Any]], temperature: float = None, max_tokens: int = None) -> Dict[str, Any]:
"""
Ruft die OpenAI API mit den gegebenen Nachrichten auf.
Args:
messages: Liste von Nachrichten im OpenAI-Format (role, content)
temperature: Temperatur für die Antwortgenerierung (0.0-1.0)
max_tokens: Maximale Anzahl der Token in der Antwort
Returns:
Die Antwort der OpenAI API
Raises:
HTTPException: Bei Fehlern in der API-Kommunikation
"""
try:
# Verwende Parameter aus der Konfiguration, falls keine überschrieben wurden
if temperature is None:
temperature = self.config.get("temperature", 0.2)
if max_tokens is None:
max_tokens = self.config.get("max_tokens", 2000)
payload = {
"model": self.model_name,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = await self.http_client.post(
self.api_url,
json=payload
)
if response.status_code != 200:
logger.error(f"OpenAI API-Fehler: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Fehler bei der Kommunikation mit OpenAI API")
return response.json()
except Exception as e:
logger.error(f"Fehler beim Aufruf der OpenAI API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Fehler beim Aufruf der OpenAI API: {str(e)}")
def prepare_file_message_content(self, prompt_text: str, file_paths: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Bereitet eine Nachricht mit Dateien für OpenAI API vor.
Args:
prompt_text: Der Text-Prompt
file_paths: Liste von Dateipfaden mit Metadaten (Dict mit id, name, type, path)
Returns:
Eine für OpenAI-API formatierte content-Liste
"""
message_content = [
{
"type": "text",
"text": prompt_text
}
]
# Füge Dateien als Base64-Anhänge hinzu
for file_info in file_paths:
file_path = file_info.get("path", "")
if file_path and os.path.exists(file_path):
try:
# Datei als Base64 codieren
with open(file_path, "rb") as f:
file_data = f.read()
base64_data = base64.b64encode(file_data).decode('utf-8')
# MIME-Typ bestimmen
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type:
mime_type = "application/octet-stream"
# Füge die Datei als Anhang hinzu
message_content.append({
"type": "file",
"source": {
"type": "base64",
"media_type": mime_type,
"data": base64_data
}
})
logger.info(f"Datei {file_info.get('name', 'Unbekannt')} als Anhang hinzugefügt")
except Exception as e:
logger.error(f"Fehler beim Hinzufügen der Datei {file_info.get('name', 'Unbekannt')}: {str(e)}")
return message_content
async def close(self):
"""Schließt den HTTP-Client beim Beenden der Anwendung"""
await self.http_client.aclose()

View file

@ -0,0 +1,189 @@
import logging
import re
import requests
from typing import List, Dict, Any, Optional
from bs4 import BeautifulSoup
import json
import os
import configload as configload
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Konfigurationsdaten laden
def load_config_data():
config = configload.load_config()
return {
"timeout": config.get('Connector_AiWebscraping', 'TIMEOUT'),
"max_urls": config.get('Connector_AiWebscraping', 'MAX_URLS'),
"max_content_length": config.get('Connector_AiWebscraping', 'MAX_CONTENT_LENGTH'),
"user_agent": config.get('Connector_AiWebscraping', 'USER_AGENT')
}
class WebScrapingService:
"""
Connector für Web-Scraping-Funktionalitäten.
"""
def __init__(self):
# Konfiguration laden
self.config = load_config_data()
logger.info(f"WebScraping Connector initialisiert mit Timeout: {self.timeout}s")
def scrape_url(self, url: str) -> str:
"""
Scrapt den Inhalt einer URL und extrahiert den relevanten Text.
Args:
url: Die zu scrapende URL
Returns:
Der extrahierte Inhalt
Raises:
Exception: Bei Fehlern im Scraping-Prozess
"""
headers = {
'User-Agent': self.user_agent
}
try:
response = requests.get(url, headers=headers, timeout=self.timeout)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Entferne Skripte, Styles und andere unwichtige Elemente
for script in soup(["script", "style", "meta", "noscript", "iframe"]):
script.extract()
# Extrahiere den Hauptinhalt
main_content = ""
# Versuche, Hauptcontainer zu finden (häufige IDs und Klassen)
main_elements = soup.select('main, #main, .main, #content, .content, article, .article, .post, #post')
if main_elements:
# Nehme den ersten gefundenen Hauptcontainer
main_content = main_elements[0].get_text(separator='\n', strip=True)
else:
# Falls kein Hauptcontainer gefunden, nehme den Body-Text
main_content = soup.body.get_text(separator='\n', strip=True)
# Bereinige den Text (entferne mehrfache Leerzeilen etc.)
lines = [line.strip() for line in main_content.split('\n') if line.strip()]
main_content = '\n'.join(lines)
# Begrenze die Länge
if len(main_content) > self.max_content_length:
main_content = main_content[:self.max_content_length] + "...\n[Inhalt gekürzt]"
return main_content
except Exception as e:
logger.error(f"Fehler beim Scrapen von {url}: {str(e)}")
raise Exception(f"Fehler beim Scrapen von {url}: {str(e)}")
def extract_urls(self, text: str) -> List[str]:
"""
Extrahiert URLs aus einem Text.
Args:
text: Der zu analysierende Text
Returns:
Liste der gefundenen URLs
"""
# Einfacher URL-Extraktions-Regex
url_pattern = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.-]*(?:\?\S+)?')
return url_pattern.findall(text)
def extract_keywords(self, text: str) -> str:
"""
Extrahiert Schlüsselwörter aus einem Text.
Args:
text: Der zu analysierende Text
Returns:
Extrahierte Schlüsselwörter als String
"""
# Einfache Implementierung - in der Praxis könntest du NLP verwenden
words = text.split()
# Filtere kurze Wörter und häufige Stopwörter
stopwords = ["einen", "einer", "eines", "keine", "nicht", "diese", "dieses", "zwischen",
"und", "oder", "aber", "denn", "wenn", "weil", "obwohl", "während", "für",
"mit", "von", "aus", "nach", "bei", "über", "unter", "durch", "gegen"]
keywords = [w for w in words if len(w) > 4 and w.lower() not in stopwords]
return " ".join(keywords[:5]) # Begrenze auf 5 Keywords
async def search_web(self, query: str) -> str:
"""
Simuliert eine Websuche mit den gegebenen Schlüsselwörtern.
Args:
query: Suchbegriffe
Returns:
Ergebnisse der Suche (simuliert)
"""
# HINWEIS: Dies ist eine Simulation! In einer echten Anwendung
# würdest du Google Custom Search API, SerpAPI oder ähnliches verwenden
# Für eine echte Implementierung:
# - Google Custom Search API: https://developers.google.com/custom-search/v1/overview
# - SerpAPI: https://serpapi.com/
# - Oder ähnliche Dienste
return f"Hinweis: Dies ist eine Demo-Implementierung ohne echte Websuche. In der Produktion würde hier der Agent tatsächlich nach '{query}' suchen."
async def scrape_web_data(self, prompt: str) -> str:
"""
Führt Web-Scraping basierend auf dem Prompt durch
Args:
prompt: Der Benutzer-Prompt
Returns:
Gescrapte Webdaten als Text
"""
try:
# Extrahiere mögliche Schlüsselwörter oder URLs aus dem Prompt
keywords = self.extract_keywords(prompt)
urls = self.extract_urls(prompt)
results = []
# Falls direkte URLs im Prompt enthalten sind
if urls:
logger.info(f"Gefundene URLs: {', '.join(urls[:self.max_urls])}")
for url in urls[:self.max_urls]: # Begrenze auf max_urls
try:
logger.info(f"Scrape URL: {url}")
content = self.scrape_url(url)
if content:
results.append(f"## Inhalt von {url}\n{content}")
logger.info(f"Scraping von {url} erfolgreich")
except Exception as e:
logger.error(f"Fehler beim Scrapen von {url}: {e}")
# Falls keine URLs, versuche Suche mit Schlüsselwörtern
elif keywords:
logger.info(f"Verwende Keywords für Suche: {keywords}")
search_results = await self.search_web(keywords)
if search_results:
results.append(f"## Suchergebnisse für: {keywords}\n{search_results}")
logger.info("Suche abgeschlossen")
if results:
return "\n\n".join(results)
logger.warning("Keine relevanten Web-Daten gefunden")
return "Keine relevanten Web-Daten gefunden."
except Exception as e:
logger.error(f"Fehler beim Web-Scraping: {e}")
return f"Web-Scraping konnte nicht durchgeführt werden: {str(e)}"

View file

@ -43,9 +43,45 @@ class JSONDatabaseConnector:
# Cache für geladene Daten
self._tables_cache = {}
# System-Tabelle initialisieren
self._system_table_name = "_system"
self._initialize_system_table()
logger.info(f"JSONDatabaseConnector initialisiert für Verzeichnis: {db_folder}")
logger.info(f"Kontext: mandate_id={mandate_id}, user_id={user_id}")
def _initialize_system_table(self):
"""Initialisiert die System-Tabelle, falls sie noch nicht existiert."""
system_table_path = self._get_table_path(self._system_table_name)
if not os.path.exists(system_table_path):
empty_system_table = {}
self._save_system_table(empty_system_table)
logger.info(f"System-Tabelle initialisiert in {system_table_path}")
def _load_system_table(self) -> Dict[str, int]:
"""Lädt die System-Tabelle mit den initialen IDs."""
system_table_path = self._get_table_path(self._system_table_name)
try:
if os.path.exists(system_table_path):
with open(system_table_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return {}
except Exception as e:
logger.error(f"Fehler beim Laden der System-Tabelle: {e}")
return {}
def _save_system_table(self, data: Dict[str, int]) -> bool:
"""Speichert die System-Tabelle mit den initialen IDs."""
system_table_path = self._get_table_path(self._system_table_name)
try:
with open(system_table_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der System-Tabelle: {e}")
return False
def _get_table_path(self, table: str) -> str:
"""Gibt den vollständigen Pfad zu einer Tabellendatei zurück"""
return os.path.join(self.db_folder, f"{table}.json")
@ -54,6 +90,10 @@ class JSONDatabaseConnector:
"""Lädt eine Tabelle aus der entsprechenden JSON-Datei"""
path = self._get_table_path(table)
# Wenn die Tabelle die System-Tabelle ist, lade sie direkt
if table == self._system_table_name:
return [] # Die System-Tabelle wird nicht wie normale Tabellen behandelt
# Wenn die Tabelle bereits im Cache ist, verwende den Cache
if table in self._tables_cache:
logger.info(f"Lade Tabelle {table} aus Cache")
@ -66,6 +106,14 @@ class JSONDatabaseConnector:
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
self._tables_cache[table] = data
# Wenn Daten geladen wurden und noch keine initiale ID registriert ist,
# registriere die ID des ersten Datensatzes (falls vorhanden)
if data and not self.has_initial_id(table):
if "id" in data[0]:
self.register_initial_id(table, data[0]["id"])
logger.info(f"Initiale ID {data[0]['id']} für Tabelle {table} nachträglich registriert")
return data
else:
# Wenn die Datei nicht existiert, erstelle eine leere Tabelle
@ -79,6 +127,10 @@ class JSONDatabaseConnector:
def _save_table(self, table: str, data: List[Dict[str, Any]]) -> bool:
"""Speichert eine Tabelle in der entsprechenden JSON-Datei"""
# Die System-Tabelle wird speziell behandelt
if table == self._system_table_name:
return False
path = self._get_table_path(table)
try:
with open(path, 'w', encoding='utf-8') as f:
@ -167,7 +219,7 @@ class JSONDatabaseConnector:
try:
for filename in os.listdir(self.db_folder):
if filename.endswith('.json'):
if filename.endswith('.json') and not filename.startswith('_'):
table_name = filename[:-5] # Entferne die .json-Endung
tables.append(table_name)
except Exception as e:
@ -293,6 +345,18 @@ class JSONDatabaseConnector:
if "user_id" not in record_data:
record_data["user_id"] = self.user_id
# Bestimme die nächste ID, falls nicht vorhanden
if "id" not in record_data:
next_id = 1
if data:
next_id = max(record["id"] for record in data if "id" in record) + 1
record_data["id"] = next_id
# Wenn die Tabelle leer ist und eine System-ID registriert werden soll
if not data:
self.register_initial_id(table, record_data["id"])
logger.info(f"Initiale ID {record_data['id']} für Tabelle {table} registriert")
# Füge den neuen Datensatz hinzu
data.append(record_data)
@ -316,6 +380,12 @@ class JSONDatabaseConnector:
# Lade die Tabellendaten
data = self._load_table(table)
# Prüfe, ob es sich um die initiale ID handelt
initial_id = self.get_initial_id(table)
if initial_id is not None and initial_id == record_id:
logger.warning(f"Versuch, den initialen Datensatz mit ID {record_id} aus Tabelle {table} zu löschen, wurde verhindert")
return False
# Suche den Datensatz
for i, record in enumerate(data):
if "id" in record and record["id"] == record_id:
@ -354,6 +424,11 @@ class JSONDatabaseConnector:
if "mandate_id" in record and record["mandate_id"] != self.mandate_id:
raise ValueError("Not your mandate")
# Verhindere Änderung der ID bei initialem Datensatz
initial_id = self.get_initial_id(table)
if initial_id is not None and initial_id == record_id and "id" in record_data and record_data["id"] != record_id:
raise ValueError(f"Die ID des initialen Datensatzes in Tabelle {table} kann nicht geändert werden")
# Aktualisiere den Datensatz
for key, value in record_data.items():
data[i][key] = value
@ -365,4 +440,72 @@ class JSONDatabaseConnector:
raise ValueError(f"Fehler beim Aktualisieren des Datensatzes in Tabelle {table}")
# Datensatz nicht gefunden
raise ValueError(f"Datensatz mit ID {record_id} nicht gefunden in Tabelle {table}")
raise ValueError(f"Datensatz mit ID {record_id} nicht gefunden in Tabelle {table}")
# System-Tabellen-Funktionen
def register_initial_id(self, table: str, initial_id: int) -> bool:
"""
Registriert die initiale ID für eine Tabelle.
Args:
table: Name der Tabelle
initial_id: Die initiale ID
Returns:
True bei Erfolg, False bei Fehler
"""
try:
# Lade die aktuelle System-Tabelle
system_data = self._load_system_table()
# Nur registrieren, wenn noch nicht vorhanden
if table not in system_data:
system_data[table] = initial_id
success = self._save_system_table(system_data)
if success:
logger.info(f"Initiale ID {initial_id} für Tabelle {table} registriert")
return success
return True # Wenn bereits vorhanden, ist das kein Fehler
except Exception as e:
logger.error(f"Fehler beim Registrieren der initialen ID für Tabelle {table}: {e}")
return False
def get_initial_id(self, table: str) -> Optional[int]:
"""
Gibt die initiale ID für eine Tabelle zurück.
Args:
table: Name der Tabelle
Returns:
Die initiale ID oder None, wenn nicht vorhanden
"""
system_data = self._load_system_table()
initial_id = system_data.get(table)
if initial_id is None:
logger.debug(f"Keine initiale ID für Tabelle {table} gefunden")
return initial_id
def has_initial_id(self, table: str) -> bool:
"""
Prüft, ob eine initiale ID für eine Tabelle registriert ist.
Args:
table: Name der Tabelle
Returns:
True, wenn eine initiale ID registriert ist, sonst False
"""
system_data = self._load_system_table()
return table in system_data
def get_all_initial_ids(self) -> Dict[str, int]:
"""
Gibt alle registrierten initialen IDs zurück.
Returns:
Dictionary mit Tabellennamen als Schlüssel und initialen IDs als Werte
"""
system_data = self._load_system_table()
return system_data.copy() # Kopie zurückgeben, um das Original zu schützen

View file

@ -43,6 +43,10 @@ class MySQLDatabaseConnector:
# Stelle Verbindung zur Datenbank her
self.connection = self._create_connection()
# System-Tabelle initialisieren
self._system_table_name = "_system"
self._initialize_system_table()
logger.info(f"MySQLDatabaseConnector initialisiert für Datenbank: {db_name}")
logger.info(f"Kontext: mandate_id={mandate_id}, user_id={user_id}")
@ -62,6 +66,39 @@ class MySQLDatabaseConnector:
logger.error(f"Fehler bei der Verbindung zu MySQL: {e}")
raise
def _initialize_system_table(self):
"""Initialisiert die System-Tabelle, falls sie noch nicht existiert."""
cursor = None
try:
cursor = self.connection.cursor()
# Prüfe, ob die System-Tabelle existiert
cursor.execute(f"""
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = '{self.db_name}'
AND table_name = '{self._system_table_name}'
""")
if cursor.fetchone()[0] == 0:
# Erstelle die System-Tabelle
cursor.execute(f"""
CREATE TABLE {self._system_table_name} (
table_name VARCHAR(255) PRIMARY KEY,
initial_id INT NOT NULL
)
""")
self.connection.commit()
logger.info(f"System-Tabelle '{self._system_table_name}' erstellt")
except Error as e:
logger.error(f"Fehler beim Initialisieren der System-Tabelle: {e}")
if self.connection.is_connected():
self.connection.rollback()
raise
finally:
if cursor and cursor.is_connected():
cursor.close()
def _execute_query(self, query: str, params: tuple = None):
"""Führt eine SQL-Abfrage aus"""
cursor = None
@ -175,6 +212,7 @@ class MySQLDatabaseConnector:
SELECT table_name
FROM information_schema.tables
WHERE table_schema = %s
AND table_name NOT LIKE '\_%'
"""
try:
@ -331,9 +369,21 @@ class MySQLDatabaseConnector:
"""
try:
# Prüfe zuerst, ob die Tabelle leer ist
check_query = f"""
SELECT COUNT(*) as count FROM {table}
"""
count_result = self._execute_select(check_query)
is_empty = count_result[0]["count"] == 0
# Führe die Abfrage aus und erhalte die ID des neuen Datensatzes
new_id = self._execute_insert(query, values)
# Wenn die Tabelle vorher leer war, registriere die neue ID als initiale ID
if is_empty and new_id:
self.register_initial_id(table, new_id)
logger.info(f"Initiale ID {new_id} für Tabelle {table} registriert")
# Füge die ID zum Datensatz hinzu, falls eine zurückgegeben wurde
if new_id:
record_data["id"] = new_id
@ -354,6 +404,12 @@ class MySQLDatabaseConnector:
Returns:
True bei Erfolg, False bei Fehler
"""
# Prüfe, ob es sich um die initiale ID handelt
initial_id = self.get_initial_id(table)
if initial_id is not None and initial_id == record_id:
logger.warning(f"Versuch, den initialen Datensatz mit ID {record_id} aus Tabelle {table} zu löschen, wurde verhindert")
return False
# Prüfe zuerst, ob der Datensatz zum aktuellen Mandanten gehört
check_query = f"""
SELECT mandate_id FROM {table} WHERE id = %s
@ -393,6 +449,11 @@ class MySQLDatabaseConnector:
Returns:
Der aktualisierte Datensatz
"""
# Prüfe, ob es sich um die initiale ID handelt und die ID geändert werden soll
initial_id = self.get_initial_id(table)
if initial_id is not None and initial_id == record_id and "id" in record_data and record_data["id"] != record_id:
raise ValueError(f"Die ID des initialen Datensatzes in Tabelle {table} kann nicht geändert werden")
# Prüfe zuerst, ob der Datensatz zum aktuellen Mandanten gehört
check_query = f"""
SELECT mandate_id FROM {table} WHERE id = %s
@ -447,6 +508,125 @@ class MySQLDatabaseConnector:
logger.error(f"Fehler beim Aktualisieren des Datensatzes in Tabelle {table}: {e}")
raise
# System-Tabellen-Funktionen
def register_initial_id(self, table: str, initial_id: int) -> bool:
"""
Registriert die initiale ID für eine Tabelle.
Args:
table: Name der Tabelle
initial_id: Die initiale ID
Returns:
True bei Erfolg, False bei Fehler
"""
try:
# Prüfe zuerst, ob bereits eine initiale ID für diese Tabelle registriert ist
check_query = f"""
SELECT COUNT(*) as count
FROM {self._system_table_name}
WHERE table_name = %s
"""
result = self._execute_select(check_query, (table,))
if result and result[0]["count"] > 0:
# Bereits registriert
return True
# Registriere die initiale ID
insert_query = f"""
INSERT INTO {self._system_table_name} (table_name, initial_id)
VALUES (%s, %s)
"""
self._execute_insert(insert_query, (table, initial_id))
logger.info(f"Initiale ID {initial_id} für Tabelle {table} registriert")
return True
except Exception as e:
logger.error(f"Fehler beim Registrieren der initialen ID für Tabelle {table}: {e}")
return False
def get_initial_id(self, table: str) -> Optional[int]:
"""
Gibt die initiale ID für eine Tabelle zurück.
Args:
table: Name der Tabelle
Returns:
Die initiale ID oder None, wenn nicht vorhanden
"""
try:
query = f"""
SELECT initial_id
FROM {self._system_table_name}
WHERE table_name = %s
"""
result = self._execute_select(query, (table,))
if result and len(result) > 0:
return result[0]["initial_id"]
return None
except Exception as e:
logger.error(f"Fehler beim Abrufen der initialen ID für Tabelle {table}: {e}")
return None
def has_initial_id(self, table: str) -> bool:
"""
Prüft, ob eine initiale ID für eine Tabelle registriert ist.
Args:
table: Name der Tabelle
Returns:
True, wenn eine initiale ID registriert ist, sonst False
"""
try:
query = f"""
SELECT COUNT(*) as count
FROM {self._system_table_name}
WHERE table_name = %s
"""
result = self._execute_select(query, (table,))
if result and len(result) > 0:
return result[0]["count"] > 0
return False
except Exception as e:
logger.error(f"Fehler beim Prüfen der initialen ID für Tabelle {table}: {e}")
return False
def get_all_initial_ids(self) -> Dict[str, int]:
"""
Gibt alle registrierten initialen IDs zurück.
Returns:
Dictionary mit Tabellennamen als Schlüssel und initialen IDs als Werte
"""
try:
query = f"""
SELECT table_name, initial_id
FROM {self._system_table_name}
"""
result = self._execute_select(query)
initial_ids = {}
for row in result:
initial_ids[row["table_name"]] = row["initial_id"]
return initial_ids
except Exception as e:
logger.error(f"Fehler beim Abrufen aller initialen IDs: {e}")
return {}
def close(self):
"""Schließt die Datenbankverbindung"""
if hasattr(self, 'connection') and self.connection.is_connected():

View file

@ -3,39 +3,27 @@ import uuid
import os
import json
import logging
import re
import base64
import mimetypes
from typing import List, Dict, Any, Optional
from datetime import datetime
import httpx
from fastapi import HTTPException
import requests
from bs4 import BeautifulSoup
import configload as configload
# Logger konfigurieren
logger = logging.getLogger(__name__)
# Konfigurationsdaten laden
def load_config_data():
config=configload.load_config()
config = configload.load_config()
result = {
"openai": {
"api_key": config.get('OpenAI', 'API_KEY'),
"api_url": config.get('OpenAI', 'API_URL'),
"model_name": config.get('OpenAI', 'MODEL_NAME')
},
"application": {
"debug": config.get('Application', 'DEBUG'),
"upload_dir": config.get('Application', 'UPLOAD_DIR'),
"results_dir": config.get('Application', 'RESULTS_DIR')
},
"webscraping": {
"timeout": config.get('WebScraping', 'TIMEOUT'),
"max_urls": config.get('WebScraping', 'MAX_URLS'),
"max_content_length": config.get('WebScraping', 'MAX_CONTENT_LENGTH'),
"user_agent": config.get('WebScraping', 'USER_AGENT')
"debug": config.get('Module_AgentserviceInterface', 'DEBUG'),
"upload_dir": config.get('Module_AgentserviceInterface', 'UPLOAD_DIR'),
"results_dir": config.get('Module_AgentserviceInterface', 'RESULTS_DIR'),
"max_history": config.get('Module_AgentserviceInterface', 'MAX_HISTORY'),
"ai_provider": config.get('Module_AgentserviceInterface', 'AI_PROVIDER', fallback="openai") # Standard ist OpenAI
}
}
# Debug-Modus einstellen
@ -48,7 +36,7 @@ def load_config_data():
class AgentService:
"""
Service für die Verwaltung und Ausführung von Multi-Agent-Workflows mit OpenAI GPT-4o.
Service für die Verwaltung und Ausführung von Multi-Agent-Workflows mit verschiedenen Modellen.
"""
def __init__(self, mandate_id: int = None, user_id: int = None):
@ -69,33 +57,35 @@ class AgentService:
# Verzeichnisse aus der Konfiguration übernehmen
self.results_dir = self.config["application"]["results_dir"]
self.upload_dir = self.config["application"]["upload_dir"]
self.max_history = int(self.config["application"]["max_history"])
# Web-Scraping-Konfiguration
self.scraping_timeout = int(self.config["webscraping"]["timeout"])
self.scraping_max_urls = int(self.config["webscraping"]["max_urls"])
self.scraping_max_content_length = int(self.config["webscraping"]["max_content_length"])
self.scraping_user_agent = self.config["webscraping"]["user_agent"]
# AI Provider aus der Konfiguration übernehmen
self.ai_provider = self.config["application"]["ai_provider"].lower()
# Verzeichnisse erstellen
os.makedirs(self.results_dir, exist_ok=True)
os.makedirs(self.upload_dir, exist_ok=True)
# Connector-Instanzen initialisieren
if self.ai_provider == "anthropic":
import connector_aichat_anthropic as service_aichat
self.service_aichat = service_aichat.ChatService()
logger.info("Anthropic AI Provider wird verwendet")
else:
import connector_aichat_openai as service_aichat
self.service_aichat = service_aichat.ChatService()
logger.info("OpenAI AI Provider wird verwendet")
import connector_aiweb_webscraping as service_aiscrap
self.service_aiscrap = service_aiscrap.WebScrapingService()
logger.info(f"AgentService initialisiert mit:")
logger.info(f" - Modell: {self.config['openai']['model_name']}")
logger.info(f" - AI Provider: {self.ai_provider}")
logger.info(f" - Ergebnisverzeichnis: {self.results_dir}")
logger.info(f" - Upload-Verzeichnis: {self.upload_dir}")
# Workflow-Speicher
self.workflows = {}
# HttpClient für API-Aufrufe
self.http_client = httpx.AsyncClient(
timeout=120.0, # Längeres Timeout für komplexe Anfragen
headers={
"Authorization": f"Bearer {self.config['openai']['api_key']}",
"Content-Type": "application/json"
}
)
async def execute_workflow(
self,
@ -106,7 +96,9 @@ class AgentService:
) -> str:
"""
Führt einen Workflow mit den angegebenen Agenten und Dateien aus.
Verwendet OpenAI GPT-4o für die Verarbeitung der Anfragen.
Anstatt die Agenten der Reihe nach abzuarbeiten, wird ein AI-Moderator verwendet,
der die Agenten basierend auf ihren Fähigkeiten und bisherigen Antworten steuert.
"""
logger.info(f"Starte Workflow {workflow_id} mit {len(agents)} Agenten und {len(files)} Dateien")
@ -243,99 +235,292 @@ class AgentService:
self.workflows[workflow_id]["progress"] = 0.1
# Verarbeitung pro Agent ausführen
for i, agent in enumerate(agents):
# Initialisiere den Chatverlauf für den Agenten-Dialog
chat_history = []
# Erstelle das Nachrichtenobjekt für die initialen Dateien und den Prompt
message_content = self.service_aichat.prepare_file_message_content(prompt, file_contexts)
# Füge Dateien als Base64-Anhänge hinzu
for file in file_contexts:
if file["path"] and os.path.exists(file["path"]):
try:
# Datei als Base64 codieren
with open(file["path"], "rb") as f:
file_data = f.read()
base64_data = base64.b64encode(file_data).decode('utf-8')
# MIME-Typ bestimmen
mime_type, _ = mimetypes.guess_type(file["path"])
if not mime_type:
mime_type = "application/octet-stream"
# Füge die Datei als Anhang hinzu
message_content.append({
"type": "file",
"source": {
"type": "base64",
"media_type": mime_type,
"data": base64_data
}
})
except Exception as e:
logger.error(f"Fehler beim Hinzufügen der Datei {file['name']} als Anhang: {str(e)}")
# Nachrichtenobjekt erstellen
initial_message = {
"role": "user",
"content": message_content
}
# Initialen Prompt zum Chatverlauf hinzufügen
chat_history.append(initial_message)
# Initialisiere die verfügbaren Agenten mit ihren Fähigkeiten
available_agents = {}
for agent in agents:
agent_id = agent["id"]
agent_name = agent["name"]
agent_type = agent["type"]
agent_capabilities = agent.get("capabilities", "")
self.workflows[workflow_id]["agent_statuses"][agent_id] = "running"
self._add_log(
workflow_id,
f"Agent '{agent_name}' beginnt mit der Verarbeitung...",
"start",
agent_id,
agent_name
)
available_agents[agent_id] = {
"id": agent_id,
"name": agent_name,
"type": agent_type,
"capabilities": agent_capabilities,
"used": False
}
# Fortschritt aktualisieren
self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents)) * 0.5
# Initialisiere den Status
self.workflows[workflow_id]["agent_statuses"][agent_id] = "pending"
# Initialisiere die Moderator-Rolle - Fester Teil
moderator_prompt_base = """
Du bist der Moderator eines Multi-Agent-Systems. Deine Aufgabe ist es, die Zusammenarbeit zwischen verschiedenen spezialisierten Agenten zu koordinieren, um die Anfrage des Benutzers bestmöglich zu erfüllen.
Du sollst:
1. Die Anfrage des Benutzers verstehen und analysieren
2. Den am besten geeigneten Agenten basierend auf seinen Fähigkeiten auswählen
3. Die Antworten der Agenten überwachen und bewerten
4. Falls nötig, weitere Agenten hinzuziehen, um die Anfrage vollständig zu bearbeiten
5. Den Workflow beenden, wenn die Anfrage vollständig erfüllt wurde
Für jeden Schritt sollst du begründen, warum du einen bestimmten Agenten auswählst, und zusammenfassen, was bisher erreicht wurde.
"""
# Dynamischer Teil - Verfügbare Agenten aus den tatsächlich vorhandenen Agenten
agents_description = "Verfügbare Agenten:\n"
for agent_id, agent in available_agents.items():
agents_description += f"- {agent['name']} (Typ: {agent['type']}): {agent['capabilities']}\n"
moderator_prompt_end = """
Beende den Workflow, wenn die Aufgabe erfüllt ist oder keine weiteren Agenten zur Bearbeitung beitragen können.
"""
# Kombiniere alle Teile
moderator_system_prompt = moderator_prompt_base + "\n" + agents_description + "\n" + moderator_prompt_end
# Starte den Workflow mit dem Moderator
self._add_log(workflow_id, "Starte Agenten-Tischrunde mit Moderator", "info")
# Maximale Anzahl der Runden zur Vermeidung endloser Schleifen
max_rounds = 12
current_round = 0
workflow_complete = False
while current_round < max_rounds and not workflow_complete:
current_round += 1
self._add_log(workflow_id, f"Starte Runde {current_round}", "info")
# Der Moderator wählt den nächsten Agenten aus
moderator_prompt = {
"role": "system",
"content": moderator_system_prompt
}
# Kopie des Chatverlaufs für den Moderator erstellen
moderator_chat = [moderator_prompt] + chat_history[-self.max_history:]
# Füge eine Zusammenfassung der verfügbaren Agenten hinzu
agent_info = "Verfügbare Agenten:\n"
for agent_id, agent in available_agents.items():
status = "✓ Bereits verwendet" if agent["used"] else "✗ Noch nicht verwendet"
agent_info += f"- {agent['name']} (Typ: {agent['type']}): {agent['capabilities']}\n Status: {status}\n"
moderator_chat.append({
"role": "system",
"content": agent_info + "\nWähle den nächsten Agenten aus oder beende den Workflow, wenn die Aufgabe erfüllt ist."
})
# Moderator trifft die Entscheidung
try:
# Agent-spezifische Anweisungen erstellen
agent_instructions = self._get_agent_instructions(agent_type)
moderator_decision = await self.service_aichat.call_api(moderator_chat)
moderator_text = moderator_decision["choices"][0]["message"]["content"]
# Vollständige Anfrage an OpenAI erstellen
full_prompt = f"""
# Aufgabe
{prompt}
# Füge die Entscheidung des Moderators zum Chatverlauf hinzu
chat_history.append({
"role": "assistant",
"content": f"[Moderator] {moderator_text}"
})
# Dateikontexte
{file_context_text}
# Log der Moderator-Entscheidung
self._add_log(workflow_id, f"Moderator-Entscheidung: {moderator_text[:100]}...", "info")
# Agent-Rolle
Du bist ein {agent_name} ({agent_type}).
# Prüfe, ob der Workflow beendet werden soll
if any(phrase in moderator_text.lower() for phrase in ["workflow beenden", "aufgabe erfüllt", "beende den workflow", "workflow abschließen"]):
self._add_log(workflow_id, "Moderator hat den Workflow beendet", "success")
workflow_complete = True
break
{agent_instructions}
# Extrahiere den ausgewählten Agenten
selected_agent_id = None
Bitte analysiere die Daten und beantworte die Anfrage gemäß deiner Rolle.
"""
# Versuche, den ausgewählten Agenten aus dem Text zu extrahieren
for agent_id, agent in available_agents.items():
if agent["name"] in moderator_text or f"Agent {agent_id}" in moderator_text:
selected_agent_id = agent_id
break
# Für Web-Scraper: Führe Web-Scraping durch
if agent_type == "scraper":
self._add_log(workflow_id, "Führe Web-Scraping durch...", "info", agent_id, agent_name)
web_data = await self._scrape_web_data(workflow_id, prompt)
if web_data:
full_prompt += f"\n\n# Gescrapte Web-Daten\n{web_data}"
self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info", agent_id, agent_name)
if not selected_agent_id:
self._add_log(workflow_id, "Moderator konnte keinen Agenten identifizieren", "warning")
# Wähle den ersten nicht verwendeten Agenten
for agent_id, agent in available_agents.items():
if not agent["used"]:
selected_agent_id = agent_id
break
# Wenn alle Agenten bereits verwendet wurden, wähle den Initialisierungs-Agenten
if not selected_agent_id:
for agent_id, agent in available_agents.items():
if agent["type"] == "initialisierung":
selected_agent_id = agent_id
break
# Als letztes Mittel wähle einfach den ersten Agenten
if not selected_agent_id and available_agents:
selected_agent_id = list(available_agents.keys())[0]
# API-Anfrage an OpenAI senden
response = await self._call_openai_api(full_prompt)
if selected_agent_id:
# Agenten aus der Liste markieren
selected_agent = available_agents[selected_agent_id]
selected_agent["used"] = True
# Agenten-Status aktualisieren
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "running"
self._add_log(
workflow_id,
f"Agent '{selected_agent['name']}' beginnt mit der Verarbeitung...",
"start",
selected_agent_id,
selected_agent['name']
)
# Agent-spezifische Anweisungen erstellen
agent_instructions = self._get_agent_instructions(selected_agent["type"])
# Agent-Prompt erstellen
agent_prompt = {
"role": "system",
"content": f"""
# Aufgabe
Du bist ein spezialisierter Agent vom Typ {selected_agent['type']} mit dem Namen {selected_agent['name']}.
{agent_instructions}
Bitte analysiere den Chatverlauf und die Dateien und beantworte die Anfrage gemäß deiner Rolle.
Ausgabeformat:
[Agent: {selected_agent['name']}]
Deine Antwort...
"""
}
# Kopie des Chatverlaufs für den Agenten erstellen
agent_chat = [agent_prompt] + chat_history[-self.max_history:]
# Falls der Agent ein Webscraper ist und Scraping notwendig ist
if selected_agent["type"] == "scraper":
self._add_log(workflow_id, "Führe Web-Scraping durch...", "info", selected_agent_id, selected_agent["name"])
web_data = await self.service_aiscrap.scrape_web_data(prompt)
if web_data:
agent_chat.append({
"role": "system",
"content": f"# Gescrapte Web-Daten\n{web_data}"
})
self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info", selected_agent_id, selected_agent["name"])
# Agent führt seinen Teil aus
try:
agent_response = await self.service_aichat.call_api(agent_chat)
agent_text = agent_response["choices"][0]["message"]["content"]
# Füge die Antwort des Agenten zum Chatverlauf hinzu
chat_history.append({
"role": "assistant",
"content": agent_text
})
# Agent-Ergebnis erstellen
result = self._create_agent_result(
workflow_id,
selected_agent,
len(self.workflows[workflow_id]["results"]),
prompt,
file_contexts,
agent_text
)
self.workflows[workflow_id]["results"].append(result)
self._add_log(
workflow_id,
f"Agent '{selected_agent['name']}' hat die Verarbeitung abgeschlossen",
"complete",
selected_agent_id,
selected_agent["name"]
)
# Agenten-Status aktualisieren
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "completed"
except Exception as e:
logger.error(f"Fehler bei der Ausführung von Agent '{selected_agent['name']}': {str(e)}")
self._add_log(
workflow_id,
f"Fehler bei der Ausführung: {str(e)}",
"error",
selected_agent_id,
selected_agent["name"]
)
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "failed"
# Füge die Fehlermeldung zum Chatverlauf hinzu
chat_history.append({
"role": "assistant",
"content": f"[Fehler bei Agent '{selected_agent['name']}']: {str(e)}"
})
else:
self._add_log(workflow_id, "Kein Agent ausgewählt. Beende Workflow.", "warning")
workflow_complete = True
# Ergebnis extrahieren und speichern
result_content = response.get("choices", [{}])[0].get("message", {}).get("content", "Keine Antwort erhalten")
# Agent-Ergebnis erstellen
result = self._create_agent_result(workflow_id, agent, i, prompt, file_contexts, result_content)
self.workflows[workflow_id]["results"].append(result)
self._add_log(
workflow_id,
f"Agent '{agent_name}' hat die Verarbeitung abgeschlossen",
"complete",
agent_id,
agent_name
)
# Fortschritt aktualisieren
self.workflows[workflow_id]["progress"] = min(0.9, 0.1 + (current_round / max_rounds) * 0.8)
except Exception as e:
logger.error(f"Fehler bei der Ausführung von Agent '{agent_name}': {str(e)}")
self._add_log(
workflow_id,
f"Fehler bei der Ausführung: {str(e)}",
"error",
agent_id,
agent_name
)
self.workflows[workflow_id]["agent_statuses"][agent_id] = "failed"
continue
self.workflows[workflow_id]["agent_statuses"][agent_id] = "completed"
self.workflows[workflow_id]["progress"] = 0.1 + (i + 1) * (0.8 / len(agents))
# Kurze Pause zwischen Agent-Aufrufen
await asyncio.sleep(1)
logger.error(f"Fehler in der Moderator-Phase: {str(e)}")
self._add_log(workflow_id, f"Fehler in der Moderator-Phase: {str(e)}", "error")
break
# Workflow-Status prüfen
failed_agents = [a for a, s in self.workflows[workflow_id]["agent_statuses"].items() if s == "failed"]
if failed_agents:
self._add_log(workflow_id, f"{len(failed_agents)} Agent(s) sind fehlgeschlagen", "error")
self._add_log(workflow_id, "Workflow mit Fehlern beendet", "error")
self.workflows[workflow_id]["status"] = "failed"
else:
self._add_log(workflow_id, "Alle Agenten haben ihre Aufgaben abgeschlossen", "info")
self._add_log(workflow_id, "Workflow erfolgreich beendet", "success")
# Workflow abschließen
if workflow_complete:
self.workflows[workflow_id]["status"] = "completed"
self._add_log(workflow_id, "Workflow erfolgreich beendet", "success")
elif current_round >= max_rounds:
self.workflows[workflow_id]["status"] = "completed"
self._add_log(workflow_id, f"Workflow nach {max_rounds} Runden automatisch beendet", "info")
else:
self.workflows[workflow_id]["status"] = "failed"
self._add_log(workflow_id, "Workflow mit Fehlern beendet", "error")
self.workflows[workflow_id]["progress"] = 1.0
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
@ -343,401 +528,4 @@ class AgentService:
# Speichere Ergebnisse in Datei für spätere Verwendung
self._save_workflow_results(workflow_id)
return workflow_id
# Die übrigen Methoden bleiben unverändert
# ...
async def _scrape_web_data(self, workflow_id: str, prompt: str) -> str:
"""
Führt Web-Scraping basierend auf dem Prompt durch
"""
try:
# Extrahiere mögliche Schlüsselwörter oder URLs aus dem Prompt
keywords = self._extract_keywords(prompt)
urls = self._extract_urls(prompt)
results = []
# Falls direkte URLs im Prompt enthalten sind
if urls:
self._add_log(workflow_id, f"Gefundene URLs: {', '.join(urls[:self.scraping_max_urls])}", "info")
for url in urls[:self.scraping_max_urls]: # Begrenze auf max_urls (Standard: 3)
try:
self._add_log(workflow_id, f"Scrape URL: {url}", "info")
content = self._scrape_url(url)
if content:
results.append(f"## Inhalt von {url}\n{content}")
self._add_log(workflow_id, f"Scraping von {url} erfolgreich", "info")
except Exception as e:
logger.error(f"Fehler beim Scrapen von {url}: {e}")
self._add_log(workflow_id, f"Fehler beim Scrapen von {url}: {e}", "error")
# Falls keine URLs, versuche Suche mit Schlüsselwörtern
elif keywords:
self._add_log(workflow_id, f"Verwende Keywords für Suche: {keywords}", "info")
search_results = self._search_web(keywords)
if search_results:
results.append(f"## Suchergebnisse für: {keywords}\n{search_results}")
self._add_log(workflow_id, "Suche abgeschlossen", "info")
if results:
return "\n\n".join(results)
self._add_log(workflow_id, "Keine relevanten Web-Daten gefunden", "warning")
return "Keine relevanten Web-Daten gefunden."
except Exception as e:
logger.error(f"Fehler beim Web-Scraping: {e}")
self._add_log(workflow_id, f"Fehler beim Web-Scraping: {e}", "error")
return f"Web-Scraping konnte nicht durchgeführt werden: {str(e)}"
def _extract_keywords(self, text: str) -> str:
"""Extrahiert Schlüsselwörter aus dem Text"""
# Einfache Implementierung - in der Praxis könntest du NLP verwenden
words = text.split()
# Filtere kurze Wörter und häufige Stopwörter
stopwords = ["einen", "einer", "eines", "keine", "nicht", "diese", "dieses", "zwischen",
"und", "oder", "aber", "denn", "wenn", "weil", "obwohl", "während", "für",
"mit", "von", "aus", "nach", "bei", "über", "unter", "durch", "gegen"]
keywords = [w for w in words if len(w) > 4 and w.lower() not in stopwords]
return " ".join(keywords[:5]) # Begrenze auf 5 Keywords
def _extract_urls(self, text: str) -> List[str]:
"""Extrahiert URLs aus dem Text"""
# Einfacher URL-Extraktions-Regex
url_pattern = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.-]*(?:\?\S+)?')
return url_pattern.findall(text)
def _scrape_url(self, url: str) -> str:
"""Scrapt den Inhalt einer URL"""
headers = {
'User-Agent': self.scraping_user_agent
}
response = requests.get(url, headers=headers, timeout=self.scraping_timeout)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Entferne Skripte, Styles und andere unwichtige Elemente
for script in soup(["script", "style", "meta", "noscript", "iframe"]):
script.extract()
# Extrahiere den Hauptinhalt
main_content = ""
# Versuche, Hauptcontainer zu finden (häufige IDs und Klassen)
main_elements = soup.select('main, #main, .main, #content, .content, article, .article, .post, #post')
if main_elements:
# Nehme den ersten gefundenen Hauptcontainer
main_content = main_elements[0].get_text(separator='\n', strip=True)
else:
# Falls kein Hauptcontainer gefunden, nehme den Body-Text
main_content = soup.body.get_text(separator='\n', strip=True)
# Bereinige den Text (entferne mehrfache Leerzeilen etc.)
lines = [line.strip() for line in main_content.split('\n') if line.strip()]
main_content = '\n'.join(lines)
# Begrenze die Länge
if len(main_content) > self.scraping_max_content_length:
main_content = main_content[:self.scraping_max_content_length] + "...\n[Inhalt gekürzt]"
return main_content
def _search_web(self, query: str) -> str:
"""
Simuliert eine Websuche (in einer vollständigen Implementierung
würdest du hier eine echte Suchmaschinen-API verwenden)
"""
# HINWEIS: Dies ist eine Simulation! In einer echten Anwendung
# würdest du Google Custom Search API, SerpAPI oder ähnliches verwenden
# Für eine echte Implementierung:
# - Google Custom Search API: https://developers.google.com/custom-search/v1/overview
# - SerpAPI: https://serpapi.com/
# - Oder ähnliche Dienste
return f"Hinweis: Dies ist eine Demo-Implementierung ohne echte Websuche. In der Produktion würde hier der Agent tatsächlich nach '{query}' suchen."
async def _call_openai_api(self, prompt: str) -> Dict[str, Any]:
"""Ruft die OpenAI API auf und gibt die Antwort zurück"""
try:
payload = {
"model": self.config["openai"]["model_name"],
"messages": [
{"role": "system", "content": "Du bist ein spezialisierter Agent in einem Multi-Agent-System zur Datenanalyse und -verarbeitung."},
{"role": "user", "content": prompt}
],
"temperature": 0.2, # Niedrige Temperatur für konsistentere Ergebnisse
"max_tokens": 2000
}
response = await self.http_client.post(
self.config["openai"]["api_url"],
json=payload
)
if response.status_code != 200:
logger.error(f"OpenAI API-Fehler: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Fehler bei der Kommunikation mit OpenAI API")
return response.json()
except Exception as e:
logger.error(f"Fehler beim Aufruf der OpenAI API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Fehler beim Aufruf der OpenAI API: {str(e)}")
def _get_agent_instructions(self, agent_type: str) -> str:
"""
Gibt agententypspezifische Anweisungen zurück, die aus der agents.json geladen werden.
Falls die Datei nicht existiert oder der Agententyp nicht gefunden wird,
werden Standard-Anweisungen zurückgegeben.
"""
try:
# Pfad zur agents.json-Datei
agents_file = os.path.join(os.path.dirname(__file__), 'data', 'agents.json')
# Überprüfen, ob die Datei existiert
if not os.path.exists(agents_file):
logger.warning(f"Agents-Definitionen nicht gefunden: {agents_file}")
return self._get_default_agent_instructions(agent_type)
# Datei lesen
with open(agents_file, 'r', encoding='utf-8') as f:
agents_data = json.load(f)
# Nach dem Agententyp suchen
for agent in agents_data:
if agent.get("type") == agent_type:
# Anweisungen zurückgeben, wenn vorhanden
instructions = agent.get("instructions")
if instructions:
logger.debug(f"Anweisungen für Agent-Typ '{agent_type}' aus agents.json geladen")
return instructions
# Wenn kein passender Agent gefunden wurde, Standardanweisungen verwenden
logger.warning(f"Keine Anweisungen für Agent-Typ '{agent_type}' in agents.json gefunden")
return self._get_default_agent_instructions(agent_type)
except Exception as e:
logger.error(f"Fehler beim Laden der Agent-Anweisungen aus agents.json: {e}")
return self._get_default_agent_instructions(agent_type)
def _get_default_agent_instructions(self, agent_type: str) -> str:
"""
Gibt Standard-Anweisungen für einen Agententyp zurück,
wenn keine spezifischen Anweisungen in der agents.json gefunden wurden.
"""
default_instructions = {
"analyzer": """
Als Datenanalyse-Agent ist es deine Aufgabe, die bereitgestellten Daten zu analysieren und wichtige Erkenntnisse zu extrahieren.
Folge diesen Anweisungen zur Analyse der Dateien:
1. Lese und verstehe den Inhalt der bereitgestellten Dateien gründlich
2. Identifiziere welchen Datentyp jede Datei enthält (z.B. Zeitreihendaten, kategorische Daten, Text)
3. Wenn es sich um tabellarische Daten handelt:
- Identifiziere Muster, Trends und Anomalien
- Berechne relevante statistische Kennzahlen (Mittelwerte, Mediane, Standardabweichungen)
- Suche nach Korrelationen zwischen verschiedenen Spalten
- Identifiziere Ausreißer und ungewöhnliche Datenpunkte
4. Wenn es sich um Textdaten handelt:
- Analysiere Schlüsselthemen und -begriffe
- Identifiziere Stimmung und Tonalität, wenn relevant
- Extrahiere zentrale Aussagen und Schlussfolgerungen
5. Erstelle eine strukturierte Zusammenfassung deiner Erkenntnisse
6. Gib konkrete, datengestützte Empfehlungen, wenn möglich
In deiner Antwort:
- Beginne mit einer kurzen Übersicht der analysierten Daten
- Struktur
- Strukturiere deine Erkenntnisse klar mit Überschriften und Aufzählungen
- Füge quantitative Erkenntnisse ein, wo immer möglich
- Schließe mit einer Zusammenfassung der wichtigsten Punkte ab
""",
"visualizer": """
Als Visualisierungs-Agent ist es deine Aufgabe, die Daten visuell zu beschreiben.
- Beschreibe, welche Art von Visualisierungen für die Daten sinnvoll wären
- Erkläre das Layout und die Komponenten der Visualisierung
- Beschreibe, wie die Daten grafisch dargestellt werden sollten
- Erkläre, welche Erkenntnisse aus dieser Visualisierung gewonnen werden können
""",
"writer": """
Als Text-Generator ist es deine Aufgabe, verständliche Berichte und Zusammenfassungen zu erstellen.
- Fasse die wichtigsten Erkenntnisse aus den Daten zusammen
- Strukturiere den Bericht klar und prägnant
- Verwende eine sachliche und professionelle Sprache
- Formuliere konkrete Empfehlungen basierend auf den Daten
- Nutze Markdown für bessere Formatierung
""",
"scraper": """
Als Web-Scraper-Agent ist es deine Aufgabe, Webseiten zu durchsuchen und relevante Informationen zu extrahieren.
Folge diesen Anweisungen:
1. Analysiere die bereitgestellten Web-Daten sorgfältig
2. Extrahiere die wichtigsten Informationen und Fakten aus den Webinhalten
3. Organisiere die Informationen in klare Kategorien
4. Identifiziere Trends, Muster und wichtige Erkenntnisse
5. Vergleiche die Informationen aus verschiedenen Quellen, wenn verfügbar
6. Fasse die gefundenen Informationen prägnant zusammen
7. Erkenne mögliche Einschränkungen oder Verzerrungen in den Quellen
In deiner Antwort:
- Beginne mit einer Zusammenfassung der gefundenen Informationen
- Strukturiere die extrahierten Daten in logische Abschnitte
- Hebe wichtige Fakten und Zahlen hervor
- Gib Quellenhinweise an
- Formuliere Schlussfolgerungen basierend auf den gesammelten Daten
"""
}
return default_instructions.get(agent_type, "Du bist ein Datenverarbeitungs-Agent. Analysiere die gegebenen Informationen und liefere relevante Erkenntnisse.")
def _add_log(
self,
workflow_id: str,
message: str,
log_type: str,
agent_id: Optional[str] = None,
agent_name: Optional[str] = None
) -> None:
"""Fügt einen Protokolleintrag zum Workflow hinzu"""
log_entry = {
"id": f"log_{uuid.uuid4()}",
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"message": message,
"type": log_type,
"timestamp": datetime.now().isoformat(),
"agent_id": agent_id,
"agent_name": agent_name
}
workflow = self.workflows.get(workflow_id)
if workflow:
workflow["logs"].append(log_entry)
logger.info(f"Workflow {workflow_id}: {message}")
def _create_agent_result(
self,
workflow_id: str,
agent: Dict[str, Any],
index: int,
prompt: str,
file_contexts: List[Dict[str, Any]],
content: str
) -> Dict[str, Any]:
"""Erstellt ein Ergebnisobjekt basierend auf dem Agententyp und der API-Antwort"""
agent_type = agent["type"]
agent_id = agent["id"]
agent_name = agent["name"]
# Grundlegende Ergebnisstruktur
result = {
"id": f"result_{workflow_id}_{index}",
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"agent_id": agent_id,
"agent_name": agent_name,
"timestamp": datetime.now().isoformat(),
"type": "text", # Standardtyp
"metadata": {
"files_processed": [file["name"] for file in file_contexts],
"prompt": prompt
}
}
# Titel und Inhalt basierend auf dem Agententyp anpassen
if agent_type == "analyzer":
result.update({
"title": "Datenanalyse-Ergebnis",
"content": content,
})
elif agent_type == "visualizer":
result.update({
"title": "Visualisierungsvorschlag",
"content": content,
"type": "chart" # Auch wenn kein echtes Diagramm, markieren wir es als solches
})
elif agent_type == "writer":
result.update({
"title": "Zusammenfassung und Empfehlungen",
"content": content,
})
elif agent_type == "scraper":
result.update({
"title": "Web-Recherche Ergebnisse",
"content": content,
})
else:
result.update({
"title": f"Ergebnis von {agent_name}",
"content": content,
})
return result
def _save_workflow_results(self, workflow_id: str) -> None:
"""Speichert die Workflow-Ergebnisse in einer Datei"""
workflow = self.workflows.get(workflow_id)
if workflow:
try:
file_path = os.path.join(self.results_dir, f"workflow_{workflow_id}.json")
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(workflow, f, indent=2, ensure_ascii=False)
logger.info(f"Workflow-Ergebnisse gespeichert: {file_path}")
except Exception as e:
logger.error(f"Fehler beim Speichern der Workflow-Ergebnisse: {e}")
def get_workflow_status(self, workflow_id: str) -> Optional[Dict[str, Any]]:
"""Gibt den Status eines Workflows zurück"""
workflow = self.workflows.get(workflow_id)
if not workflow:
return None
return {
"id": workflow["id"],
"mandate_id": workflow.get("mandate_id"),
"user_id": workflow.get("user_id"),
"status": workflow["status"],
"progress": workflow["progress"],
"started_at": workflow["started_at"],
"completed_at": workflow["completed_at"],
"agent_statuses": workflow["agent_statuses"]
}
def get_workflow_logs(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
"""Gibt die Protokolle eines Workflows zurück"""
workflow = self.workflows.get(workflow_id)
if not workflow:
return None
return workflow["logs"]
def get_workflow_results(self, workflow_id: str) -> Optional[List[Dict[str, Any]]]:
"""Gibt die Ergebnisse eines Workflows zurück"""
workflow = self.workflows.get(workflow_id)
if not workflow:
return None
return workflow["results"]
async def close(self):
"""Schließt den HTTP-Client beim Beenden der Anwendung"""
await self.http_client.aclose()
# Singleton-Factory für AgentService-Instanzen pro Kontext
_agent_service_instances = {}
def get_agentservice_interface(mandate_id: int = None, user_id: int = None) -> AgentService:
"""
Gibt eine AgentService-Instanz für den angegebenen Kontext zurück.
Wiederverwendet bestehende Instanzen.
"""
context_key = f"{mandate_id}_{user_id}"
if context_key not in _agent_service_instances:
_agent_service_instances[context_key] = AgentService(mandate_id, user_id)
return _agent_service_instances[context_key]
return workflow_id

View file

@ -4,7 +4,6 @@ from typing import Dict, Any, List, Optional, Union
import importlib
from passlib.context import CryptContext
from connector_db_json import JSONDatabaseConnector
from modules.gateway_model import User, UserInDB, Token, Mandate
logger = logging.getLogger(__name__)
@ -29,8 +28,8 @@ class GatewayInterface:
user_id: ID des aktuellen Benutzers (optional)
"""
# Bei der Initialisierung kann der Kontext leer sein
self.mandate_id = mandate_id if mandate_id is not None else 1 # Root-Mandant als Standard
self.user_id = user_id if user_id is not None else 1 # Admin-Benutzer als Standard
self.mandate_id = mandate_id
self.user_id = user_id
# Datenverzeichnis
self.data_folder = "_database_gateway"
@ -48,10 +47,36 @@ class GatewayInterface:
# Konnektor erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id
mandate_id=self.mandate_id if self.mandate_id is not None else 0,
user_id=self.user_id if self.user_id is not None else 0
)
# Hole die ID des Root-Mandanten
initial_mandate_id = self.get_initial_id("mandates")
# Aktualisiere den Mandanten-Kontext, falls nötig
if self.mandate_id is None and initial_mandate_id is not None:
self.mandate_id = initial_mandate_id
# Konnektor mit korrektem Kontext neu erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id if self.user_id is not None else 0
)
# Hole die ID des Admin-Benutzers
initial_user_id = self.get_initial_id("users")
# Aktualisiere den Benutzer-Kontext, falls nötig
if self.user_id is None and initial_user_id is not None:
self.user_id = initial_user_id
# Konnektor mit korrektem Kontext neu erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id
)
# Datenbank initialisieren, falls nötig
self._initialize_database()
@ -68,13 +93,21 @@ class GatewayInterface:
logger.info("Erstelle Root-Mandant")
root_mandate = {
"id": 1,
"name": "Root",
"language": "de"
}
self.db.record_create("mandates", root_mandate)
logger.info("Root-Mandant wurde erstellt")
created_mandate = self.db.record_create("mandates", root_mandate)
logger.info(f"Root-Mandant wurde erstellt mit ID {created_mandate['id']}")
# Aktualisiere den Mandanten-Kontext
self.mandate_id = created_mandate['id']
# Konnektor mit korrektem Kontext neu erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id if self.user_id is not None else 0
)
# Prüfe, ob Benutzer existieren
users = self.db.get_recordset("users")
@ -84,8 +117,7 @@ class GatewayInterface:
logger.info("Erstelle Admin-Benutzer")
admin_user = {
"id": 1,
"mandate_id": 1, # Root-Mandant
"mandate_id": self.mandate_id,
"username": "admin",
"email": "admin@example.com",
"full_name": "Administrator",
@ -95,8 +127,29 @@ class GatewayInterface:
"hashed_password": self._get_password_hash("admin") # In der Produktion ein sicheres Passwort verwenden!
}
self.db.record_create("users", admin_user)
logger.info("Admin-Benutzer wurde erstellt")
created_user = self.db.record_create("users", admin_user)
logger.info(f"Admin-Benutzer wurde erstellt mit ID {created_user['id']}")
# Aktualisiere den Benutzer-Kontext
self.user_id = created_user['id']
# Konnektor mit korrektem Kontext neu erstellen
self.db = JSONDatabaseConnector(
db_folder=self.data_folder,
mandate_id=self.mandate_id,
user_id=self.user_id
)
def get_initial_id(self, table: str) -> Optional[int]:
"""
Gibt die initiale ID für eine Tabelle zurück.
Args:
table: Name der Tabelle
Returns:
Die initiale ID oder None, wenn nicht vorhanden
"""
return self.db.get_initial_id(table)
def _get_password_hash(self, password: str) -> str:
"""Erstellt einen Hash für ein Passwort"""
@ -126,14 +179,7 @@ class GatewayInterface:
def create_mandate(self, name: str, language: str = "de") -> Dict[str, Any]:
"""Erstellt einen neuen Mandanten"""
# Bestimme die nächste ID
mandates = self.db.get_recordset("mandates")
next_id = 1
if mandates:
next_id = max(mandate["id"] for mandate in mandates) + 1
mandate_data = {
"id": next_id,
"name": name,
"language": language
}
@ -179,9 +225,10 @@ class GatewayInterface:
if not mandate:
return False
# Root-Mandant darf nicht gelöscht werden
if mandate_id == 1:
logger.warning("Versuch, den Root-Mandanten zu löschen, wurde verhindert")
# Prüfe, ob es der initiale Mandant ist
initial_mandate_id = self.get_initial_id("mandates")
if initial_mandate_id is not None and mandate_id == initial_mandate_id:
logger.warning(f"Versuch, den Root-Mandanten zu löschen, wurde verhindert")
return False
# Finde alle Benutzer des Mandanten
@ -277,24 +324,17 @@ class GatewayInterface:
if existing_user:
raise ValueError(f"Benutzer '{username}' existiert bereits")
# Bestimme die nächste ID
users = self.db.get_recordset("users")
next_id = 1
if users:
next_id = max(user["id"] for user in users) + 1
# Verwende den übergebenen mandate_id oder den aktuellen Kontext
user_mandate_id = mandate_id if mandate_id is not None else self.mandate_id
user_data = {
"id": next_id,
"mandate_id": user_mandate_id,
"username": username,
"email": email,
"full_name": full_name,
"disabled": disabled,
"language": language,
"privilege": privilege, # Neue Eigenschaft
"privilege": privilege,
"hashed_password": self._get_password_hash(password)
}
@ -447,8 +487,9 @@ class GatewayInterface:
if not users:
return False
# Root-Admin (ID 1) darf nicht gelöscht werden
if user_id == 1:
# Prüfe, ob es der initiale Benutzer ist
initial_user_id = self.get_initial_id("users")
if initial_user_id is not None and user_id == initial_user_id:
logger.warning("Versuch, den Root-Admin zu löschen, wurde verhindert")
return False

View file

@ -61,21 +61,32 @@ class LucyDOMInterface:
# Erstelle den Default Workspace
default_workspace = {
"id": 1,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": "Default Workspace",
"created_at": self._get_current_timestamp()
}
self.db.record_create("workspaces", default_workspace)
logger.info("Default Workspace wurde erstellt")
created_workspace = self.db.record_create("workspaces", default_workspace)
logger.info(f"Default Workspace wurde erstellt mit ID {created_workspace['id']}")
def _get_current_timestamp(self) -> str:
"""Gibt den aktuellen Zeitstempel im ISO-Format zurück"""
from datetime import datetime
return datetime.now().isoformat()
def get_initial_id(self, table: str) -> Optional[int]:
"""
Gibt die initiale ID für eine Tabelle zurück.
Args:
table: Name der Tabelle
Returns:
Die initiale ID oder None, wenn nicht vorhanden
"""
return self.db.get_initial_id(table)
# Workspace-Methoden
def get_all_workspaces(self) -> List[Dict[str, Any]]:
@ -91,14 +102,7 @@ class LucyDOMInterface:
def create_workspace(self, name: str) -> Dict[str, Any]:
"""Erstellt einen neuen Workspace"""
# Bestimme die nächste ID
workspaces = self.db.get_recordset("workspaces")
next_id = 1
if workspaces:
next_id = max(workspace["id"] for workspace in workspaces) + 1
workspace_data = {
"id": next_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name,
@ -125,11 +129,7 @@ class LucyDOMInterface:
# Daten für die Aktualisierung vorbereiten
workspace_data = {
"id": workspace_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name,
"created_at": workspace.get("created_at")
"name": name
}
# Workspace aktualisieren
@ -141,6 +141,12 @@ class LucyDOMInterface:
Returns:
True, wenn der Workspace erfolgreich gelöscht wurde, sonst False
"""
# Prüfen, ob es der initiale Workspace ist
initial_workspace_id = self.get_initial_id("workspaces")
if initial_workspace_id is not None and workspace_id == initial_workspace_id:
logger.warning("Versuch, den Default Workspace zu löschen, wurde verhindert")
return False
return self.db.record_delete("workspaces", workspace_id)
# Agent-Methoden
@ -163,14 +169,7 @@ class LucyDOMInterface:
def create_agent(self, name: str, agent_type: str, workspace_id: int,
capabilities: str = None, description: str = None) -> Dict[str, Any]:
"""Erstellt einen neuen Agenten"""
# Bestimme die nächste ID
agents = self.db.get_recordset("agents")
next_id = 1
if agents:
next_id = max(agent["id"] for agent in agents) + 1
agent_data = {
"id": next_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name,
@ -205,9 +204,6 @@ class LucyDOMInterface:
# Daten für die Aktualisierung vorbereiten
agent_data = {
"id": agent_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name,
"type": agent_type,
"workspace_id": workspace_id,
@ -248,14 +244,7 @@ class LucyDOMInterface:
def create_file(self, name: str, file_type: str, content_type: str = None,
size: int = None, path: str = None) -> Dict[str, Any]:
"""Erstellt einen neuen Dateieintrag"""
# Bestimme die nächste ID
files = self.db.get_recordset("files")
next_id = 1
if files:
next_id = max(file["id"] for file in files) + 1
file_data = {
"id": next_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name,
@ -290,17 +279,18 @@ class LucyDOMInterface:
return None
# Daten für die Aktualisierung vorbereiten
file_data = {
"id": file_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"name": name if name is not None else file.get("name"),
"type": file_type if file_type is not None else file.get("type"),
"content_type": content_type if content_type is not None else file.get("content_type"),
"size": size if size is not None else file.get("size"),
"path": path if path is not None else file.get("path"),
"upload_date": file.get("upload_date")
}
file_data = {}
if name is not None:
file_data["name"] = name
if file_type is not None:
file_data["type"] = file_type
if content_type is not None:
file_data["content_type"] = content_type
if size is not None:
file_data["size"] = size
if path is not None:
file_data["path"] = path
# Datei aktualisieren
return self.db.record_modify("files", file_id, file_data)
@ -328,14 +318,7 @@ class LucyDOMInterface:
def create_prompt(self, content: str, workspace_id: int) -> Dict[str, Any]:
"""Erstellt einen neuen Prompt"""
# Bestimme die nächste ID
prompts = self.db.get_recordset("prompts")
next_id = 1
if prompts:
next_id = max(prompt["id"] for prompt in prompts) + 1
prompt_data = {
"id": next_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"content": content,
@ -363,14 +346,12 @@ class LucyDOMInterface:
return None
# Daten für die Aktualisierung vorbereiten
prompt_data = {
"id": prompt_id,
"mandate_id": self.mandate_id,
"user_id": self.user_id,
"content": content if content is not None else prompt.get("content"),
"workspace_id": workspace_id if workspace_id is not None else prompt.get("workspace_id"),
"created_at": prompt.get("created_at")
}
prompt_data = {}
if content is not None:
prompt_data["content"] = content
if workspace_id is not None:
prompt_data["workspace_id"] = workspace_id
# Prompt aktualisieren
return self.db.record_modify("prompts", prompt_id, prompt_data)
@ -401,5 +382,5 @@ def get_lucydom_interface(mandate_id: int = 0, user_id: int = 0) -> LucyDOMInter
_lucydom_interfaces[context_key] = LucyDOMInterface(mandate_id, user_id)
return _lucydom_interfaces[context_key]
#Init
# Init
get_lucydom_interface()

View file

@ -85,4 +85,78 @@ async def get_prompt(
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
)
return prompt
return prompt
@router.put("/{prompt_id}", response_model=Dict[str, Any])
async def update_prompt(
prompt_id: int,
prompt_data: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestehenden Prompt aktualisieren"""
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Benutzerkontext initialisieren
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Prüfe, ob der Prompt existiert
existing_prompt = lucy_interface.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
)
# Wenn workspace_id vorhanden ist, prüfe, ob der Workspace existiert
workspace_id = prompt_data.get("workspace_id")
if workspace_id:
workspace = lucy_interface.get_workspace(workspace_id)
if not workspace:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workspace mit ID {workspace_id} nicht gefunden"
)
updated_prompt = lucy_interface.update_prompt(
prompt_id=prompt_id,
content=prompt_data.get("content"),
workspace_id=workspace_id
)
if not updated_prompt:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Aktualisieren des Prompts"
)
return updated_prompt
@router.delete("/{prompt_id}", response_model=Dict[str, Any])
async def delete_prompt(
prompt_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen Prompt löschen"""
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Benutzerkontext initialisieren
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Prüfe, ob der Prompt existiert
existing_prompt = lucy_interface.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt mit ID {prompt_id} nicht gefunden"
)
success = lucy_interface.delete_prompt(prompt_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Löschen des Prompts"
)
return {"message": f"Prompt mit ID {prompt_id} wurde erfolgreich gelöscht"}

View file

@ -59,4 +59,75 @@ async def create_workspace(
lucy_interface = get_lucydom_interface(mandate_id, user_id)
new_workspace = lucy_interface.create_workspace(name=workspace.get("name", "Neuer Workspace"))
return new_workspace
return new_workspace
@router.put("/{workspace_id}", response_model=Dict[str, Any])
async def update_workspace(
workspace_id: int,
workspace: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen bestehenden Workspace aktualisieren"""
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Benutzerkontext initialisieren
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Prüfe, ob der Workspace existiert
existing_workspace = lucy_interface.get_workspace(workspace_id)
if not existing_workspace:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workspace mit ID {workspace_id} nicht gefunden"
)
updated_workspace = lucy_interface.update_workspace(
workspace_id=workspace_id,
name=workspace.get("name", existing_workspace.get("name"))
)
if not updated_workspace:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Aktualisieren des Workspaces"
)
return updated_workspace
@router.delete("/{workspace_id}", response_model=Dict[str, Any])
async def delete_workspace(
workspace_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Einen Workspace löschen"""
mandate_id, user_id = await get_user_context(current_user)
# LucyDOM-Interface mit Benutzerkontext initialisieren
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Prüfe, ob der Workspace existiert
existing_workspace = lucy_interface.get_workspace(workspace_id)
if not existing_workspace:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workspace mit ID {workspace_id} nicht gefunden"
)
# Prüfe, ob es der initiale Workspace ist
initial_workspace_id = lucy_interface.get_initial_id("workspaces")
if initial_workspace_id == workspace_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Der Default Workspace kann nicht gelöscht werden"
)
success = lucy_interface.delete_workspace(workspace_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Fehler beim Löschen des Workspaces"
)
return {"message": f"Workspace mit ID {workspace_id} wurde erfolgreich gelöscht"}

View file

@ -1,3 +1,79 @@
.......................... Tasks
Kannst du mir bitte folgende anpassungen am frontend code machen:
Bei der Ausgabe eines datensatzes ans frontend formular soll die ID immer als schreibgeschützt mitgegeben werden. Es wird nie eine ID gesetzt bei Create.
Im Frontend soll im generischen Formular "generic-entity.js" für ein neues Objekt die ID entweder hidden oder schreibgeschützt sein. die ID wird nicht benötigt, sondern wird erst mit dem speichern in der datenbank erstellt. d.h. nach dem speichern in der datenbank werden die daten der entsprechenden tabelle neu geladen.
In den Einstellungen des Frontends soll die Sprache des aktiven benutzers gemäss den Listenoptionen in den "...model.py" angepasst werden können. die sprache gilt dann auch für die Attributnamen in einem Formularfeld im "generic-entity.js". eine sprachänderung zieht somit eine anpassung des Users über das API nach sich, indem die Sprache in der Datenbank angepasst wird.
Chat mit Instant message - auch inputs geben während der ausführung
Sprache spezialisierten
Admin Seite mit CRUD für User Mgmt und Mandate Management, generisch
--------------------------- OPEN
----------------------- done
Kannst du mir bitte code struktur und logik das 'agentservice_interface.py' anpsssen und die code struktur zur besseren wartung und weiterenwticklung verbessern:
1. die anbindung der ai-modelle mit den entsprechenden config-daten und den funktionsaufrufen in separate dateien auslagern ("connector_ai_openai","connector_ai_webscraping"). im 'agentservice_interface.py' die connector module bei der initialisierung importieren und vorbereiten.
2. den agenten-chat 'execute_workflow' nicht in der reihenfolge der agents ausführen, sondern als tischrunde der agents.das heisst ein AI moderator moderiert die agenten autonom und ruft anhand der produzierten antworten und der eigenschaften der agentss den jeweils nächsten geeigneten agenten anhand der 'capabilities' auf, nachdem ein agent seine antwort geliefert hat.
der initiale prompt mit den zugehörigen files und dem chatverlauf im 'LogEntry' mit den n letzten Datensätzen (n wird aus dem Config file aus der variablen Application.MAX_HISTORY gelesen) wird in ein 'message'-objekt als dictionary transformiert, welches so aussieht:
message = {
"role": "user", #--> statisch, immer so
"content": [ #--> liste der Files
{
"type": "text",
"text": prompt_text
},
{
"type": content_type, # --> diese funktion integrieren wir später
"source": {
"type": "base64",
"media_type": mime_type,
"data": base64_file # --> hier das dateiname der jeweiligen datei
}
},
{
"type": "text",
"text": LogEntries # --> hier die LogEinträge als Textpaket
}
]
}
wenn der AI moderator der Meinung ist, dass die aufgabe erfüllt ist, beendet er den workflow.
3. initialisierungsset: beantwortet Anfragen direkt mit dem hinterlegten KI Modell, welche keine spezialisierten Agenten benötigen. Dies ist die Generierung von Text, Code, Strukturen, die Analyse von Files, Graphiken erstellen, etc.
(Agent) Organisator: Dieser analysiert den User Prompt und strukturiert die auszuführenden Aufräge sowie die nötigen zu liefernden Resultate
(Agent) Entwickler: Dieser entwickelt python code im Auftrag der anderen Agents und führt ihn anschliessend aus
(Agent) Webscrape: Ein Agent, welcher webscraping durchführt. Dieser nutzt die Funktion '_scrape_url', um eine Webseite zu scannen und den Inhalt zurückzugeben. Er kann auch den Entwickler beauftragen, einen Code zu generieren, welcher die funktion _scrape_url mit einer logik (z.B. iterativ oder batch-mässig) ausführt
(Prompt): Kannst Du mir ein paar initiale Prompts für die folgenden Fragebereiche vorbereiten, welche ausgewählt werden können:
. Web Research
. Analyse
. Protokoll
. Design
4. Kannst Du bitte die fehlenden CRUD Methoden in den modulen "workspaces" und "prompts" ergänzen. Ich glaube, es fehlen Post und Delete.
5. Datenbank-Management verbessern: In den zwei Modulen "gateway_interface.py" und "lucydom_interface" finden keine Manipulationen oder Referenzierungen mit ID's statt. Die ID's für einen neuen Datensatz werden nur in "connector_....py" modulen vergeben. Jeder datensatz hat eine unique id. in den modulen "...interface.py" werden keine id's generiert. die abfrage für die id=1 wird ersetzt mit der funktion 'get_initial_id', welche weiter unten erklärt ist.
Dazu bitte die Module anpassen und in den Modulen "connector...py" eine system-tabelle ergänzen, welche sich merkt, welche ID der erste datensatz jeder tabelle hat, denn dieser ist der jeweilige system-datensatz. dann eine funktion 'get_initial_id' erfassen, welche in den modulen Modulen "gateway_interface.py" und "lucydom_interface" aufgerufen werden kann, um die id des initialen datensatzes pro tabelle abzufragen.
der gateway funktioniert noch nicht ganz.
kannst mir bitte die module prüfen und besser stukturieren?
@ -67,82 +143,3 @@ agent_service.py: Umbenennen in "interface_agentservice.py"
- Ziel soll es sein, dass die Endpunkte-Codestruktur maximal schlank und übersichtlich ist, also auch die Strukturierung und Gruppierung der Endpunkte
Summary of the Restructuring
I've completely restructured the gateway code according to your specifications. Here's what I've done:
1. Model Files
model_gateway.py: Created this file for user, auth, and mandate models.
model_lucydom.py: Renamed from models.py with all application-specific models.
Enhanced both model files to include labels with multi-language support for all classes and attributes.
Changed all IDs from string to long integers as requested.
Made all objects tenant-capable by adding mandate_id and user_id to each model.
2. Database Connector
connector_db_json.py: Created a generic connector that provides standard database operations (get_tables, get_fields, get_schema, etc.).
Added automatic filtering by mandate_id for data isolation.
Added proper error handling for case when a user tries to access data from another mandate.
3. Interface Files
interface_lucydom.py: Created an interface for the LucyDOM application logic.
interface_gateway.py: Created an interface for the authentication and user management.
interface_agentservice.py: Renamed from agent_service.py with better structure.
Added proper context handling (mandate_id and user_id) throughout all interfaces.
4. Authentication
auth.py: Centralized all authentication logic from app.py into this file.
Added better token handling with mandate context.
5. Main Application (app.py)
Completely restructured to use the new interfaces.
Simplified endpoint implementations by leveraging the interface functionality.
Made all endpoints context-aware (tenant-aware).
Added new endpoints for mandate management.
6. Overall Structure
Improved the code structure to be more modular and maintainable.
Made the system properly tenant-aware at all levels.
Enhanced error handling and logging throughout the codebase.
Made database initialization logic more robust.
Key Improvements
Better Separation of Concerns: Clear distinction between models, data access, and business logic.
Multi-Tenancy: All operations now respect tenant boundaries.
Enhanced Type Safety: More explicit typing and model definitions.
Improved Maintainability: Modular code structure makes it easier to maintain and extend.
Centralized Authentication: All auth logic in one place for better security.
This structure will allow for easier future expansion and maintenance of the system.
.......................... Tasks
Kannst du mir bitte folgende anpassungen am code machen:
Beim gateway_model.py "User" das Attribut "role" ergänzen mit allen Details. Dies ist die Berechtigungsrolle, sie kann eine dieser Optionen sein: [root,admin,user]. Wenn kein User vorhanden ist, wird ein User mit der Rolle "root" erzeugt. dieser wird dann auch gleich als aktiver user genutzt.
Im Datenmodell den Datentyp "Lookup" oder ähnlich ergänzen, für Felder mit Auswahl, so wie die Auswahlobjekte für Berechtigungsrolle als Beispiel
Im "lucydom_interface.py" einen default workspace erstellen, falls keiner vorhanden ist. dieser soll mit den credentials des angemendeten users stattfinden.
unique id's bei datenobjekten in einer tabelle sicherstellen. über alle datensätze ist eine id eindeutig über mandanten hinweg. das management der id's soll im Connector "connector_bd_json.py" erfolgen. In den "...interface.py" dies rausnehmen. wenn ein neues datenobjekt erstellt wird, so erhält es die nächste verfügbare id. hier soll sichergestellt werden, dass bei parallelen funktionsaufrufen von mehreren Users nicht eine id doppelt gesetzt wird.
Bei der Ausgabe eines datensatzes soll die ID immer als schreibgeschützt mitgegeben werden.
Im Frontend soll im generischen Formular "generic-entity.js" für ein neues Objekt die ID entweder hidden oder schreibgeschützt sein. die ID wird nicht benötigt, sondern wird erst mit dem speichern in der datenbank erstellt. d.h. nach dem speichern in der datenbank werden die daten der entsprechenden tabelle neu geladen.
In den Einstellungen des Frontends soll die Sprache des aktiven benutzers gemäss den Listenoptionen in den "...model.py" angepasst werden können. die sprache gilt dann auch für die Attributnamen in einem Formularfeld im "generic-entity.js". eine sprachänderung zieht somit eine anpassung des Users über das API nach sich, indem die Sprache in der Datenbank angepasst wird.

122
gwserver/xxxxx_ant_call.py Normal file
View file

@ -0,0 +1,122 @@
from anthropic import Anthropic
import base64
import magic
import os
from typing import Dict, Any, Union, List
def create_message_with_document(file_path: str, prompt_text: str = "Bitte analysiere dieses Dokument:") -> Dict[str, Any]:
"""
Erstellt ein Message-Objekt für die Anthropic API, das ein Dokument enthält.
Args:
file_path: Pfad zur Datei
prompt_text: Text, der zusammen mit dem Dokument gesendet werden soll
Returns:
Ein Message-Objekt für die Anthropic API
"""
# Datei einlesen und als Base64 kodieren
with open(file_path, "rb") as file:
file_content = file.read()
base64_file = base64.b64encode(file_content).decode('utf-8')
# Mime-Typ der Datei mit python-magic erkennen
mime_type = magic.from_buffer(file_content, mime=True)
# Fallback auf Dateiendung, wenn magic keine klare Erkennung liefert
if mime_type == "application/octet-stream":
extension = os.path.splitext(file_path)[1].lower()[1:]
mime_type = get_mime_type_from_extension(extension)
# Message-Objekt erstellen
content_type, message_structure = determine_content_structure(mime_type)
message = {
"role": "user",
"content": [
{
"type": "text",
"text": prompt_text
},
{
"type": content_type,
"source": {
"type": "base64",
"media_type": mime_type,
"data": base64_file
}
}
]
}
return message
def determine_content_structure(mime_type: str) -> tuple[str, str]:
"""
Bestimmt den richtigen content_type und die Nachrichtenstruktur basierend auf dem MIME-Typ.
Args:
mime_type: Der MIME-Typ der Datei
Returns:
Tuple mit (content_type, message_structure)
"""
# Bildtypen
if mime_type.startswith("image/"):
return "image", "image"
# Dokumenttypen
document_types = [
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", # docx
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # xlsx
"application/vnd.openxmlformats-officedocument.presentationml.presentation", # pptx
"application/vnd.ms-excel",
"application/vnd.ms-powerpoint",
"application/msword",
"text/csv",
"text/plain",
"application/json",
"application/xml",
"text/html"
]
if any(mime_type.startswith(dt) for dt in document_types) or mime_type in document_types:
return "document", "document"
# Fallback für unbekannte Typen
return "document", "document"
def get_mime_type_from_extension(extension: str) -> str:
"""
Bestimmt den MIME-Typ basierend auf der Dateiendung.
Args:
extension: Die Dateiendung ohne Punkt
Returns:
Der entsprechende MIME-Typ
"""
extension_to_mime = {
"pdf": "application/pdf",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"doc": "application/msword",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xls": "application/vnd.ms-excel",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"ppt": "application/vnd.ms-powerpoint",
"csv": "text/csv",
"txt": "text/plain",
"json": "application/json",
"xml": "application/xml",
"html": "text/html",
"htm": "text/html",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"png": "image/png",
"gif": "image/gif",
"webp": "image/webp",
"svg": "image/svg+xml"
}
return extension_to_mime.get(extension, "application/octet-stream")

View file

@ -58,6 +58,8 @@ aiofiles>=23.1.0 # Async file operations
# AI and NLP
openai>=0.27.4 # OpenAI API client
anthropic
python-magic
#nltk>=3.8.1 # Natural Language Toolkit
#scikit-learn>=1.2.2 # For machine learning utilities
#spacy>=3.5.2 # For advanced NLP