Files
Orchestrator/bahn/teamlandkarte-mcp/.kiro/specs/llm-competence-inference/design.md
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

221 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LLM-Kompetenz-Inferenz Bugfix Design
## Übersicht
Die Kompetenz-Inferenz in `validate_task_requirements`, `extract_requirements` und `find_matching_tasks` (Score-Modus) ist defekt, weil der alte Embedding-Ansatz entfernt wurde, ohne einen Ersatz zu implementieren. Die Variable `inferred_comps` ist stets eine leere Liste `[]`.
Der Fix implementiert eine neue Methode `infer_competences` in der bestehenden `VocabularyCache`-Klasse, analog zum bereits funktionierenden `infer_primary_role`. Diese Methode nutzt den `AzureOpenAIClient.chat_completion`-Aufruf, um aus der vollständigen Kompetenzliste (`get_all_competence_names()`) bis zu 10 passende Kompetenzen mit Konfidenzwerten auszuwählen.
## Glossar
- **Bug_Condition (C)**: Der Zustand, in dem ein Task-Text vorhanden ist UND Kompetenzen in der DB existieren, aber `inferred_comps` trotzdem `[]` zurückgibt
- **Property (P)**: Das gewünschte Verhalten eine nicht-leere Liste von bis zu 10 `(competence_name, confidence)`-Tupeln, wobei jeder Name in `get_all_competence_names()` enthalten ist
- **Preservation**: Die bestehende Rollen-Inferenz (`infer_primary_role`), DB-Skills-Anzeige und LLM-Fulltext-Matching bleiben unverändert
- **VocabularyCache**: Klasse in `src/teamlandkarte_mcp/matching/vocabulary.py`, die LLM-basierte Inferenz kapselt
- **AzureOpenAIClient**: Client in `src/teamlandkarte_mcp/azure/openai_client.py` mit `chat_completion(system, user) -> str`
- **inferred_comps**: Die lokale Variable in den betroffenen Tools, die aktuell immer `[]` ist
## Bug-Details
### Fault Condition
Der Bug manifestiert sich, wenn ein Tool (`validate_task_requirements`, `extract_requirements`, `find_matching_tasks` im Score-Modus) einen nicht-leeren Task-Text verarbeitet und Kompetenzen in der DB vorhanden sind. Die Kompetenz-Inferenz liefert stets eine leere Liste, weil kein LLM-Aufruf stattfindet.
**Formale Spezifikation:**
```
FUNCTION isBugCondition(input)
INPUT: input of type {task_text: str, competence_names: list[str]}
OUTPUT: boolean
RETURN input.task_text.strip() != ""
AND len(input.competence_names) > 0
AND infer_competences(input.task_text) == []
END FUNCTION
```
### Beispiele
- `validate_task_requirements("task-123")` mit Task-Titel "Python Backend Entwicklung" und 50 Kompetenzen in der DB → Erwartung: bis zu 10 Kompetenzen mit Konfidenz; Aktuell: leere Tabelle
- `extract_requirements("Wir brauchen einen React-Entwickler mit TypeScript-Erfahrung")` mit Kompetenzen ["React", "TypeScript", "Angular", ...] in der DB → Erwartung: ["React", "TypeScript", ...] mit Konfidenz; Aktuell: `inferred_competences = []`
- `find_matching_tasks(capacity_id=1)` im Score-Modus → Erwartung: `inferred_comp_names` enthält LLM-inferierte Kompetenzen für das Scoring; Aktuell: nur DB-Skills werden verwendet
- Leerer Task-Text → Erwartung: leere Liste (kein Fehler) dieses Verhalten ist korrekt und bleibt erhalten
## Erwartetes Verhalten
### Preservation Requirements
**Unverändertes Verhalten:**
- `infer_primary_role` muss weiterhin genau eine Rolle mit Konfidenz zurückgeben
- DB-Skills eines Tasks müssen weiterhin korrekt in der Ausgabe angezeigt werden
- LLM-Fulltext-Matching (`find_matching_capacities` / `find_matching_tasks` im `llm_fulltext`-Modus) bleibt unverändert
- Fehlerbehandlung bei LLM-Aufruf-Fehlern für Rollen-Inferenz bleibt unverändert (`None` zurückgeben)
- Die `AzureOpenAIClient`-Schnittstelle wird nicht verändert
**Scope:**
Alle Eingaben, die KEINEN nicht-leeren Task-Text mit vorhandenen Kompetenzen in der DB kombinieren, sind vom Fix nicht betroffen:
- Leerer Task-Text → weiterhin leere Liste
- Keine Kompetenzen in der DB → weiterhin leere Liste
- Mausklick-/UI-Interaktionen → nicht betroffen (MCP-Server)
- LLM-Fulltext-Modus → verwendet eigene Logik, nicht betroffen
## Hypothesierte Ursache
Basierend auf der Bug-Analyse sind die Ursachen klar identifiziert:
1. **Fehlende Implementierung**: Die alte `infer_competences`-Methode (Embedding-basiert) wurde entfernt. An den drei Stellen im Code steht nur noch `inferred_comps: list[tuple[str, float]] = []` bzw. `inferred_competences: list[tuple[str, float]] = []` ohne jeglichen LLM-Aufruf.
2. **Kein System-Prompt für Kompetenz-Inferenz**: Im Gegensatz zu `_ROLE_INFERENCE_SYSTEM_PROMPT` existiert kein entsprechender Prompt für Kompetenz-Inferenz.
3. **Keine Methode in VocabularyCache**: Die Klasse hat nur `infer_primary_role`, aber keine `infer_competences`-Methode.
4. **Keine Integration in die Tools**: Selbst wenn eine Methode existieren würde, fehlt der `await`-Aufruf an den drei betroffenen Stellen.
## Correctness Properties
Property 1: Fault Condition - Kompetenz-Inferenz liefert Ergebnisse
_For any_ input where der Task-Text nicht leer ist UND mindestens eine Kompetenz in der DB existiert (isBugCondition returns true), SHALL die fixierte `infer_competences`-Methode eine nicht-leere Liste von bis zu 10 Tupeln `(competence_name, confidence)` zurückgeben, wobei jeder `competence_name` in `get_all_competence_names()` enthalten ist und `confidence` im Bereich [0.0, 1.0] liegt.
**Validates: Requirements 2.1, 2.2, 2.3**
Property 2: Preservation - Rollen-Inferenz und DB-Skills unverändert
_For any_ input (unabhängig davon ob die Bug-Condition gilt oder nicht), SHALL die fixierte Codebasis das gleiche Ergebnis für `infer_primary_role` und die DB-Skills-Anzeige produzieren wie der originale Code, und das LLM-Fulltext-Matching bleibt unverändert.
**Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5**
## Fix-Implementierung
### Erforderliche Änderungen
**Datei**: `src/teamlandkarte_mcp/matching/vocabulary.py`
**Änderung 1: System-Prompt hinzufügen**
- Neuer Modul-Level-Konstante `_COMPETENCE_INFERENCE_SYSTEM_PROMPT` analog zu `_ROLE_INFERENCE_SYSTEM_PROMPT`
- Prompt instruiert das LLM, aus einer gegebenen Kompetenzliste bis zu 10 passende Kompetenzen für einen Task-Text auszuwählen
- Antwortformat: `{"competences": [{"name": "<name>", "confidence": <float>}]}`
**Änderung 2: Neue Methode `infer_competences` in `VocabularyCache`**
- Signatur: `async def infer_competences(self, *, task_text: str, max_competences: int = 10) -> list[tuple[str, float]]`
- Holt Kompetenzliste via `self._db.get_all_competence_names()`
- Bei leerer Liste oder leerem Text: `[]` zurückgeben
- LLM-Aufruf via `self._client.chat_completion(system, user)`
- JSON-Parsing der Antwort, Validierung gegen DB-Kompetenzliste
- Bei Fehler: leere Liste zurückgeben + Warning loggen
---
**Datei**: `src/teamlandkarte_mcp/mcp_server.py`
**Änderung 3: `validate_task_requirements` LLM-Aufruf integrieren**
- Ersetze `inferred_comps: list[tuple[str, float]] = []` durch:
```python
inferred_comps = await vocab_cache.infer_competences(task_text=task_text)
```
**Änderung 4: `extract_requirements` LLM-Aufruf integrieren**
- Ersetze `inferred_competences: list[tuple[str, float]] = []` durch:
```python
inferred_competences = await vocab_cache.infer_competences(task_text=desc)
```
**Änderung 5: `find_matching_tasks` (Score-Modus) LLM-Aufruf integrieren**
- Ersetze den Block `inferred_comp_names = [str(x) for x in (getattr(t, "skills", None) or []) if x]` durch:
```python
inferred_comp_tuples = await vocab_cache.infer_competences(task_text=full_text)
inferred_comp_names = [name for name, _conf in inferred_comp_tuples]
if not inferred_comp_names:
inferred_comp_names = [str(x) for x in (getattr(t, "skills", None) or []) if x]
```
## Testing-Strategie
### Validierungsansatz
Die Testing-Strategie folgt einem zweiphasigen Ansatz: Zuerst Counterexamples auf dem unfixierten Code aufdecken, dann den Fix verifizieren und Preservation sicherstellen.
### Exploratory Fault Condition Checking
**Ziel**: Counterexamples aufdecken, die den Bug VOR der Implementierung des Fixes demonstrieren. Root-Cause-Analyse bestätigen oder widerlegen.
**Testplan**: Tests schreiben, die `infer_competences` (bzw. die betroffenen Tools) mit nicht-leerem Task-Text und vorhandenen Kompetenzen aufrufen. Auf dem unfixierten Code beobachten, dass stets `[]` zurückkommt.
**Testfälle**:
1. **validate_task_requirements mit gültigem Task**: Aufruf mit Task-ID, der einen beschriebenen Task hat (wird auf unfixiertem Code leere Kompetenz-Tabelle liefern)
2. **extract_requirements mit Freitext**: Aufruf mit beschreibendem Text (wird auf unfixiertem Code `inferred_competences = []` liefern)
3. **find_matching_tasks im Score-Modus**: Aufruf mit Capacity-ID (wird auf unfixiertem Code nur DB-Skills verwenden, keine LLM-Inferenz)
4. **Leerer Task-Text**: Aufruf mit leerem Text (soll auch nach Fix `[]` liefern Baseline)
**Erwartete Counterexamples**:
- `inferred_comps` ist immer `[]`, unabhängig vom Task-Text
- Ursache: Kein LLM-Aufruf, keine `infer_competences`-Methode vorhanden
### Fix Checking
**Ziel**: Verifizieren, dass für alle Eingaben, bei denen die Bug-Condition gilt, die fixierte Funktion das erwartete Verhalten produziert.
**Pseudocode:**
```
FOR ALL input WHERE isBugCondition(input) DO
result := infer_competences_fixed(input.task_text)
ASSERT len(result) > 0
ASSERT len(result) <= 10
FOR EACH (name, confidence) IN result DO
ASSERT name IN get_all_competence_names()
ASSERT 0.0 <= confidence <= 1.0
END FOR
END FOR
```
### Preservation Checking
**Ziel**: Verifizieren, dass für alle Eingaben, bei denen die Bug-Condition NICHT gilt, die fixierte Funktion das gleiche Ergebnis wie die originale Funktion produziert.
**Pseudocode:**
```
FOR ALL input WHERE NOT isBugCondition(input) DO
ASSERT infer_competences_fixed(input.task_text) == []
END FOR
FOR ALL input DO
ASSERT infer_primary_role_fixed(input) == infer_primary_role_original(input)
END FOR
```
**Testing-Ansatz**: Property-Based Testing wird für Preservation Checking empfohlen, weil:
- Es automatisch viele Testfälle über den Eingabebereich generiert
- Es Randfälle findet, die manuelle Unit-Tests übersehen könnten
- Es starke Garantien bietet, dass Verhalten für alle nicht-buggy Eingaben unverändert bleibt
**Testplan**: Verhalten auf unfixiertem Code zuerst beobachten (leere Ergebnisse, funktionierende Rollen-Inferenz), dann Property-Based Tests schreiben, die dieses Verhalten nach dem Fix verifizieren.
**Testfälle**:
1. **Rollen-Inferenz Preservation**: Verifizieren, dass `infer_primary_role` nach dem Fix identische Ergebnisse liefert
2. **DB-Skills Preservation**: Verifizieren, dass DB-Skills weiterhin korrekt angezeigt werden
3. **Leerer Text Preservation**: Verifizieren, dass leerer Task-Text weiterhin `[]` liefert
4. **LLM-Fulltext Preservation**: Verifizieren, dass der LLM-Fulltext-Modus nicht beeinflusst wird
### Unit Tests
- Test `infer_competences` mit gemocktem LLM-Client: gültige JSON-Antwort → korrekte Tupel-Liste
- Test `infer_competences` mit leerem Task-Text → `[]`
- Test `infer_competences` mit leerer Kompetenzliste in DB → `[]`
- Test `infer_competences` bei LLM-Fehler (Exception) → `[]` + Warning geloggt
- Test `infer_competences` bei ungültiger JSON-Antwort → `[]`
- Test `infer_competences` bei Kompetenz-Namen die nicht in DB sind → werden herausgefiltert
- Test `validate_task_requirements` liefert nicht-leere Kompetenz-Tabelle
- Test `extract_requirements` liefert nicht-leere `inferred_competences`
### Property-Based Tests
- Generiere zufällige Task-Texte und Kompetenzlisten; verifiziere, dass Ergebnisse stets Subset der DB-Kompetenzen sind
- Generiere zufällige Eingaben; verifiziere, dass Konfidenzwerte immer in [0.0, 1.0] liegen
- Generiere zufällige Eingaben; verifiziere, dass maximal 10 Kompetenzen zurückgegeben werden
- Generiere zufällige Eingaben; verifiziere, dass `infer_primary_role` unverändert funktioniert (Preservation)
### Integration Tests
- End-to-End Test: `validate_task_requirements` mit echtem (gemocktem) LLM-Client zeigt Kompetenzen
- End-to-End Test: `extract_requirements` → `find_matching_capacities` Pipeline mit inferierten Kompetenzen
- End-to-End Test: `find_matching_tasks` im Score-Modus nutzt inferierte Kompetenzen für besseres Scoring