feat: agent platform initial commit

- FastAPI + HTMX UI: dashboard, agents CRUD, chat, audit, tools
- SQLite schema: agents, conversations, messages, audit, sessions
- Code-agent sync (agents/*.py -> DB on startup)
- MCP client with health check
- Token auth (Bearer + session)
- LiteLLM integration via llm.py
- Docker Compose: agent-platform + mcp-tools services
- Smoke tests pass: /health 200, /api/agents 200, / 401
This commit is contained in:
Leopold
2026-07-05 22:18:00 +02:00
commit 64b840c94c
30 changed files with 2690 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
"""Agent-Loader: scannt agents/ Ordner und lädt alle BaseAgent-Subklassen."""
import importlib
import inspect
import logging
from pathlib import Path
from typing import Dict, List
from agents.base import BaseAgent, AgentConfig
logger = logging.getLogger("agents.loader")
def discover_agents() -> Dict[str, BaseAgent]:
"""Scannt das agents/ Verzeichnis und gibt alle Agent-Instanzen zurück."""
agents: Dict[str, BaseAgent] = {}
agents_dir = Path(__file__).parent
for py_file in agents_dir.glob("*.py"):
if py_file.name.startswith("_") or py_file.name in ("base.py", "__init__.py"):
continue
module_name = f"agents.{py_file.stem}"
try:
module = importlib.import_module(module_name)
except Exception as e:
logger.warning(f"Konnte {module_name} nicht laden: {e}")
continue
for name, obj in inspect.getmembers(module, inspect.isclass):
if obj is BaseAgent:
continue
if issubclass(obj, BaseAgent) and obj.__module__ == module_name:
try:
instance = obj()
if hasattr(instance, "config"):
agents[instance.config.id] = instance
logger.info(f"Agent geladen: {instance.config.id} ({instance.config.name})")
except Exception as e:
logger.warning(f"Konnte Agent {name} aus {module_name} nicht instanziieren: {e}")
return agents
def get_agent(agent_id: str) -> BaseAgent:
"""Holt einen Agent by ID."""
agents = discover_agents()
if agent_id not in agents:
raise KeyError(f"Agent '{agent_id}' nicht gefunden. Verfügbar: {list(agents.keys())}")
return agents[agent_id]
def list_agents() -> List[AgentConfig]:
"""Gibt Configs aller Agents zurück."""
return [a.config for a in discover_agents().values()]
+37
View File
@@ -0,0 +1,37 @@
"""Pydantic-Schema für hardcoded Agents.
Beispiel:
# agents/recherche.py
from agents.base import BaseAgent, AgentConfig
class RechercheAgent(BaseAgent):
config = AgentConfig(
id="recherche",
name="Recherche-Assistent",
description="Recherchiert Themen und fasst zusammen.",
system_prompt="Du bist ein Recherche-Assistent...",
allowed_tools=["http_get"],
)
"""
from abc import ABC
from typing import List, Optional
from pydantic import BaseModel, Field
class AgentConfig(BaseModel):
"""Type-safe Definition eines hardcoded Agents."""
id: str = Field(..., pattern=r'^[a-z0-9_-]+$', min_length=1, max_length=64)
name: str = Field(..., min_length=1)
description: str = ""
system_prompt: str = Field(..., min_length=1)
allowed_tools: List[str] = []
enabled: bool = True
model: Optional[str] = None
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
max_tokens: int = Field(default=2000, ge=100, le=32000)
class BaseAgent(ABC):
"""Marker-Base für Code-Agents. Setze `config` als Class-Variable."""
config: AgentConfig
+26
View File
@@ -0,0 +1,26 @@
"""Beispiel-Agent: zeigt wie ein Agent definiert wird."""
from agents.base import BaseAgent, AgentConfig
class ExampleAgent(BaseAgent):
"""Generischer Beispiel-Agent mit Tool-Zugriff."""
config = AgentConfig(
id="example",
name="Beispiel-Agent",
description="Demonstriert einen hardcoded Agent mit Tool-Zugriff.",
system_prompt="""Du bist ein hilfreicher KI-Assistent.
Du hast Zugriff auf folgende Tools:
- echo: Gibt einen Text zurück
- get_time: Aktuelle Zeit
- calculate: Mathematische Berechnungen
- http_get: HTTP-Requests
- list_env: Umgebungsvariablen
- json_format: JSON formatieren
Antworte immer auf Deutsch, präzise und freundlich.
Wenn du etwas nicht weißt, sage es ehrlich.""",
allowed_tools=["echo", "get_time", "calculate", "http_get", "list_env", "json_format"],
enabled=True,
)