# Implementierungsplan: LLM Batch-Matching (Concurrent Requests) ## Übersicht Sequenzielle LLM-Aufrufe in `LlmFulltextMatcher` werden durch `asyncio.gather()` mit Semaphore-Begrenzung ersetzt. Die Konfiguration wird um `max_concurrency` erweitert. Die öffentliche Schnittstelle bleibt unverändert. ## Tasks - [x] 1. Konfiguration erweitern - [x] 1.1 `AzureOpenAIConfig` um Feld `max_concurrency: int = 5` erweitern - In `src/teamlandkarte_mcp/config.py` das frozen Dataclass `AzureOpenAIConfig` um das Feld ergänzen - _Anforderungen: 2.5_ - [x] 1.2 Validierung in `_parse_azure_openai` hinzufügen - `max_concurrency` aus Config lesen mit Default 5 - `ConfigError` werfen wenn Wert < 1 oder > 20 - Feld an `AzureOpenAIConfig`-Konstruktor übergeben - Auch im `load_config`-Rebuild-Block (`azure_openai = AzureOpenAIConfig(...)`) das neue Feld durchreichen - _Anforderungen: 2.1, 2.2, 2.3, 2.4_ - [x] 1.3 `config.toml` um kommentierten `max_concurrency`-Schlüssel erweitern - Unter `[azure_openai]` einen Kommentar mit Erklärung und auskommentierten Default-Wert einfügen - _Anforderungen: 2.1_ - [x] 1.4 Property-Test für Config-Validierung schreiben - **Property 6: Config-Validierung** - Teste mit hypothesis: `st.integers(min_value=-100, max_value=100)` – Erfolg genau dann wenn 1 <= n <= 20, sonst `ConfigError` - **Validiert: Anforderungen 2.1, 2.3, 2.4** - [x] 2. `LlmFulltextMatcher` um Concurrency erweitern - [x] 2.1 Konstruktor um `max_concurrency`-Parameter und Semaphore erweitern - `max_concurrency: int = 5` als keyword-only Parameter hinzufügen - `self._semaphore = asyncio.Semaphore(max_concurrency)` im `__init__` anlegen - `self._max_concurrency = max_concurrency` speichern - `import asyncio` und `import time` ergänzen - _Anforderungen: 1.2, 1.5, 5.4_ - [x] 2.2 Neue Methode `_categorize_one_throttled` implementieren - Async-Wrapper um `_categorize_one` der `async with self._semaphore:` verwendet - Gleiche Signatur wie `_categorize_one` (item_id, user_prompt, raw) - _Anforderungen: 1.2, 1.4, 1.5_ - [x] 2.3 `match_capacities` auf `asyncio.gather` umstellen - Sequenzielle for-Schleife durch Liste von `_categorize_one_throttled`-Aufrufen ersetzen - `asyncio.gather(*tasks)` für parallele Ausführung verwenden - Ergebnisse iterieren und in `by_category` / `errors` einsortieren - Logging (INFO) vor und nach dem Batch mit Kandidatenanzahl, Concurrency, Dauer, Erfolge, Fehler - Bestehende Sortierung beibehalten - _Anforderungen: 1.1, 3.1, 3.2, 4.2, 4.3, 4.4, 5.1, 6.1, 6.2_ - [x] 2.4 `match_tasks` auf `asyncio.gather` umstellen - Analog zu 2.3: sequenzielle Schleife durch gather + throttled ersetzen - Logging analog zu `match_capacities` - _Anforderungen: 1.1, 3.1, 3.2, 4.2, 4.3, 4.4, 5.1, 6.1, 6.2_ - [x] 3. MCP-Server Verdrahtung - [x] 3.1 `max_concurrency` an `LlmFulltextMatcher` übergeben - In `build_server()` bei der Instanziierung von `LlmFulltextMatcher` den Wert `cfg.azure_openai.max_concurrency` übergeben - _Anforderungen: 2.1, 5.4_ - [x] 4. Checkpoint - Sicherstellen dass alle bestehenden Tests weiterhin bestehen. Bei Fragen den Nutzer konsultieren. - [x] 5. Property-Based Tests - [x] 5.1 Property-Test: Vollständigkeit der Ergebnisse (Partition) - **Property 1: Vollständigkeit der Ergebnisse** - Generiere zufällige Kandidatenlisten mit Mock-Client (Mix aus Erfolg/Fehler); prüfe `len(by_category items) + len(errors) == len(input)` - **Validiert: Anforderungen 1.1, 1.4, 3.2, 4.2, 4.3** - [x] 5.2 Property-Test: Concurrency-Begrenzung - **Property 2: Concurrency-Begrenzung** - Mock-Client mit asyncio-Counter für gleichzeitige Aufrufe; prüfe `max_concurrent <= max_concurrency` für verschiedene Werte - **Validiert: Anforderungen 1.2** - [x] 5.3 Property-Test: Deterministische Sortierung - **Property 3: Deterministische Sortierung** - Generiere Ergebnisse mit zufälligen Kategorien; prüfe dass jede Kategorie nach `item_id` aufsteigend sortiert ist - **Validiert: Anforderungen 3.1** - [x] 5.4 Property-Test: Eingabereihenfolge-Unabhängigkeit - **Property 4: Eingabereihenfolge-Unabhängigkeit** - Generiere Kandidatenliste, permutiere, führe Matching aus, vergleiche Ergebnisse auf Gleichheit - **Validiert: Anforderungen 3.3** - [x] 5.5 Property-Test: Korrekte Zuordnung (Response-Mapping) - **Property 5: Korrekte Zuordnung** - Mock gibt item_id-spezifische Kategorien zurück; prüfe dass jedes Item in der korrekten Kategorie landet - **Validiert: Anforderungen 3.4** - [x] 5.6 Property-Test: Logging-Konsistenz - **Property 7: Logging-Konsistenz** - Capture Logs mit `caplog`; prüfe dass Start- und End-Log die korrekte Kandidatenanzahl und Summe (Erfolge + Fehler) enthalten - **Validiert: Anforderungen 6.1, 6.2** - [x] 6. Unit Tests - [x] 6.1 Unit Tests für Batch-Matching schreiben - Leere Eingabe: sofort leeres Ergebnis ohne LLM-Aufrufe - Alle Fehler: keine Exception, vollständige Fehlerliste - Default-Wert: ohne `max_concurrency` wird 5 verwendet - Einzelner Fehler beeinflusst andere Kandidaten nicht - _Anforderungen: 4.2, 4.3, 4.4, 5.3_ - [x] 7. Abschluss-Checkpoint - Sicherstellen dass alle Tests bestehen und die Schnittstelle abwärtskompatibel bleibt. Bei Fragen den Nutzer konsultieren. ## Hinweise - Tasks mit `*` sind optional und können für ein schnelleres MVP übersprungen werden - Jeder Task referenziert spezifische Anforderungen für Nachvollziehbarkeit - Property-Tests verwenden `hypothesis` (pytest-Plugin) - Der Score-Modus bleibt vollständig unberührt (Anforderung 7)