19 KiB
| stepsCompleted | inputDocuments | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Architektur-Entscheidungsdokument — CouncilOS
Autor: KI-Konzil Dev-Team Datum: 2026-03-12 Version: 1.0.0 Bezug: PRD v1.0.0
1. Architektur-Überblick
CouncilOS ist eine Full-Stack-Webanwendung bestehend aus einem FastAPI-Backend (Python), einem Next.js-Frontend (TypeScript) und einer PostgreSQL-Datenbank. Die Kernintelligenz liegt im LangGraph-Engine, der zyklische Multi-Agenten-Graphen ausführt.
┌─────────────────────────────────────────────────────────────────┐
│ Browser (Next.js) │
│ ┌─────────────────────┐ ┌──────────────────────────────┐ │
│ │ Rat-Architekt Tab │ │ Konferenzzimmer Tab │ │
│ │ (React Flow Canvas)│ │ (Execution View) │ │
│ └─────────────────────┘ └──────────────────────────────┘ │
└─────────────┬───────────────────────────┬───────────────────────┘
│ REST (HTTP/JSON) │ WebSocket
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ FastAPI Backend │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ REST Routes │ │ WebSocket Route │ │ Background Tasks│ │
│ └──────────────┘ └──────────────────┘ └──────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ LangGraph Engine │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Master Agent│ │ Critic Agent│ │ Writer Agent │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │ │
│ │ └────────────────┼──────────────────┘ │ │
│ │ CouncilState (TypedDict) │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
PostgreSQL ChromaDB LLM APIs
(Blueprints) (PDF-Vektor) (Anthropic/OpenAI)
2. Architektur-Entscheidungen (ADRs)
ADR-001: LangGraph als KI-Orchestrierungs-Framework
Kontext: Wir benötigen ein Framework, das echte Zyklen (Loops) in KI-Agenten-Pipelines unterstützt und Human-in-the-Loop ermöglicht.
Entscheidung: LangGraph (Python) wird als einziges KI-Orchestrierungs-Framework verwendet.
Begründung:
- LangGraph unterstützt native Zyklen (A → B → A) ohne Workarounds.
interrupt_beforefür Human-in-the-Loop ist eingebaut.CouncilStateals TypedDict mitoperator.add-Reducern für sichere State-Akkumulation.- Andere Frameworks (LangChain, AutoGen) unterstützen keine echten Zyklen.
Konsequenzen: LangGraph-Versionen ≥ 0.2.x sind Voraussetzung. Synchrone graph.invoke()-Aufrufe werden in Thread-Pools ausgeführt, um den FastAPI-Event-Loop nicht zu blockieren.
ADR-002: FastAPI mit asyncio + Background Tasks
Kontext: LangGraph-Runs können mehrere Minuten dauern und müssen nicht-blockierend ausgeführt werden.
Entscheidung: FastAPI mit BackgroundTasks + asyncio.get_event_loop().run_in_executor() für alle LangGraph-Aufrufe.
Begründung:
- Background Tasks ermöglichen sofortige HTTP-Antwort (202 Accepted) mit
run_id. run_in_executor()verhindert Blockierung des Event Loops durch synchrones LangGraph.- Clients können per WebSocket live-Updates empfangen oder per GET pollen.
Konsequenzen: Run-Status wird in einem In-Memory-Store (run_store) zwischen Tasks geteilt. Bei Neustart des Servers gehen laufende Runs verloren — akzeptabel für MVP.
ADR-003: WebSocket für Echtzeit-Agent-Events
Kontext: Das Frontend muss in Echtzeit wissen, welcher Agent gerade arbeitet, damit der Node im Canvas pulsiert.
Entscheidung: Dedizierter WebSocket-Endpoint /ws/council/{run_id} pro Council-Run.
Begründung:
- Polling ist nicht akzeptabel (zu hohe Latenz, unnötige Serverlast).
- WebSockets ermöglichen Push-Events innerhalb von 500 ms.
- Jede
run_idhat eine eigene isolierte WebSocket-Session — kein Event-Crossover.
Konsequenzen: WebSocket-Sessions werden in einem connection_manager verwaltet. Verbindungsabbrüche müssen graceful behandelt werden.
ADR-004: Dynamischer Graph-Builder (Phase 3)
Kontext: Ab Phase 3 muss der LangGraph-Graph aus einem JSON-Blueprint dynamisch aufgebaut werden.
Entscheidung: dynamic_graph_builder.py interpretiert das Blueprint-JSON und konstruiert den StateGraph zur Laufzeit.
Begründung:
- Hartcodierte Graphen in Produktion verstoßen gegen die Kern-Design-Constraint.
- Das Blueprint-JSON ist das kanonische Austauschformat zwischen Frontend und Backend.
- Zyklen müssen im JSON darstellbar sein und dürfen nicht zu DAGs vereinfacht werden.
Konsequenzen: Blueprint-JSON muss versioniert sein (version-Feld). Ungültige Blueprints müssen mit klaren Fehlermeldungen abgelehnt werden.
ADR-005: PostgreSQL + JSONB für Blueprints
Kontext: Blueprint-Konfigurationen sind komplexe, verschachtelte Objekte, die sich über die Zeit ändern.
Entscheidung: PostgreSQL 16 mit JSONB-Spalte für Blueprint-Daten.
Begründung:
- JSONB ermöglicht flexible Schema-Entwicklung ohne Migrationszwang bei jedem neuen Feld.
- PostgreSQL ist für alle anderen relationalen Daten (Council-Run-Verlauf) bereits vorhanden.
- Ein
version-Feld im JSONB ermöglicht Schema-Versionierung.
Konsequenzen: Alembic verwaltet Datenbankmigrationen. SQLAlchemy (async) wird als ORM verwendet.
ADR-006: ChromaDB für PDF-Vektor-Suche
Kontext: Agents müssen auf Inhalte aus hochgeladenen PDFs zugreifen können.
Entscheidung: ChromaDB (lokal, persistent) als Vektor-Datenbank.
Begründung:
- Kostenlos, lokal betreibbar, keine externe API.
PersistentClientüberlebt Server-Neustarts.- Cosine-Similarity für semantische Suche eingebaut.
Konsequenzen: CHROMA_PERSIST_DIR Umgebungsvariable muss gesetzt sein. In Docker Compose wird ein Named Volume verwendet.
3. Datenmodell
3.1 CouncilState (TypedDict)
class CouncilState(TypedDict):
input_topic: str # Ursprungsthema / PDF-Inhalt des Nutzers
current_draft: str # Das aktuell bearbeitete Dokument
feedback_history: Annotated[List[str], operator.add] # Akkumuliertes Kritiker-Feedback (append-only)
route_decision: str # Routing-Signal: "rework" | "approve" | benutzerdefiniert
messages: Annotated[list, operator.add] # LLM-Nachrichtenverlauf (akkumulierend)
iteration_count: int # Anzahl Rework-Schleifen
critic_score: Optional[float] # Numerische Bewertung (0–10)
run_id: str # Eindeutige Run-ID (für WebSocket-Events)
active_node: str # Name des aktuell aktiven Agent-Nodes
Wichtig: feedback_history und messages verwenden operator.add als Reducer — sie werden niemals überschrieben, nur angehängt.
3.2 Blueprint-JSON-Schema
{
"version": "1.0",
"name": "Mein Content-Rat",
"nodes": [
{
"id": "node-1",
"type": "agent",
"name": "Master KI",
"system_prompt": "Du bist ein erstklassiger Content-Writer...",
"model": "claude-3-5-sonnet-20241022",
"tools": {
"web_search": false,
"pdf_reader": false
}
}
],
"edges": [
{
"id": "edge-1",
"source": "node-1",
"target": "node-2",
"type": "linear"
},
{
"id": "edge-2",
"source": "node-2",
"target": "node-1",
"type": "conditional",
"condition": "rework"
}
]
}
3.3 Datenbankschema (PostgreSQL)
Tabelle blueprints:
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
UUID | Primärschlüssel |
name |
VARCHAR(255) | Name des Councils |
data |
JSONB | Blueprint-JSON |
created_at |
TIMESTAMP | Erstellungszeitpunkt |
updated_at |
TIMESTAMP | Letzter Update |
Tabelle council_runs:
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
UUID | Primärschlüssel |
blueprint_id |
UUID | FK → blueprints.id (nullable für Phase-1-Runs) |
input_topic |
TEXT | Eingabe-Prompt |
status |
VARCHAR(50) | pending|running|completed|failed|paused |
final_draft |
TEXT | Finaler Output |
critic_score |
FLOAT | Letzte Critic-Bewertung |
iteration_count |
INT | Anzahl Rework-Schleifen |
created_at |
TIMESTAMP | Startzeitpunkt |
completed_at |
TIMESTAMP | Endzeitpunkt |
4. Komponentenstruktur
4.1 Backend-Verzeichnisstruktur
backend/
├── main.py # FastAPI-Entrypoint
├── state.py # CouncilState TypedDict + Konstanten
├── database.py # Async SQLAlchemy setup + Session
├── alembic.ini # Alembic-Konfiguration
├── alembic/ # Datenbankmigrationen
│ └── versions/
├── api/
│ ├── routes.py # Run-Endpunkte + Health-Check
│ ├── blueprint_routes.py # Blueprint-CRUD-Endpunkte
│ ├── run_history_routes.py # Run-Verlauf-Endpunkte
│ ├── run_store.py # In-Memory-Run-Status-Store
│ └── websocket.py # WebSocket-Connection-Manager + Route
├── agents/
│ ├── master_agent.py # Master-Agent-Node
│ ├── critic_agent.py # Critic-Agent-Node + Routing
│ └── writer_agent.py # Writer-Agent-Node
├── services/
│ ├── graph_builder.py # Phase-1-Hartcodierter-Graph
│ ├── dynamic_graph_builder.py # Phase-3-Dynamischer-Graph aus Blueprint
│ ├── blueprint_service.py # Blueprint-CRUD-Service
│ └── run_service.py # Run-History-Service
├── models/
│ ├── blueprint.py # SQLAlchemy Blueprint-Modell
│ └── council_run.py # SQLAlchemy CouncilRun-Modell
└── tools/
├── web_search.py # Tavily-Web-Suche-Wrapper
└── pdf_reader.py # PyPDF + ChromaDB-Wrapper
4.2 Frontend-Verzeichnisstruktur
frontend/
├── app/
│ ├── layout.tsx # Root-Layout + Navigation-Tabs
│ ├── page.tsx # Redirect → /rat-architekt
│ ├── rat-architekt/ # Tab A: Visueller Canvas
│ │ └── page.tsx
│ ├── konferenzzimmer/ # Tab B: Ausführung
│ │ └── page.tsx
│ ├── components/
│ │ ├── ArchitectCanvas.tsx # React Flow Canvas
│ │ ├── NavTabs.tsx # Tab-Navigation
│ │ ├── nodes/ # Custom React Flow Nodes
│ │ ├── edges/ # Custom React Flow Edges
│ │ └── panels/ # Sidebar + Settings-Panels
│ ├── hooks/ # Custom React Hooks (WebSocket, API)
│ ├── store/ # Zustand State Management
│ ├── types/ # TypeScript-Typen
│ └── utils/
│ ├── blueprint-parser.ts # React Flow → Blueprint JSON
│ └── api-client.ts # HTTP-Client für Backend-API
└── __tests__/ # Vitest-Tests
5. API-Vertragsübersicht
5.1 REST-Endpunkte
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/api/councils/ |
Blueprint erstellen |
GET |
/api/councils/ |
Alle Blueprints auflisten |
GET |
/api/councils/{id} |
Einzelnen Blueprint abrufen |
PUT |
/api/councils/{id} |
Blueprint aktualisieren |
DELETE |
/api/councils/{id} |
Blueprint löschen |
POST |
/api/councils/run |
Phase-1-Run starten |
POST |
/api/councils/{id}/run |
Blueprint-Run starten |
GET |
/api/councils/run/{run_id} |
Run-Status/Ergebnis abrufen |
POST |
/api/councils/run/{run_id}/approve |
God Mode: genehmigen/ablehnen/modifizieren |
GET |
/api/councils/run/{run_id}/state |
God Mode: pausierten State abrufen |
POST |
/api/councils/upload-pdf |
PDF hochladen und in ChromaDB einlesen |
GET |
/api/runs/ |
Run-Verlauf auflisten |
GET |
/api/runs/{run_id} |
Run-Verlauf-Detail |
GET |
/api/health |
Health-Check |
5.2 WebSocket
| Endpoint | Event-Format |
|---|---|
WS /ws/council/{run_id} |
{"node": "master_agent", "status": "running"} |
Status-Werte: running | completed | done
6. Sequenzdiagramm: Council-Run
Nutzer Frontend FastAPI LangGraph LLM API
│ │ │ │ │
├─POST /run───►│ │ │ │
│ ├─POST /run────►│ │ │
│ │ ├─202 (run_id)──►│ │
│ │ ├─Background:─────────────────► │
│ │ │ graph.invoke() │ │
│ ├─WS connect───►│ │ │
│ │ │ │ │
│ │ │ [Node: master_agent running] │
│ │ ├─WS event───────────────────────►
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │ │ [Node: critic_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │ │ [score < 8 → rework] │
│ │ │ [Node: master_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ... │
│ │ │ [score ≥ 8 → approve] │
│ │ │ [Node: writer_agent running] │
│ │◄─WS: running──┤ │ │
│ │ │ ├─LLM call──────►
│ │ │ │◄──response────┤
│ │◄─WS: done─────┤ │ │
├─GET /result─►│ │ │ │
│ ├─GET result───►│ │ │
│ │◄──final draft─┤ │ │
7. God Mode — Sequenzdiagramm
Nutzer Frontend FastAPI LangGraph
│ │ │ │
├─POST /run (god_mode=true)────► │
│ │ ├─interrupt_before──────────────►
│ │ │◄──paused state─┤
│ │◄─WS: paused───┤ │
│ │ │ │
├─Approve/Reject/Modify────────► │
│ │ ├─resume──────────────────────►
│ │ │◄──next state───┤
│ │ │ │
│ │ (repeat per node if god_mode=true) │
8. Sicherheitsüberlegungen
- API-Key-Management: Alle Secrets ausschließlich in
.env-Dateien (gitignored). - CORS: In Produktion auf bekannte Origins beschränken.
- PDF-Upload-Validierung: Nur
.pdf-Dateien akzeptieren, Größenlimit durchsetzen. - Input-Validierung: Pydantic-Modelle für alle API-Inputs mit
min_length/max_length. - SQL-Injection: SQLAlchemy ORM verhindert Raw-SQL-Injection.
- Infinite-Loop-Schutz:
MAX_ITERATIONS = 5— nach 5 Rework-Schleifen automatische Genehmigung.
9. Deployment-Architektur (Lokal)
# docker-compose.yml (vereinfacht)
services:
db: # PostgreSQL 16
api: # FastAPI + LangGraph (Port 8000)
frontend: # Next.js (Port 3000)
volumes:
postgres_data:
chroma_data:
Umgebungsvariablen (.env):
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
TAVILY_API_KEY=
DATABASE_URL=postgresql+asyncpg://user:password@db:5432/councilOS
CHROMA_PERSIST_DIR=./chroma_db