2026-05-29 17:37:51 +02:00
2026-05-29 13:47:28 +02:00
2026-05-29 13:47:28 +02:00
2026-05-29 14:33:13 +02:00
2026-05-29 17:37:51 +02:00
2026-05-28 11:19:44 +02:00

AISE AI Code Editor — Technische Dokumentation

KI-unterstützter Lightweight Code Editor auf Basis von Streamlit (AISE501 Spring 2026).


Projektinformationen

Modul AI in Software Engineering 1 (AISE501)
Autoren Irina Rüegg & Livio Meuli
Semester Spring 2026

Inhaltsverzeichnis

  1. Projektstruktur
  2. Schnellstart
  3. Frontend
  4. Backend Manager
  5. Backend Agent (MCP-System)
  6. MCP-Server-Konfiguration
  7. Architektur-Übersicht
  8. Wichtige Designentscheidungen
  9. Tests
  10. Umgebungsvariablen

Projektstruktur

AISE_AIAgent/
├── frontend/                          # Streamlit UI-Komponenten
│   ├── 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/
│   ├── 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 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/                             # pytest-Unit-Tests
│   ├── conftest.py                    # Globaler MCP-Mock (keine echten Subprozesse in Tests)
│   ├── test_chat_manager.py
│   ├── test_coding_agent.py
│   ├── test_debug_logger.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_search_manager.py
│   └── test_system_prompter.py
│
├── 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

Schnellstart

Die folgenden Schritte funktionieren auf Windows und macOS — abweichende Befehle sind jeweils mit dem Betriebssystem gekennzeichnet.


Schritt 1 — Voraussetzungen prüfen

Python 3.10 oder neuer muss installiert sein.

# Windows (PowerShell)
python --version

# macOS (Terminal)
python3 --version

Falls Python nicht installiert ist:

  • Windows: python.org/downloads herunterladen und installieren. Bei der Installation „Add Python to PATH" aktivieren.
  • macOS: python.org/downloads herunterladen und installieren, oder via Homebrew: brew install python3

Git muss ebenfalls installiert sein:

git --version

Falls nicht vorhanden: git-scm.com (Windows) bzw. brew install git (macOS).


Schritt 2 — Repository klonen

git clone https://gitea.fhgr.ch/meulilivio/AISE1_Project_Irina_Livio.git
cd AISE1_Project_Irina_Livio

Schritt 3 — Virtuelle Umgebung erstellen

Eine virtuelle Umgebung isoliert die Projekt-Abhängigkeiten vom restlichen System. Sie muss einmalig erstellt werden.

# Windows (PowerShell)
python -m venv .venv

# macOS (Terminal)
python3 -m venv .venv

Schritt 4 — Virtuelle Umgebung aktivieren

Die Umgebung muss jedes Mal neu aktiviert werden, wenn ein neues Terminal geöffnet wird.

# Windows (PowerShell)
.\.venv\Scripts\Activate.ps1

Falls PowerShell die Ausführung blockiert, einmalig folgenden Befehl ausführen und danach erneut versuchen:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# macOS (Terminal)
source .venv/bin/activate

Nach erfolgreicher Aktivierung erscheint (.venv) am Anfang der Eingabezeile.


Schritt 5 — Abhängigkeiten installieren

pip install -r requirements.txt

Dieser Schritt lädt alle benötigten Pakete herunter (~25 Minuten je nach Internetverbindung). Er muss nur einmal ausgeführt werden.


Schritt 6 — Umgebungsvariablen konfigurieren

# Windows (PowerShell)
copy .env.example .env

# macOS (Terminal)
cp .env.example .env

Danach die Datei .env in einem Texteditor öffnen und die Werte eintragen welche per Mail mitgeteilt wurden (HOST, PORT, API_KEY, MODEL).


Schritt 7 — App starten

# Windows & macOS
streamlit run frontend/app.py

Streamlit öffnet die App automatisch im Standard-Browser unter http://localhost:8501.
Falls der Browser nicht automatisch aufgeht, die URL manuell eingeben.

Zum Beenden der App im Terminal Ctrl + C drücken.


Schritt 8 — Tests ausführen (optional)

pytest tests/ -v

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()):

  • Kompakte 4-spaltige Toolbar direkt über dem Chat-Input: [● Agent Mode] [🔍 Search] [🗑️ Clear] [⚙️ Settings] — Web-Suche und Einstellungen jeweils als st.popover, Clear öffnet einen Bestätigungs-Dialog.
  • System-Prompt wird vor jeder ausgehenden Nachricht neu generiert (_set_system_prompt()).
  • Unterstützt Slash-Befehle /search <Abfrage> und /search clear.
  • Settings-Popover: 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:

SystemPrompter.generate_prompt(
    user_message="",
    file_context=None,      # {"name": str, "content": str}
    search_context=None,    # list[{"title", "url", "snippet"}] — in system_prompter.py implementiert,
                            # aber in chat.py nicht verwendet: dort wird Search-Kontext direkt
                            # als <search_context>-Block vor die Nachricht eingefügt
    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 sys.executable (plattformübergreifend; zeigt auf den aktuell aktiven Python-Interpreter)

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:

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:

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:

{"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 in die History; propose_next_action() wird danach separat aufgerufen
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()
build_all_tool_description() 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:

  • 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, 15 s Timeout, Output begrenzt auf MAX_OUTPUT_LENGTH

MCP-Server-Konfiguration

Format: backend/agent/mcp_server_config.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:
    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:
    "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

┌─────────────────────────────────────────────────────────────────┐
│  Frontend (Streamlit)                                           │
│  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                    │
└──────────────┬──────────────────────────────────────────────────┘
               │ async-Aufrufe (via _run_async-Bridge)
               ▼
┌─────────────────────────────────────────────────────────────────┐
│  Coding Agent                                                   │
│  coding_agent.py  ◄──►  mcp_server_adapter.py                  │
│                               │ stdio (Subprocess pro Aufruf)   │
│              ┌────────────────┼───────────────┐                 │
│              ▼                ▼               ▼                 │
│  mcp_server_file_search  mcp_server_web  mcp_server_code        │
└──────────────┬──────────────────────────────────────────────────┘
               │ lesen / schreiben
               ▼
┌─────────────────────────────────────────────────────────────────┐
│  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

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.).


Einsatz von KI-Werkzeugen

Während der Entwicklung wurden KI-Assistenten (Claude, GitHub Copilot) als Werkzeuge eingesetzt — vergleichbar mit der Nutzung von Dokumentation, Stack Overflow oder einer IDE mit Autocomplete.

Konkret bedeutet das:

  • Eigenständige Konzeption und Architektur: Die Gesamtarchitektur (Schichtentrennung Frontend / Manager / Agent), die Designentscheidungen und die Aufteilung in Komponenten wurden selbst erarbeitet und geplant.
  • Implementierung mit Unterstützung: Boilerplate-Code, Docstrings und einzelne Hilfsfunktionen wurden teils mit KI-Unterstützung geschrieben, verstanden und anschliessend in das Projekt integriert.
  • MCP-Integration und Chat-Logik: Für das Model Context Protocol und den Chat-Assistenten haben wir uns an den Kursbeispielen des Dozenten orientiert und diese als Ausgangsbasis adaptiert und erweitert.
  • Debugging und Refactoring: KI wurde als Gesprächspartner genutzt, um Fehler zu analysieren und Lösungsansätze zu diskutieren — die Entscheidungen wurden jedoch eigenständig getroffen und umgesetzt.

Der gesamte Code wurde von uns gelesen, verstanden und bewusst eingesetzt. Unkritisch übernommener oder nicht verstandener Code wurde nicht ins Projekt aufgenommen.

Description
Code for the project we have to complete as part of the Artificial Intelligence in Software Engineering module. It is a coding agent.
Readme 1.4 MiB
Languages
Python 100%