Update Readme #20
744
README.md
744
README.md
@ -1,21 +1,21 @@
|
||||
# AISE AI Code Editor — Technische Dokumentation
|
||||
|
||||
AI-Supported Lightweight Code Editor built with Streamlit (AISE501 Spring 2026)
|
||||
|
||||
Diese Datei enthält die ausführliche technische Dokumentation des Projekts. Für eine Kurzübersicht siehe `README.md`.
|
||||
KI-unterstützter Lightweight Code Editor auf Basis von Streamlit (AISE501 Spring 2026).
|
||||
|
||||
---
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Projektstruktur](#projektstruktur)
|
||||
2. [Frontend](#frontend)
|
||||
3. [Backend Manager](#backend-manager)
|
||||
4. [Backend Agent (MCP-System)](#backend-agent-mcp-system)
|
||||
5. [MCP-Server-Konfiguration](#mcp-server-konfiguration)
|
||||
6. [Tests](#tests)
|
||||
7. [Setup](#setup)
|
||||
8. [Architektur-Übersicht](#architektur-übersicht)
|
||||
2. [Schnellstart](#schnellstart)
|
||||
3. [Frontend](#frontend)
|
||||
4. [Backend Manager](#backend-manager)
|
||||
5. [Backend Agent (MCP-System)](#backend-agent-mcp-system)
|
||||
6. [MCP-Server-Konfiguration](#mcp-server-konfiguration)
|
||||
7. [Architektur-Übersicht](#architektur-übersicht)
|
||||
8. [Wichtige Designentscheidungen](#wichtige-designentscheidungen)
|
||||
9. [Tests](#tests)
|
||||
10. [Umgebungsvariablen](#umgebungsvariablen)
|
||||
|
||||
---
|
||||
|
||||
@ -24,288 +24,52 @@ Diese Datei enthält die ausführliche technische Dokumentation des Projekts. F
|
||||
```
|
||||
AISE_AIAgent/
|
||||
├── frontend/ # Streamlit UI-Komponenten
|
||||
│ ├── app.py # Haupteinstiegspunkt der Streamlit-App
|
||||
│ ├── state.py # Session-State-Verwaltung
|
||||
│ ├── sidebar.py # Datei-Navigation (Sidebar)
|
||||
│ ├── editor.py # Code-Editor-Pane
|
||||
│ └── chat.py # Chat-Interface
|
||||
│ ├── app.py # Einstiegspunkt der App; Seitenkonfiguration + Routing
|
||||
│ ├── state.py # Zentrale Session-State-Initialisierung
|
||||
│ ├── sidebar.py # Datei-Explorer + Navigations-Radio
|
||||
│ ├── editor.py # Ace-Editor-Tabs + Ausführungs-Output
|
||||
│ └── chat.py # Chat-Interface + Agent-Mode-UI
|
||||
│
|
||||
├── backend/ # Backend-Logik
|
||||
│ ├── managers/ # Business-Logik für UI-Operationen
|
||||
│ │ ├── file_manager.py # Datei-CRUD (lesen, schreiben, listen)
|
||||
│ │ ├── chat_manager.py # AI-Chat-Verwaltung und -History
|
||||
│ │ ├── system_prompter.py # System-Prompts und Kontext-Injektion
|
||||
│ │ ├── search_manager.py # Web-Suche (DuckDuckGo)
|
||||
│ │ ├── execution_engine.py # Code-Ausführung und Sandboxing
|
||||
│ │ └── debug_logger.py # Logging, Fehlerbehandlung, Debug-Ausgaben
|
||||
├── backend/
|
||||
│ ├── managers/ # Business-Logik, direkt vom Frontend aufgerufen
|
||||
│ │ ├── file_manager.py # Workspace-CRUD mit Path-Traversal-Schutz
|
||||
│ │ ├── chat_manager.py # LLM-API-Wrapper + Sliding-Window-History
|
||||
│ │ ├── system_prompter.py # Kontextbewusste System-Prompt-Generierung
|
||||
│ │ ├── search_manager.py # DuckDuckGo-Websuche + Seitenabruf
|
||||
│ │ ├── execution_engine.py # Subprocess-basierte Code-Ausführung (Python, LaTeX)
|
||||
│ │ └── debug_logger.py # Rotierende Logdatei + Fehler-Aggregation
|
||||
│ │
|
||||
│ └── agent/ # Autonomes AI-Agent-System (MCP-basiert)
|
||||
│ ├── coding_agent.py # Haupt-Agent-Loop (Plan-Act-Observe)
|
||||
│ ├── mcp_server_adapter.py # MCP-Adapter: verbindet Agent mit MCP-Servern
|
||||
│ ├── mcp_server_adapter_RAG.py # MCP-Adapter mit RAG-basierter Tool-Auswahl
|
||||
│ ├── mcp_server_config.json # Konfiguration der MCP-Server (Startbefehle)
|
||||
│ └── servers/ # MCP-Server-Implementierungen
|
||||
│ ├── mcp_server_code_execution.py # Tool: Python-Code ausführen
|
||||
│ ├── mcp_server_file_search.py # Tool: Dateien suchen und lesen
|
||||
│ └── mcp_server_web_search.py # Tool: Web-Suche via DuckDuckGo
|
||||
│ └── agent/ # Autonomes KI-Agenten-System (MCP-basiert)
|
||||
│ ├── coding_agent.py # Plan→Aktion→Beobachten-Schleife
|
||||
│ ├── mcp_server_adapter.py # Verbindet den Agenten mit MCP-Tool-Servern
|
||||
│ ├── mcp_server_config.json # Welche MCP-Server gestartet werden (Pfade + Befehle)
|
||||
│ └── servers/ # MCP-Server-Implementierungen (stdio-Transport)
|
||||
│ ├── mcp_server_code_execution.py # Tool: Sandbox-Python-Ausführung + Linting
|
||||
│ ├── mcp_server_file_search.py # Tool: Workspace-Datei lesen/schreiben/suchen
|
||||
│ └── mcp_server_web_search.py # Tool: DuckDuckGo-Suche + Seitenabruf
|
||||
│
|
||||
├── tests/ # Unit-Tests (pytest)
|
||||
│ ├── conftest.py # Globale Test-Fixtures und MCP-Mocks
|
||||
│ ├── test_file_manager.py
|
||||
├── tests/ # pytest-Unit-Tests
|
||||
│ ├── conftest.py # Globaler MCP-Mock (keine echten Subprozesse in Tests)
|
||||
│ ├── test_chat_manager.py
|
||||
│ ├── test_execution_engine.py
|
||||
│ ├── test_coding_agent.py
|
||||
│ ├── test_debug_logger.py
|
||||
│ ├── test_system_prompter.py
|
||||
│ ├── test_execution_engine.py
|
||||
│ ├── test_file_manager.py
|
||||
│ ├── test_mcp_server_code_execution.py
|
||||
│ ├── test_mcp_server_file_search.py
|
||||
│ └── test_mcp_server_web_search.py
|
||||
│ ├── test_mcp_server_web_search.py
|
||||
│ ├── test_search_manager.py
|
||||
│ └── test_system_prompter.py
|
||||
│
|
||||
├── workspace/ # Agent-Sandbox (isoliertes Arbeitsverzeichnis)
|
||||
└── .env.example # Vorlage für Umgebungsvariablen
|
||||
├── workspace/ # Sandbox-Verzeichnis für Agent- und Editor-Dateien
|
||||
├── logs/ # Rotierende Logdateien (app.log, errors.log)
|
||||
├── .env # Lokale Umgebungsvariablen (nicht eingecheckt)
|
||||
└── .env.example # Vorlage für erforderliche Umgebungsvariablen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
Das Frontend besteht aus Streamlit-Komponenten, die zusammen eine interaktive Code-Editor-Oberfläche bilden.
|
||||
|
||||
### `app.py`
|
||||
Haupteinstiegspunkt der Applikation. Orchestriert das Layout und initialisiert alle UI-Komponenten (Sidebar, Editor, Chat).
|
||||
|
||||
### `state.py`
|
||||
Zentralisierte Verwaltung des Streamlit Session-State. Stellt sicher, dass alle Komponenten denselben Zustand (geöffnete Datei, Chat-History, Agent-Status) teilen.
|
||||
|
||||
### `sidebar.py`
|
||||
Datei-Browser und Projekt-Navigation. Erlaubt das Durchsuchen des Workspaces und das Öffnen von Dateien im Editor.
|
||||
|
||||
### `editor.py`
|
||||
Code-Editor-Pane mit Syntax-Highlighting. Ermöglicht das Bearbeiten und Speichern von Code-Dateien direkt im Browser.
|
||||
|
||||
### `chat.py`
|
||||
Chat-Interface für den KI-Assistenten. Zeigt die Konversations-History und ermöglicht Eingaben an das AI-Modell.
|
||||
|
||||
---
|
||||
|
||||
## Backend Manager
|
||||
|
||||
Die Manager-Klassen kapseln die Business-Logik und werden direkt vom Frontend aufgerufen.
|
||||
|
||||
### `file_manager.py`
|
||||
Stellt CRUD-Operationen auf dem Workspace-Verzeichnis bereit:
|
||||
- Dateien lesen, schreiben, umbenennen, löschen
|
||||
- Verzeichnisstruktur auflisten
|
||||
- Sichere Pfadvalidierung (verhindert Path-Traversal)
|
||||
|
||||
**Designentscheidung — `list_files()` vs. `get_file_tree()`:**
|
||||
Die Projektspezifikation nennt `list_files()` als `FileManager`-Methode. Im vorliegenden Design wurde bewusst `get_file_tree()` implementiert, da das Frontend eine verschachtelte Baumstruktur benötigt (für den interaktiven File Explorer in der Sidebar). Eine flache Liste würde die Navigation nicht unterstützen. Für den Agent Mode übernimmt der MCP-Server `mcp_server_file_search.py` die Dateisuche — die Funktionalität ist damit im System vorhanden, nur architektonisch sauber getrennt.
|
||||
|
||||
### `chat_manager.py`
|
||||
Verwaltet AI-Chat-Interaktionen:
|
||||
- Aufbau und Verwaltung der Chat-History
|
||||
- Senden von Nachrichten an das AI-Modell
|
||||
- Formatierung von System- und User-Nachrichten
|
||||
|
||||
**Designentscheidung — Fehler-Output im normalen Chat:**
|
||||
Laufzeitfehler und stderr-Output werden im normalen Chat bewusst **nicht automatisch** in den Chat-Kontext injiziert. Stattdessen gibt es den "Debug with AI"-Button im Editor, über den der User selbst entscheidet wann er die AI einschalten möchte. Dies verhindert, dass die Chat-History mit ungewollten Fehlermeldungen geflutet wird. Im Agent Mode wird dies anders gelöst: dort landet jeder Execution-Fehler automatisch als Observation im Plan-Act-Observe-Loop und der Agent replant ohne User-Eingriff.
|
||||
|
||||
### `system_prompter.py`
|
||||
Generiert kontextreiche System-Prompts für den AI-Assistenten:
|
||||
- Injektion von aktuellem Dateiinhalt als Kontext
|
||||
- Steuerung des AI-Verhaltens (Coding-Assistent-Persona)
|
||||
|
||||
### `execution_engine.py`
|
||||
Führt Python-Code sicher aus:
|
||||
- Subprocess-basierte Code-Ausführung
|
||||
- Timeout-Schutz und Output-Capture
|
||||
- Fehler- und Exception-Handling
|
||||
|
||||
### `debug_logger.py`
|
||||
Logging und Fehler-Tracking:
|
||||
- Formatierte Log-Ausgaben für Debugging
|
||||
- `format_debug_output(output)` formatiert den Execution-Output (`stdout`, `stderr`, `return_code`) in einen einheitlichen String für die UI-Anzeige und den AI-Chat-Kontext
|
||||
|
||||
**Designentscheidung — `log_error()` nicht implementiert:**
|
||||
Die Projektspezifikation nennt `log_error()` als `DebugLogger`-Methode. Diese wurde bewusst nicht als separate Methode implementiert, da Python's eingebautes `logging`-Modul diese Funktionalität mit `logger.error()` bereits vollständig abdeckt. Im gesamten Projekt wird konsistent `logger = get_logger(__name__)` gefolgt von `logger.error(...)` verwendet — eine eigene Wrapper-Methode wäre toter Code ohne Mehrwert.
|
||||
|
||||
### `search_manager.py`
|
||||
Web-Suche für den KI-Assistenten via DuckDuckGo:
|
||||
- Nutzt die `ddgs`-Bibliothek (DuckDuckGo Search) für API-freie Websuche
|
||||
- Gibt strukturierte Suchergebnisse zurück (Titel, URL, Snippet)
|
||||
- Wird vom Chat-Manager aufgerufen, wenn der Assistent externe Dokumentation oder Code-Beispiele benötigt
|
||||
- Keine API-Key-Konfiguration notwendig (da DuckDuckGo öffentlich zugänglich ist)
|
||||
|
||||
---
|
||||
|
||||
## Backend Agent (MCP-System)
|
||||
|
||||
Der Agent ist ein autonomes System, das komplexe Coding-Aufgaben selbstständig löst. Er kommuniziert mit externen Tool-Servern über das **Model Context Protocol (MCP)**.
|
||||
|
||||
### `coding_agent.py`
|
||||
Implementiert den Plan-Act-Observe-Loop:
|
||||
1. **Plan**: Das AI-Modell wählt das nächste Tool und Argumente
|
||||
2. **Act**: Das Tool wird via MCP-Adapter aufgerufen (nach User-Bestätigung)
|
||||
3. **Observe**: Das Ergebnis wird in die Message-History eingefügt
|
||||
4. Der Loop wiederholt sich bis zur Fertigstellung oder einem `done`-Tool-Aufruf
|
||||
|
||||
Wichtige Klassen und Funktionen:
|
||||
- `CodingAgent`: Haupt-Klasse mit `start_task()`, `propose_next_action()`, `approve()`, `reject()`
|
||||
- `truncate_result()`: Kürzt lange Tool-Outputs bevor sie in die History gehen
|
||||
- `trim_messages()`: Entfernt alte Turns aus der History wenn das Kontextfenster voll wird
|
||||
- `_strip_code_fences()`: Bereinigt Markdown-Fences aus LLM-JSON-Antworten
|
||||
|
||||
Konstanten: `MAX_ITERATIONS`, `MAX_RESULT_LENGTH`, `MAX_HISTORY_CHARS`
|
||||
|
||||
### `mcp_server_adapter.py`
|
||||
Verbindet den Coding-Agent mit den MCP-Tool-Servern:
|
||||
- Liest `mcp_server_config.json` und startet die konfigurierten Server als Subprozesse
|
||||
- Baut MCP-Sessions via `stdio_client` auf
|
||||
- Registriert alle verfügbaren Tools aus allen Servern in einem zentralen `tool_registry`
|
||||
- Delegiert Tool-Aufrufe an den richtigen Server via `call_tool()`
|
||||
|
||||
Hauptmethoden:
|
||||
- `initialize_all_servers()`: Startet alle Server und baut Sessions auf
|
||||
- `get_all_tools()`: Gibt alle registrierten Tool-Definitionen zurück
|
||||
- `call_tool(tool_name, arguments)`: Führt ein Tool auf dem zuständigen Server aus
|
||||
|
||||
### `mcp_server_adapter_RAG.py`
|
||||
Erweiterter MCP-Adapter mit semantischer Tool-Auswahl via Retrieval-Augmented Generation (RAG):
|
||||
|
||||
**Motivation**: Bei vielen MCP-Tools kann das LLM-Kontextfenster überfüllt werden, wenn alle Tool-Definitionen mitgesendet werden. Der RAG-Adapter löst dies durch semantische Vorauswahl.
|
||||
|
||||
**Funktionsweise**:
|
||||
1. Beim Initialisieren werden alle Tool-Beschreibungen mit `SentenceTransformer('all-MiniLM-L6-v2')` in Embeddings umgewandelt
|
||||
2. Bei jedem Agent-Schritt wird der aktuelle Task als Query kodiert
|
||||
3. Cosine-Similarity zwischen Query- und Tool-Embeddings bestimmt die `top_k` relevantesten Tools
|
||||
4. Nur diese Tools werden dem LLM als verfügbare Aktionen präsentiert
|
||||
|
||||
Hauptmethoden:
|
||||
- `initialize_all_sessions()`: Startet Server, baut Sessions auf, erstellt Embedding-Index
|
||||
- `get_relevant_tools(query, top_k=5)`: Gibt die `top_k` semantisch ähnlichsten Tools zurück
|
||||
- `call_tool(tool_name, arguments)`: Findet den zuständigen Server und führt das Tool aus
|
||||
- `shutdown_all_sessions()`: Schliesst alle offenen MCP-Sessions sauber
|
||||
|
||||
Abhängigkeit: `sentence-transformers`, `numpy`
|
||||
|
||||
**Hinweis**: Diese Klasse befindet sich noch in der Entwicklung (Work in Progress). Es gibt bekannte Bugs (z.B. Tippfehler `commanf` statt `command`, falsche Verwendung von `result.get()` vs. `result.tools`).
|
||||
|
||||
---
|
||||
|
||||
## MCP-Server-Konfiguration
|
||||
|
||||
### Format: `mcp_server_config.json`
|
||||
|
||||
Die Datei `backend/agent/mcp_server_config.json` definiert, welche MCP-Server der Adapter starten soll. Das Format ist ein JSON-Objekt, wobei jeder Key ein frei wählbarer Servername ist:
|
||||
|
||||
```json
|
||||
{
|
||||
"ServerName": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_datei.py"],
|
||||
"env": {
|
||||
"API_KEY": "optional_key"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Feld | Pflicht | Beschreibung |
|
||||
|-----------|---------|--------------|
|
||||
| `command` | Ja | Ausführbares Programm (z.B. `py`, `python3`, `node`) |
|
||||
| `args` | Ja | Argumente als Array (Pfad zum Server-Script) |
|
||||
| `env` | Nein | Umgebungsvariablen für den Serverprozess |
|
||||
|
||||
### Aktuelle Server
|
||||
|
||||
```json
|
||||
{
|
||||
"FileSearchServer": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_file_search.py"]
|
||||
},
|
||||
"WebSearchServer": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_web_search.py"],
|
||||
"env": { "DDGS_API_KEY": "your_ddgs_api_key_here" }
|
||||
},
|
||||
"CodeExecutionServer": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_code_execution.py"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Neuen MCP-Server hinzufügen
|
||||
|
||||
1. Neues Server-Script in `backend/agent/servers/` erstellen (MCP-konformes Python-Script)
|
||||
2. Eintrag in `mcp_server_config.json` ergänzen:
|
||||
```json
|
||||
"MeinNeuerServer": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_mein_tool.py"]
|
||||
}
|
||||
```
|
||||
3. Der Adapter erkennt den neuen Server beim nächsten Start automatisch und registriert seine Tools
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
### Tests ausführen
|
||||
|
||||
```bash
|
||||
# Alle Tests ausführen
|
||||
pytest tests/ -v
|
||||
|
||||
# Einzelnes Test-Modul ausführen
|
||||
pytest tests/test_coding_agent.py -v
|
||||
|
||||
# Tests mit Kurzausgabe
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
### Teststruktur
|
||||
|
||||
Die Tests liegen in `tests/` und folgen dem Muster `test_<modulname>.py`.
|
||||
|
||||
#### `conftest.py`
|
||||
Globale Pytest-Konfiguration. Patcht den `MCPToolAdapter` auf `sys.modules`-Ebene, bevor irgendein Test-Modul importiert wird. Dadurch werden beim Import von `coding_agent` keine echten MCP-Subprozesse gestartet. Der Mock-Adapter liefert sofort leere Ergebnisse zurück.
|
||||
|
||||
#### Was wird getestet
|
||||
|
||||
| Test-Datei | Getestetes Modul | Schwerpunkt |
|
||||
|---|---|---|
|
||||
| `test_file_manager.py` | `backend/managers/file_manager.py` | Datei-CRUD, Pfad-Validierung |
|
||||
| `test_chat_manager.py` | `backend/managers/chat_manager.py` | Chat-History, Nachrichtenformatierung |
|
||||
| `test_execution_engine.py` | `backend/managers/execution_engine.py` | Code-Ausführung, Timeouts, Fehlerbehandlung |
|
||||
| `test_system_prompter.py` | `backend/managers/system_prompter.py` | Prompt-Generierung, Kontext-Injektion |
|
||||
| `test_debug_logger.py` | `backend/managers/debug_logger.py` | Log-Formatierung, Fehler-Aggregation |
|
||||
| `test_coding_agent.py` | `backend/agent/coding_agent.py` | Agent-Loop, Tool-Dispatch, History-Trimming |
|
||||
| `test_mcp_server_code_execution.py` | `backend/agent/servers/mcp_server_code_execution.py` | MCP Code-Execution-Tool |
|
||||
| `test_mcp_server_file_search.py` | `backend/agent/servers/mcp_server_file_search.py` | MCP Datei-Such-Tool |
|
||||
| `test_mcp_server_web_search.py` | `backend/agent/servers/mcp_server_web_search.py` | MCP Web-Such-Tool |
|
||||
|
||||
#### Testklassen in `test_coding_agent.py`
|
||||
|
||||
- **`TestTruncateResult`**: Prüft, dass lange Tool-Outputs korrekt gekürzt werden
|
||||
- **`TestTrimMessages`**: Prüft, dass alte History-Turns entfernt werden wenn der Kontext zu gross wird; System-Message und Original-Task bleiben immer erhalten
|
||||
- **`TestStripCodeFences`**: Prüft, dass Markdown-Codeblöcke aus LLM-Antworten entfernt werden
|
||||
- **`TestCodingAgentInit`**: Prüft initialen Zustand und `start_task()`-Reset-Verhalten
|
||||
- **`TestProposeNextAction`**: Prüft den API-Aufruf-Zyklus mit gemockter API; testet Fehler-Handling (JSON-Parse-Fehler, API-Exceptions, Max-Iterations)
|
||||
- **`TestApprove`**: Prüft `approve()` mit gemocktem `dispatch_tool`; testet Tool-Ergebnis-Injektion und Error-Replan-Tagging
|
||||
- **`TestReject`**: Prüft, dass `reject()` das Feedback korrekt in die History injiziert und kein Tool ausführt
|
||||
|
||||
#### Test-Konventionen
|
||||
|
||||
- MCP-Server werden in Tests **nicht** als echte Subprozesse gestartet (via `conftest.py`-Mock)
|
||||
- Streamlit-Aufrufe werden mit `patch("modul.st")` gemockt
|
||||
- Dateisystem-Tests nutzen `tmp_path` (pytest-Fixture) für isolierte temporäre Verzeichnisse
|
||||
- Async-Tests verwenden `@pytest.mark.asyncio` (benötigt `pytest-asyncio`)
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
## Schnellstart
|
||||
|
||||
### 1. Repository klonen
|
||||
|
||||
@ -320,7 +84,7 @@ cd AISE1_Project
|
||||
# Windows
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# macOS/Linux
|
||||
# macOS / Linux
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
@ -334,10 +98,10 @@ pip install -r requirements.txt
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# .env mit API-Keys befüllen
|
||||
# .env öffnen und HOST, PORT, API_KEY, MODEL eintragen
|
||||
```
|
||||
|
||||
### 5. Applikation starten
|
||||
### 5. App starten
|
||||
|
||||
```bash
|
||||
streamlit run frontend/app.py
|
||||
@ -349,6 +113,281 @@ streamlit run frontend/app.py
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
**Optionale Abhängigkeit:** Die LaTeX-Unterstützung (`.tex`-Ausführung) erfordert `pdflatex`
|
||||
im System-`PATH`. Ohne `pdflatex` funktioniert der Editor weiterhin; beim Ausführen einer
|
||||
`.tex`-Datei erscheint ein `FileNotFoundError` im stderr-Panel.
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
Alle Frontend-Module sind reine Streamlit-Komponenten. Sie enthalten keine Business-Logik,
|
||||
sondern delegieren alles an die Backend-Manager.
|
||||
|
||||
### `app.py`
|
||||
Einstiegspunkt der Applikation. Ruft `init_state()` auf Modul-Ebene auf (vor `main()`),
|
||||
damit alle Session-State-Schlüssel existieren, bevor ein Widget gerendert wird. Delegiert
|
||||
an `render_sidebar()`, `render_editor()` und `render_chat()` basierend auf dem Navigations-Radio.
|
||||
|
||||
### `state.py`
|
||||
Zentrale Quelle aller `st.session_state`-Schlüsselnamen und ihrer Standardwerte.
|
||||
Alle Schlüssel nutzen `if key not in st.session_state`-Guards, damit bei Streamlit-Reruns
|
||||
keine bestehenden Werte überschrieben werden. Aktuell verwaltete Schlüssel:
|
||||
|
||||
| Schlüssel | Standard | Zweck |
|
||||
|-----------|----------|-------|
|
||||
| `last_selected` | `None` | Zuletzt angeklickter Baum-Knoten (verhindert erneutes Ausführen bei jedem Rerender) |
|
||||
| `selected_folder` / `selected_folder_rel` | `None` | Aktuell markierter Ordner |
|
||||
| `chat_manager` | `ChatManager()` | Live-ChatManager-Instanz |
|
||||
| `open_files` | `[]` | Geordnete Liste absoluter Pfade als Editor-Tabs |
|
||||
| `files_content` | `{}` | Pfad → aktueller Editor-Inhalt (kann von Disk abweichen) |
|
||||
| `active_file` | `None` | Absoluter Pfad des aktiven Editor-Tabs |
|
||||
| `exec_results` | `{}` | Pfad → letztes Ausführungsergebnis-Dict |
|
||||
| `chat_history` | `[]` | Flache Liste von `{role, content}`-Dicts zur Anzeige |
|
||||
| `agent_mode` | `False` | Ob die Agent-Mode-UI aktiv ist |
|
||||
| `coding_agent` | `None` | Live-`CodingAgent`-Instanz während einer Aufgabe |
|
||||
| `agent_status` | `"idle"` | `"idle"` / `"waiting_approval"` / `"done"` |
|
||||
| `agent_log` | `[]` | Liste abgeschlossener Schritt-Einträge |
|
||||
| `agent_pending_action` | `None` | Vorgeschlagene Aktion, die auf Benutzer-Genehmigung wartet |
|
||||
| `search_results` | `[]` | Aktive Websuchergebnisse für Kontext-Injektion |
|
||||
|
||||
### `sidebar.py`
|
||||
Rendert das Navigations-Radio und den Workspace-Datei-Explorer (basierend auf
|
||||
`streamlit-arborist` für einen interaktiven Baum). Datei-Klicks öffnen einen neuen
|
||||
Editor-Tab; Ordner-Klicks zeigen eine Aktionsleiste mit «Datei hinzufügen» / «Ordner
|
||||
hinzufügen» / «Löschen». Ein Popover am unteren Rand ermöglicht das Erstellen und
|
||||
Hochladen von Dateien (bis 1 MB) im Workspace-Wurzelverzeichnis.
|
||||
|
||||
### `editor.py`
|
||||
Verwendet `streamlit-ace` für syntaxhervorgehobene Bearbeitung. Jede geöffnete Datei
|
||||
erhält einen eigenen Tab via `st.tabs()`. Die aktive Datei steuert die Schaltflächen
|
||||
«Code ausführen», «Herunterladen», «Schliessen», «Umbenennen» und «Löschen». Der Button
|
||||
**Code ausführen** führt für Python-Dateien zuerst `ast.parse()` durch, um Syntaxfehler
|
||||
vor dem Subprocess zu erkennen. Der Button **Mit KI debuggen** (nach einem fehlgeschlagenen
|
||||
Lauf eingeblendet) formatiert die Fehlerausgabe und navigiert zur Chat-Ansicht mit einer
|
||||
vorausgefüllten Debug-Nachricht.
|
||||
|
||||
### `chat.py`
|
||||
Zwei sich gegenseitig ausschliessende Ansichten, umgeschaltet via `st.toggle("Agent Mode")`:
|
||||
|
||||
**Normaler Chat** (`render_normal_chat()`):
|
||||
- Websuch-Panel oben — öffnet sich automatisch, wenn Suchergebnisse aktiv sind.
|
||||
- System-Prompt wird vor jeder ausgehenden Nachricht neu generiert (`_set_system_prompt()`).
|
||||
- Unterstützt Slash-Befehle `/search <Abfrage>` und `/search clear`.
|
||||
- Einstellungs-Expander: Dateikontext-Toggle, Modell-Auswahl, Max-Token-Slider,
|
||||
benutzerdefinierter System-Prompt.
|
||||
- «Mit KI debuggen»-Nachrichten vom Editor werden über `pending_debug_message` im
|
||||
Session-State weitergeleitet.
|
||||
|
||||
**Agent Mode** (`render_agent_mode()`):
|
||||
- `idle` → Aufgabeneingabe + Start-Schaltfläche.
|
||||
- `waiting_approval` → zeigt vorgeschlagenen Gedanken + Tool + Argumente; Benutzer kann
|
||||
Genehmigen, Ablehnen (mit Feedback) oder Abbrechen.
|
||||
- `done` → Erfolgsmeldung + Folgefrage-Eingabe zum Weiterführen der Aufgabe.
|
||||
|
||||
---
|
||||
|
||||
## Backend Manager
|
||||
|
||||
### `file_manager.py`
|
||||
Alle öffentlichen Methoden lösen Pfade auf und prüfen, ob sie innerhalb von `workspace/`
|
||||
bleiben, bevor sie das Dateisystem berühren (**Path-Traversal-Schutz**). Je nach Operation
|
||||
werden relative oder absolute Pfade akzeptiert und zurückgegeben:
|
||||
|
||||
| Methode | Pfad-Typ | Hinweise |
|
||||
|---------|----------|----------|
|
||||
| `create_folder(relative_path, name)` | Workspace-relativ | Erstellt eine Ebene |
|
||||
| `create_file(relative_path, name)` | Workspace-relativ | Standard: `.txt` |
|
||||
| `read_file(absolute_path)` | Absolutes `Path`-Objekt | Vom Editor verwendet |
|
||||
| `save_file(absolute_path, content)` | Absoluter String | Überschreibt vorhandenes |
|
||||
| `rename_file(relative_path, new_name)` | Workspace-relativ | Erweiterung bleibt immer erhalten |
|
||||
| `delete_file(relative_path)` | Workspace-relativ | |
|
||||
| `delete_folder(relative_path)` | Workspace-relativ | Rekursiv via `shutil.rmtree` |
|
||||
| `get_file_tree()` | — | Gibt verschachteltes Dict zurück; Verzeichnisse → Dict, Dateien → None |
|
||||
|
||||
Dateien werden in `get_file_tree()` standardmässig auf `CODE_EXTENSIONS` gefiltert.
|
||||
|
||||
### `chat_manager.py`
|
||||
Kapselt einen OpenAI-kompatiblen REST-Endpunkt, konfiguriert via `.env`.
|
||||
|
||||
**Sliding-Window-History:** `_build_payload_messages()` sendet immer zuerst die
|
||||
System-Nachricht (damit sie nie verworfen wird), gefolgt von den letzten
|
||||
`max_history_messages` (20) Nicht-System-Nachrichten. Das begrenzt die Payload-Grösse,
|
||||
ohne den System-Prompt zu verlieren.
|
||||
|
||||
**Fehlerbehandlung:** Verbindungs-Timeouts und HTTP-Fehler werden abgefangen, geloggt und
|
||||
als Assistenten-Nachrichten in der History gespeichert (sodass die UI den Fehler inline
|
||||
anzeigt).
|
||||
|
||||
**API-Key:** Falls `API_KEY` den Wert `"EMPTY"` hat oder fehlt, wird kein
|
||||
`Authorization`-Header gesendet (unterstützt lokale/anonyme Endpunkte).
|
||||
|
||||
### `system_prompter.py`
|
||||
Generiert kontextbewusste System-Prompts. Signatur:
|
||||
```python
|
||||
SystemPrompter.generate_prompt(
|
||||
user_message="",
|
||||
file_context=None, # {"name": str, "content": str}
|
||||
search_context=None, # reserviert, noch nicht verdrahtet
|
||||
task_type="default", # "debug" | "explain" | "optimize" | "default"
|
||||
)
|
||||
```
|
||||
Der Aufgabentyp wird anhand von Schlüsselwörtern in der Benutzernachricht durch
|
||||
`_detect_task_type()` in `chat.py` ermittelt. Der Dateiinhalt wird wörtlich in einen
|
||||
XML-ähnlichen `<file>...<code>`-Block eingebettet und bei `MAX_FILE_CHARS` Zeichen
|
||||
abgeschnitten. `_extract_relevant_context()` nutzt `ast.parse()`, um nur die spezifische
|
||||
Funktion oder Klasse zurückzugeben, nach der der Benutzer fragt, anstatt die gesamte Datei.
|
||||
|
||||
### `execution_engine.py`
|
||||
Führt Dateien in einem Subprocess mit `capture_output=True`, `text=True` und einem
|
||||
`RUN_TIMEOUT` von 30 Sekunden aus. Aktuell unterstützt:
|
||||
- `.py` — via `py`-Launcher (Windows) / `sys.executable`
|
||||
- `.tex` — via `pdflatex -interaction=nonstopmode` (erfordert pdflatex im PATH)
|
||||
|
||||
Rückgabe: `{"stdout": str, "stderr": str, "rc": int}`.
|
||||
|
||||
### `search_manager.py`
|
||||
DuckDuckGo-basierte Websuche und Seitenabruf für die Chat-Ansicht. SSRF-geschützt:
|
||||
`_validate_url()` blockiert Nicht-HTTP(S)-Schemata, Loopback- und RFC-1918-private
|
||||
IP-Bereiche. `fetch_page()` extrahiert lesbaren Text via BeautifulSoup, entfernt
|
||||
`<script>`-, `<style>`-, `<nav>`- und `<footer>`-Tags und kürzt auf `MAX_PAGE_CHARS`.
|
||||
|
||||
### `debug_logger.py`
|
||||
Richtet einen rotierenden Datei-Handler für `logs/app.log` (5 MB × 5 Backups) und eine
|
||||
separate `logs/errors.log` für `ERROR`/`CRITICAL`-Einträge ein. Verwendung im Code:
|
||||
|
||||
```python
|
||||
from backend.managers.debug_logger import get_logger
|
||||
logger = get_logger(__name__) # Standard-Python-Logger
|
||||
|
||||
logger.info("Dienst gestartet")
|
||||
logger.error("Etwas ist schiefgelaufen")
|
||||
logger.exception("Unerwarteter Fehler") # loggt Stack-Trace
|
||||
```
|
||||
|
||||
Zusätzliche Classmethods zur sitzungsweisen Fehler-Aggregation:
|
||||
```python
|
||||
DebugLogger.log_error("Nachricht") # loggt + hängt an _error_log-Liste an
|
||||
DebugLogger.get_errors() # gibt Liste der Fehlermeldungen dieser Sitzung zurück
|
||||
DebugLogger.clear_errors() # leert die In-Memory-Liste
|
||||
|
||||
DebugLogger.format_debug_output({ # formatiert Ausführungsergebnis für die KI
|
||||
"return_code": 1,
|
||||
"stdout": "...",
|
||||
"stderr": "...",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend Agent (MCP-System)
|
||||
|
||||
### `coding_agent.py`
|
||||
Implementiert eine **Plan → Aktion → Beobachten**-Schleife, die schrittweise durch die
|
||||
Streamlit-UI gesteuert wird. Das LLM antwortet immer mit einer strukturierten JSON-Aktion:
|
||||
|
||||
```json
|
||||
{"thought": "...", "tool": "<tool_name>", "arguments": {"key": "value"}}
|
||||
```
|
||||
|
||||
Wichtige Methoden:
|
||||
|
||||
| Methode | Beschreibung |
|
||||
|---------|--------------|
|
||||
| `start_task(task)` | Setzt den gesamten Zustand zurück, befüllt History mit System + Aufgabe |
|
||||
| `propose_next_action()` | Ruft das LLM auf, parst JSON, speichert als `pending_action` |
|
||||
| `approve()` | Führt das ausstehende Tool via `dispatch_tool()` aus, loggt Ergebnis |
|
||||
| `reject(feedback)` | Injiziert Feedback + Replan-Tag; schlägt neue Aktion vor |
|
||||
| `follow_up(question)` | Fügt nach «done» eine Folgefrage ein, setzt Schleife fort |
|
||||
|
||||
Hilfsfunktionen (Modul-Ebene):
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| `truncate_result(text)` | Begrenzt Tool-Output auf `MAX_RESULT_LENGTH` (10 000 Zeichen) |
|
||||
| `trim_messages(msgs)` | Entfernt alte Turns, wenn History `MAX_HISTORY_CHARS` (80 000 Zeichen) überschreitet; System-Nachricht + Original-Aufgabe bleiben immer erhalten |
|
||||
| `_strip_code_fences(text)` | Entfernt ` ```json `- / ` ``` `-Wrapper aus LLM-Antworten |
|
||||
| `dispatch_tool(name, arguments)` | Leitet weiter an `MCPToolAdapter.call_tool()` |
|
||||
| `get_tool_descriptions()` | Erstellt eine menschenlesbare Tool-Liste für den System-Prompt |
|
||||
|
||||
### `mcp_server_adapter.py`
|
||||
Liest `mcp_server_config.json`, startet jeden Server als stdio-Subprocess (immer mit
|
||||
`sys.executable`, unabhängig vom literalen Befehl in der Konfiguration) und registriert
|
||||
alle Tools in einem flachen `tool_registry`. Verbindungen werden pro Aufruf geöffnet
|
||||
(nicht dauerhaft gehalten), da Streamlits synchrones Rerun-Modell langlebige
|
||||
async-Kontextmanager unpraktisch macht.
|
||||
|
||||
### MCP-Server (in `servers/`)
|
||||
|
||||
Alle drei Server sind FastMCP-Applikationen, die über stdio kommunizieren.
|
||||
|
||||
**`mcp_server_file_search.py`** — Workspace-Dateioperationen:
|
||||
- `list_files()` — flache rekursive Auflistung
|
||||
- `get_file_tree(dir_path)` — baumförmige Verzeichnisstruktur
|
||||
- `search_files(query)` — Name- und Inhaltssuche (bis 30 Treffer)
|
||||
- `read_file(path)` — Textdatei lesen
|
||||
- `write_new_file(path, content)` — Erstellen (kein Überschreiben)
|
||||
- `create_new_directory(path)` — Verzeichnis erstellen
|
||||
|
||||
**`mcp_server_web_search.py`** — Webzugriff:
|
||||
- `web_search(query, max_results=5)` — DuckDuckGo-Suche
|
||||
- `fetch_page(url)` — Abrufen + Text extrahieren (max. `MAX_PAGE_LENGTH` Zeichen)
|
||||
- Beide Tools nutzen SSRF-Schutz (gleiche URL-Validierung wie `search_manager.py`)
|
||||
|
||||
**`mcp_server_code_execution.py`** — Sandbox-Python-Analyse:
|
||||
- `check_code_safety(code)` — statische Analyse; blockiert gefährliche Imports/Builtins
|
||||
- `analyse_structure(code)` — AST-basierte Strukturzusammenfassung
|
||||
- `lint_code(code)` — pyflakes-Analyse
|
||||
- `python_code_validation(code)` — Sicherheits- + Syntaxprüfung ohne Ausführung
|
||||
- `run_python_sandboxed(code)` — Ausführung in einem Subprocess mit `PYTHONIOENCODING=utf-8`,
|
||||
10 s Timeout, Output begrenzt auf `MAX_OUTPUT_LENGTH`
|
||||
|
||||
---
|
||||
|
||||
## MCP-Server-Konfiguration
|
||||
|
||||
### Format: `backend/agent/mcp_server_config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"Servername": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_beispiel.py"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Feld | Pflicht | Beschreibung |
|
||||
|------|---------|--------------|
|
||||
| `command` | Ja | Ausführbare Datei (`py`, `python3`, `node`, …) — wird für Python immer durch `sys.executable` ersetzt |
|
||||
| `args` | Ja | Argumente-Array — erstes Element ist der Server-Script-Pfad relativ zu `backend/agent/` |
|
||||
| `env` | Nein | Zusätzliche Umgebungsvariablen für den Serverprozess |
|
||||
|
||||
### Neuen MCP-Server hinzufügen
|
||||
|
||||
1. Neues FastMCP-Script in `backend/agent/servers/` erstellen:
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
mcp = FastMCP("MeinServer")
|
||||
|
||||
@mcp.tool()
|
||||
def mein_tool(param: str) -> str:
|
||||
"""Tool-Beschreibung, die dem Agenten angezeigt wird."""
|
||||
return f"Ergebnis: {param}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
```
|
||||
2. Eintrag in `mcp_server_config.json` ergänzen:
|
||||
```json
|
||||
"MeinServer": {
|
||||
"command": "py",
|
||||
"args": ["servers/mcp_server_mein_tool.py"]
|
||||
}
|
||||
```
|
||||
3. Der Adapter erkennt den neuen Server beim nächsten App-Start automatisch und
|
||||
bindet seine Tools in den System-Prompt des Agenten ein.
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Übersicht
|
||||
@ -356,36 +395,135 @@ pytest tests/ -v
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (Streamlit) │
|
||||
│ app.py → sidebar.py / editor.py / chat.py │
|
||||
│ │ │
|
||||
│ state.py (Session-State) │
|
||||
│ app.py ──► sidebar.py / editor.py / chat.py │
|
||||
│ │ │
|
||||
│ state.py (alle Session-State-Schlüssel) │
|
||||
└──────────────┬──────────────────────────────────────────────────┘
|
||||
│
|
||||
│ direkte Python-Aufrufe
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Backend Manager │
|
||||
│ FileManager / ChatManager / SystemPrompter / │
|
||||
│ SearchManager / ExecutionEngine / DebugLogger │
|
||||
│ FileManager ChatManager SystemPrompter │
|
||||
│ SearchManager ExecutionEngine DebugLogger │
|
||||
└──────────────┬──────────────────────────────────────────────────┘
|
||||
│
|
||||
│ async-Aufrufe (via _run_async-Bridge)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Coding Agent (backend/agent/) │
|
||||
│ coding_agent.py ←→ mcp_server_adapter.py │
|
||||
│ │ │
|
||||
│ mcp_server_config.json │
|
||||
│ │ │
|
||||
│ ┌───────────────┼───────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ Coding Agent │
|
||||
│ coding_agent.py ◄──► mcp_server_adapter.py │
|
||||
│ │ stdio (Subprocess pro Aufruf) │
|
||||
│ ┌────────────────┼───────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ mcp_server_file_search mcp_server_web mcp_server_code │
|
||||
│ │
|
||||
│ (Optional: mcp_server_adapter_RAG.py für semantische │
|
||||
│ Tool-Auswahl via Sentence Transformers) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
└──────────────┬──────────────────────────────────────────────────┘
|
||||
│ lesen / schreiben
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ workspace/ │
|
||||
│ Isoliertes Sandbox-Verzeichnis für Agent-Dateien │
|
||||
│ workspace/ (isoliertes Sandbox-Verzeichnis) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Async-Bridge:** Streamlit läuft synchron. Der `CodingAgent` verwendet async-Methoden
|
||||
(da die MCP-Client-Bibliothek async ist). `_run_async(coro)` in `chat.py` erstellt pro
|
||||
Aufruf eine neue Event-Loop (`asyncio.new_event_loop()`), was im Thread-Modell von
|
||||
Streamlit sicher ist, da auf dem UI-Thread keine Loop läuft.
|
||||
|
||||
**MCP-Verbindungsmodell:** Der Adapter öffnet pro Tool-Aufruf eine neue stdio-Verbindung
|
||||
statt eine persistente Session zu halten. Das vermeidet die Komplexität langlebiger
|
||||
async-Kontextmanager über Streamlit-Reruns hinweg.
|
||||
|
||||
---
|
||||
|
||||
## Wichtige Designentscheidungen
|
||||
|
||||
### Warum `get_file_tree()` statt einer flachen Dateiliste?
|
||||
Der interaktive Datei-Explorer in der Sidebar benötigt eine verschachtelte Struktur,
|
||||
um das Baum-Widget aufzubauen. Eine flache Liste würde die clientseitige Rekonstruktion
|
||||
von Eltern-Kind-Beziehungen erfordern. Der MCP-Server `mcp_server_file_search.py` stellt
|
||||
sein eigenes `list_files()`-Tool für den Agenten bereit, wo eine flache Auflistung
|
||||
für das LLM nützlicher ist.
|
||||
|
||||
### Warum wird der System-Prompt bei jeder Nachricht neu generiert?
|
||||
Die im Editor aktive Datei kann sich zwischen Nachrichten ändern. Durch das Neuerstellen
|
||||
des Prompts hat die KI immer den aktuellen Dateikontext. Der vorhandene System-Nachrichten-Eintrag
|
||||
wird in-place aktualisiert (nicht angehängt), sodass die History stets genau eine
|
||||
System-Nachricht enthält.
|
||||
|
||||
### Warum ist die Chat-History auf ein Sliding-Window begrenzt?
|
||||
`ChatManager._build_payload_messages()` behält die System-Nachricht und die letzten 20 Turns.
|
||||
Das verhindert, dass die Payload während langer Sitzungen das Kontextlimit des Modells
|
||||
überschreitet, während der System-Prompt immer erhalten bleibt. Der Agent hat eine eigene,
|
||||
separate Kürzungslogik (`trim_messages()`), die zusätzlich die ursprüngliche Aufgabenbeschreibung
|
||||
bewahrt.
|
||||
|
||||
### Warum werden MCP-Tool-Verbindungen pro Aufruf geöffnet?
|
||||
Streamlit führt das gesamte Script bei jeder Benutzerinteraktion erneut aus. Eine lebende
|
||||
async-MCP-Session über Reruns hinweg zu erhalten würde entweder einen Hintergrund-Thread
|
||||
oder eine persistente asyncio-Loop erfordern — beides erhöht Komplexität und Fehleranfälligkeit.
|
||||
Verbindungen pro Aufruf sind einfacher und zuverlässiger, auf Kosten eines kleinen
|
||||
Subprocess-Start-Overheads pro Tool-Aufruf.
|
||||
|
||||
### Warum wird Fehler-Output NICHT automatisch in den normalen Chat injiziert?
|
||||
Das automatische Einschleusen jedes Laufzeitfehlers würde die Chat-History schnell mit
|
||||
Rauschen überfluten. Stattdessen entscheidet der Benutzer selbst, wann er die KI über
|
||||
den Button «Mit KI debuggen» im Editor einbezieht. Der Agent-Mode behandelt dies anders:
|
||||
Tool-Fehler werden immer als Beobachtungen an das LLM zurückgegeben und lösen automatisches
|
||||
Replanning aus.
|
||||
|
||||
### Warum setzt `run_python_sandboxed()` `PYTHONIOENCODING=utf-8`?
|
||||
Unter Windows ist die Standard-Konsolen-Kodierung cp1252, die Unicode-Zeichen ausserhalb
|
||||
des Latin-1-Bereichs (z. B. Emoji, CJK) nicht kodieren kann. Das Setzen von
|
||||
`PYTHONIOENCODING=utf-8` in der Subprocess-Umgebung stellt sicher, dass `print()` für
|
||||
beliebige Unicode-Inhalte korrekt funktioniert.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
### Tests ausführen
|
||||
|
||||
```bash
|
||||
pytest tests/ -v # alle Tests
|
||||
pytest tests/test_chat_manager.py -v # einzelne Datei
|
||||
pytest tests/ -q # Kurzausgabe
|
||||
```
|
||||
|
||||
### Test-Architektur
|
||||
|
||||
`conftest.py` patcht `MCPToolAdapter` auf `sys.modules`-Ebene **vor** dem Import eines
|
||||
Testmoduls. Das verhindert, dass `coding_agent.py`'s Modul-Level-Aufruf
|
||||
`asyncio.run(adapter.initialize_all_servers())` echte MCP-Subprozesse startet.
|
||||
|
||||
| Testdatei | Getestetes Modul | Wichtige Muster |
|
||||
|-----------|-----------------|-----------------|
|
||||
| `test_chat_manager.py` | `ChatManager` | `@patch("requests.post")` für HTTP |
|
||||
| `test_coding_agent.py` | `CodingAgent` | `@pytest.mark.asyncio`, mock `_call_api` |
|
||||
| `test_debug_logger.py` | `DebugLogger` | autouse-Fixture setzt `_error_log`-Klassenvariable zurück |
|
||||
| `test_execution_engine.py` | `ExecutionEngine` | `@patch("subprocess.run")` |
|
||||
| `test_file_manager.py` | `FileManager` | `tmp_path`-Fixture, mock `st` |
|
||||
| `test_mcp_server_code_execution.py` | MCP-Code-Server | führt echten Python-Code aus |
|
||||
| `test_mcp_server_file_search.py` | MCP-Datei-Server | `monkeypatch` tauscht `ALLOWED_DIR` |
|
||||
| `test_mcp_server_web_search.py` | MCP-Web-Server | patcht `DDGS` im Server-Namespace |
|
||||
| `test_search_manager.py` | `SearchManager` | mockt DDGS-Kontextmanager + requests |
|
||||
| `test_system_prompter.py` | `SystemPrompter` | reine Funktion, kein Mocking nötig |
|
||||
|
||||
### Async-Tests
|
||||
Die Tests in `test_coding_agent.py` verwenden `@pytest.mark.asyncio` aus `pytest-asyncio`.
|
||||
Der `MCPToolAdapter` ist vollständig via `conftest.py` gemockt, sodass kein MCP-Subprocess
|
||||
beteiligt ist.
|
||||
|
||||
---
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
`.env.example` nach `.env` kopieren und ausfüllen:
|
||||
|
||||
| Variable | Beschreibung | Beispiel |
|
||||
|----------|--------------|---------|
|
||||
| `HOST` | Hostname des LLM-API-Endpunkts | `localhost` |
|
||||
| `PORT` | Port des LLM-API-Endpunkts | `8000` |
|
||||
| `API_KEY` | Bearer-Token — `EMPTY` für offene Endpunkte verwenden | `sk-...` |
|
||||
| `MODEL` | Modellname, der in API-Payloads gesendet wird | `mistral-7b` |
|
||||
|
||||
Die App funktioniert mit jedem OpenAI-kompatiblen API-Endpunkt (vLLM, Ollama mit
|
||||
OpenAI-Shim, OpenAI selbst usw.).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user