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.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"specId": "6c39340f-10af-426d-9318-c96cb2f7ad6d", "workflowType": "requirements-first", "specType": "feature"}
|
||||
@@ -0,0 +1,677 @@
|
||||
# Design: Entfernung Embedding-basierter Similarity – Umstellung auf BM25 + LLM
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses Design beschreibt die vollständige Entfernung der Embedding-Infrastruktur aus dem Teamlandkarte-MCP-System. Das System wird von einem hybriden Embedding/BM25-Ansatz auf eine reine BM25 + LLM-Architektur umgestellt:
|
||||
|
||||
- **Kompetenz-Matching**: Ausschließlich BM25+RRF (bereits implementiert, nur Conditional-Logik entfernen)
|
||||
- **Rollen-Similarity**: LLM Chat Completion (neu, ersetzt Embedding-Cosine-Similarity)
|
||||
- **Rollen-Inferenz**: LLM Chat Completion (neu, ersetzt Embedding-basierte Nearest-Neighbor-Suche)
|
||||
- **AutoTagger**: Bleibt unverändert (bereits LLM-basiert)
|
||||
|
||||
Entfernt werden: `EmbeddingCache`, `_embed`, `prefetch_embeddings`, `_per_skill_similarity`, `_aggregate_similarity`, Embedding-API-Methoden im `AzureOpenAIClient`, alle Embedding-Konfigurationsfelder, und der Startup-Preload.
|
||||
|
||||
## Architektur
|
||||
|
||||
### Vorher (Hybrid)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[MCP Server Startup] --> B[Preload Embeddings]
|
||||
B --> C[VocabularyCache: Role + Competence Embeddings]
|
||||
B --> D[Task Embeddings]
|
||||
|
||||
E[Matching Request] --> F{use_bm25_search?}
|
||||
F -->|true| G[BM25+RRF Competence Similarity]
|
||||
F -->|false| H[Embedding Cosine Competence Similarity]
|
||||
|
||||
E --> I[Embedding Cosine Role Similarity]
|
||||
|
||||
J[infer_primary_role] --> K[Task Embedding → Cosine vs Role Vocab]
|
||||
```
|
||||
|
||||
### Nachher (BM25 + LLM)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[MCP Server Startup] --> B[DB Schema Verification]
|
||||
A --> C[AzureOpenAIClient: nur Chat Completion]
|
||||
|
||||
E[Matching Request] --> G[BM25+RRF Competence Similarity]
|
||||
E --> I[LLM Role Similarity mit In-Run Cache]
|
||||
|
||||
J[infer_primary_role] --> K[LLM Chat Completion: Task Text → Role Selection]
|
||||
```
|
||||
|
||||
### Datenfluss Matching (Nachher)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Matcher
|
||||
participant SimilarityEngine
|
||||
participant BM25
|
||||
participant LLM as AzureOpenAIClient (Chat)
|
||||
|
||||
Client->>Matcher: match(capacities, requirements)
|
||||
Matcher->>Matcher: Filter by availability
|
||||
Matcher->>BM25: Build global index (all candidate competences)
|
||||
|
||||
loop Per Candidate
|
||||
Matcher->>SimilarityEngine: compute_competence_similarity(required, candidate, global_index)
|
||||
SimilarityEngine->>BM25: rank per required competence
|
||||
SimilarityEngine-->>Matcher: {req → {score, best_match, rationale}}
|
||||
|
||||
Matcher->>SimilarityEngine: compute_role_similarity(required_role, candidate_role)
|
||||
SimilarityEngine->>SimilarityEngine: Check in-run cache
|
||||
alt Cache Miss
|
||||
SimilarityEngine->>LLM: chat_completion(role_similarity_prompt)
|
||||
LLM-->>SimilarityEngine: {"similarity": 0.85}
|
||||
SimilarityEngine->>SimilarityEngine: Cache result
|
||||
end
|
||||
SimilarityEngine-->>Matcher: float score
|
||||
end
|
||||
|
||||
Matcher-->>Client: MatchResult
|
||||
```
|
||||
|
||||
## Komponenten und Schnittstellen
|
||||
|
||||
### 1. SimilarityEngine (refactored)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/matching/similarity.py`
|
||||
|
||||
**Entfernte Methoden:**
|
||||
- `prefetch_embeddings`
|
||||
- `_embed`
|
||||
- `get_embedding_for_cache_key`
|
||||
- `get_embeddings_for_cache_keys`
|
||||
- `_aggregate_similarity`
|
||||
- `_per_skill_similarity`
|
||||
|
||||
**Entfernte Properties:**
|
||||
- `embedding_model`
|
||||
- `embedding_dimensions`
|
||||
- `use_bm25_search`
|
||||
|
||||
**Entfernte Constructor-Parameter:**
|
||||
- `cache` (EmbeddingCache)
|
||||
- `embedding_model`
|
||||
- `embedding_dimensions`
|
||||
- `strategy`
|
||||
- `use_bm25_search`
|
||||
|
||||
**Neuer Constructor:**
|
||||
|
||||
```python
|
||||
class SimilarityEngine:
|
||||
"""Compute similarity scores using BM25+RRF and LLM."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
client: AzureOpenAIClient,
|
||||
cost_tracker: CostTracker | None = None,
|
||||
use_auto_tagging: bool = False,
|
||||
auto_tagger: AutoTagger | None = None,
|
||||
) -> None:
|
||||
self._client = client
|
||||
self._cost_tracker = cost_tracker
|
||||
self._use_auto_tagging = use_auto_tagging
|
||||
self._auto_tagger = auto_tagger
|
||||
# Per-run cache for LLM role similarity: (role_a, role_b) → score
|
||||
self._role_similarity_cache: dict[tuple[str, str], float] = {}
|
||||
```
|
||||
|
||||
**Geänderte Methode `compute_competence_similarity`:**
|
||||
|
||||
```python
|
||||
async def compute_competence_similarity(
|
||||
self,
|
||||
required: list[str],
|
||||
candidate: list[str],
|
||||
global_index: Bm25Index | None = None,
|
||||
) -> dict[str, dict[str, object]]:
|
||||
"""BM25+RRF competence similarity (einziger Pfad)."""
|
||||
working = list(candidate)
|
||||
if self._use_auto_tagging and self._auto_tagger is not None:
|
||||
working = await self._auto_tagger.expand_competences(required, candidate)
|
||||
return self._bm25_rrf_similarity(required, working, global_index=global_index)
|
||||
```
|
||||
|
||||
**Neue Methode `compute_role_similarity` (LLM-basiert):**
|
||||
|
||||
```python
|
||||
_ROLE_SIMILARITY_SYSTEM_PROMPT = (
|
||||
"You are a job-role similarity expert. Given two role names, "
|
||||
"determine their semantic similarity on a scale from 0.0 to 1.0. "
|
||||
"0.0 means completely unrelated roles, 1.0 means identical or "
|
||||
"interchangeable roles. Consider synonyms, hierarchy, and domain overlap. "
|
||||
'Respond with a JSON object: {"similarity": <float>}'
|
||||
)
|
||||
|
||||
async def compute_role_similarity(
|
||||
self,
|
||||
required_role: str | None,
|
||||
candidate_role: str | None,
|
||||
) -> float:
|
||||
"""LLM-basierte Rollen-Similarity."""
|
||||
if self._is_bad_role(required_role) or self._is_bad_role(candidate_role):
|
||||
return 0.0
|
||||
|
||||
req_norm = str(required_role).strip().lower()
|
||||
cand_norm = str(candidate_role).strip().lower()
|
||||
|
||||
if req_norm == cand_norm:
|
||||
return 1.0
|
||||
|
||||
# Symmetrischer Cache-Key
|
||||
cache_key = tuple(sorted((req_norm, cand_norm)))
|
||||
if cache_key in self._role_similarity_cache:
|
||||
return self._role_similarity_cache[cache_key]
|
||||
|
||||
try:
|
||||
raw = await self._client.chat_completion(
|
||||
system=_ROLE_SIMILARITY_SYSTEM_PROMPT,
|
||||
user=f"Role A: {required_role}\nRole B: {candidate_role}",
|
||||
)
|
||||
payload = json.loads(raw)
|
||||
score = float(payload.get("similarity", 0.0))
|
||||
score = max(0.0, min(1.0, score))
|
||||
except Exception:
|
||||
score = 0.0
|
||||
|
||||
self._role_similarity_cache[cache_key] = score
|
||||
return score
|
||||
```
|
||||
|
||||
**Neue Methode `clear_role_cache`:**
|
||||
|
||||
```python
|
||||
def clear_role_cache(self) -> None:
|
||||
"""Cache zwischen Matching-Runs leeren."""
|
||||
self._role_similarity_cache.clear()
|
||||
```
|
||||
|
||||
### 2. VocabularyCache (refactored)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/matching/vocabulary.py`
|
||||
|
||||
**Entfernte Methoden:**
|
||||
- `preload`
|
||||
- `_preload_role_vocab`
|
||||
- `_preload_competence_vocab`
|
||||
- `ensure_task_embedding`
|
||||
- `infer_primary_role` (alte Signatur mit `task_embedding`)
|
||||
- `infer_competences`
|
||||
|
||||
**Entfernte Properties:**
|
||||
- `roles`
|
||||
- `competences`
|
||||
|
||||
**Neuer Constructor:**
|
||||
|
||||
```python
|
||||
class VocabularyCache:
|
||||
"""LLM-basierte Rollen-Inferenz aus Task-Text."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
db: DBClient,
|
||||
client: AzureOpenAIClient,
|
||||
) -> None:
|
||||
self._db = db
|
||||
self._client = client
|
||||
```
|
||||
|
||||
**Neue Methode `infer_primary_role` (LLM-basiert):**
|
||||
|
||||
```python
|
||||
_ROLE_INFERENCE_SYSTEM_PROMPT = (
|
||||
"You are a role classification expert. Given a task description and a list "
|
||||
"of available roles, select the single most appropriate role for the task. "
|
||||
"You MUST select exactly one role from the provided list. "
|
||||
"Respond with a JSON object: "
|
||||
'{"role": "<selected role name>", "confidence": <float 0.0-1.0>}'
|
||||
)
|
||||
|
||||
async def infer_primary_role(
|
||||
self,
|
||||
*,
|
||||
task_text: str,
|
||||
) -> tuple[str, float] | None:
|
||||
"""Inferiere die passendste Rolle für einen Task-Text via LLM."""
|
||||
role_names = self._db.get_all_role_names()
|
||||
if not role_names:
|
||||
return None
|
||||
|
||||
text = (task_text or "").strip()
|
||||
if not text:
|
||||
return None
|
||||
|
||||
user_prompt = (
|
||||
f"Task: {text}\n\n"
|
||||
f"Available roles: {json.dumps(list(role_names), ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
try:
|
||||
raw = await self._client.chat_completion(
|
||||
system=_ROLE_INFERENCE_SYSTEM_PROMPT,
|
||||
user=user_prompt,
|
||||
)
|
||||
payload = json.loads(raw)
|
||||
role = str(payload.get("role", "")).strip()
|
||||
confidence = float(payload.get("confidence", 0.0))
|
||||
confidence = max(0.0, min(1.0, confidence))
|
||||
|
||||
# Validierung: Rolle muss in der DB-Liste existieren
|
||||
if role not in set(role_names):
|
||||
return None
|
||||
|
||||
return role, confidence
|
||||
except Exception:
|
||||
return None
|
||||
```
|
||||
|
||||
### 3. AzureOpenAIClient (vereinfacht)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/azure/openai_client.py`
|
||||
|
||||
**Entfernte Methoden:**
|
||||
- `embeddings`
|
||||
- `get_embeddings_batch`
|
||||
|
||||
**Entfernte Constructor-Parameter:**
|
||||
- `embedding_api_key`
|
||||
- `embedding_deployment`
|
||||
- `embedding_batch_size`
|
||||
|
||||
**Neuer Constructor:**
|
||||
|
||||
```python
|
||||
class AzureOpenAIClient:
|
||||
"""Azure OpenAI Chat Completion Client."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
endpoint: str,
|
||||
api_version: str,
|
||||
chat_deployment: str,
|
||||
llm_api_key: str,
|
||||
timeout_s: float = 30.0,
|
||||
max_retries: int = 5,
|
||||
cost_tracker: CostTracker | None = None,
|
||||
) -> None:
|
||||
self._chat_deployment = chat_deployment
|
||||
self._max_retries = max_retries
|
||||
self._timeout_s = timeout_s
|
||||
self._cost_tracker = cost_tracker
|
||||
|
||||
self._chat = AsyncAzureOpenAI(
|
||||
api_key=llm_api_key,
|
||||
azure_endpoint=endpoint,
|
||||
api_version=api_version,
|
||||
)
|
||||
```
|
||||
|
||||
Die `chat_completion`-Methode bleibt unverändert, außer dass die Guard-Clause für fehlende Konfiguration entfällt (Chat ist jetzt immer konfiguriert).
|
||||
|
||||
### 4. Matcher (minimal geändert)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/matching/matcher.py`
|
||||
|
||||
**Änderungen:**
|
||||
- Entfernung der `if self._sim.use_bm25_search`-Bedingung beim BM25-Index-Aufbau
|
||||
- Der globale BM25-Index wird **immer** gebaut (unconditional)
|
||||
|
||||
```python
|
||||
# Vorher:
|
||||
global_bm25_index: Bm25Index | None = None
|
||||
if self._sim.use_bm25_search and filtered:
|
||||
global_corpus = list(...)
|
||||
global_bm25_index = Bm25Index(corpus=global_corpus)
|
||||
|
||||
# Nachher:
|
||||
global_bm25_index: Bm25Index | None = None
|
||||
if filtered:
|
||||
global_corpus = list(
|
||||
{c for cap in filtered for c in cap.competences if c.strip()}
|
||||
)
|
||||
global_bm25_index = Bm25Index(corpus=global_corpus)
|
||||
```
|
||||
|
||||
### 5. MCP Server (vereinfacht)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/mcp_server.py`
|
||||
|
||||
**Entfernte Elemente:**
|
||||
- `_startup_preload_embeddings` Funktion
|
||||
- `_deferred_preload` Funktion und `_preload_started` Flag
|
||||
- `_ensure_preloaded` Funktion
|
||||
- `EmbeddingCache`-Instanziierung
|
||||
- `emb_cache`-Variable
|
||||
- Alle `await _ensure_preloaded()`-Aufrufe in Tools
|
||||
- Import von `EmbeddingCache`
|
||||
- Import von `VocabularyCache` (wird direkt mit `AzureOpenAIClient` konstruiert)
|
||||
|
||||
**Geänderte Konstruktion:**
|
||||
|
||||
```python
|
||||
# Vorher: Embedding-Client + separater LLM-Client
|
||||
azure_client = AzureOpenAIClient(
|
||||
endpoint=..., embedding_api_key=..., embedding_deployment=..., ...
|
||||
)
|
||||
|
||||
# Nachher: Nur noch ein LLM-Client
|
||||
azure_client = AzureOpenAIClient(
|
||||
endpoint=cfg.azure_openai.endpoint,
|
||||
api_version=cfg.azure_openai.api_version,
|
||||
chat_deployment=cfg.azure_openai.chat_deployment,
|
||||
llm_api_key=cfg.azure_openai.llm_api_key,
|
||||
cost_tracker=cost_tracker,
|
||||
)
|
||||
|
||||
similarity = SimilarityEngine(
|
||||
client=azure_client,
|
||||
cost_tracker=cost_tracker,
|
||||
use_auto_tagging=cfg.similarity.use_auto_tagging,
|
||||
auto_tagger=auto_tagger,
|
||||
)
|
||||
|
||||
vocab_cache = VocabularyCache(
|
||||
db=db_client,
|
||||
client=azure_client,
|
||||
)
|
||||
```
|
||||
|
||||
**Geändertes `infer_primary_role`-Tool:**
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
async def infer_primary_role(
|
||||
task_id: Optional[str] = None,
|
||||
task_text: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Infer the single closest role from either a DB task or free text."""
|
||||
if bool(task_id) == bool(task_text):
|
||||
return "Provide exactly one of task_id or task_text."
|
||||
|
||||
text = (task_text or "").strip()
|
||||
if task_id:
|
||||
_ensure_db()
|
||||
task = db_client.get_task_by_id(task_id)
|
||||
if task is None:
|
||||
return f"Task not found or not published: {task_id}"
|
||||
title = (task.title or "").strip()
|
||||
desc = (task.description or "").strip()
|
||||
text = (title + "\n\n" + desc).strip() if title else desc
|
||||
|
||||
if not text:
|
||||
return "Task text is empty."
|
||||
|
||||
best = await vocab_cache.infer_primary_role(task_text=text)
|
||||
if best is None:
|
||||
rows = [["", ""]]
|
||||
else:
|
||||
role, score = best
|
||||
rows = [[str(role), f"{float(score):.3f}"]]
|
||||
|
||||
return md_table(["Role", "Confidence"], rows)
|
||||
```
|
||||
|
||||
**Entferntes Tool `validate_task_requirements`:**
|
||||
Dieses Tool basiert vollständig auf Embedding-Inferenz (`infer_competences`, `ensure_task_embedding`). Es wird entfernt oder durch eine vereinfachte Version ersetzt, die nur DB-Felder anzeigt.
|
||||
|
||||
### 6. Config (vereinfacht)
|
||||
|
||||
**Datei:** `src/teamlandkarte_mcp/config.py`
|
||||
|
||||
**Entfernte Dataclasses:**
|
||||
- `EmbeddingCacheConfig`
|
||||
- `InferenceConfig`
|
||||
|
||||
**Geänderte Dataclasses:**
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class AzureOpenAIConfig:
|
||||
"""Azure OpenAI configuration (nur Chat Completion)."""
|
||||
endpoint: str
|
||||
api_version: str = "2024-02-15-preview"
|
||||
chat_deployment: str = ""
|
||||
llm_api_key: str = ""
|
||||
show_costs_in_output: bool = False
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SimilarityConfig:
|
||||
"""Similarity engine configuration."""
|
||||
use_auto_tagging: bool = False
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MatchingConfig:
|
||||
"""Matching weights and thresholds."""
|
||||
competence_weight: float = 0.8
|
||||
role_weight: float = 0.2
|
||||
require_confirmation: bool = True
|
||||
thresholds: MatchingThresholds = MatchingThresholds()
|
||||
fuzzy: FuzzyConfig = FuzzyConfig()
|
||||
# inference entfällt
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AppConfig:
|
||||
"""Top-level application configuration."""
|
||||
database: DatabaseConfig
|
||||
matching: MatchingConfig
|
||||
cache: CacheConfig
|
||||
azure_openai: AzureOpenAIConfig
|
||||
similarity: SimilarityConfig
|
||||
# embedding_cache entfällt
|
||||
```
|
||||
|
||||
**Entfernte Felder aus `SimilarityConfig`:**
|
||||
- `embedding_model`
|
||||
- `embedding_dimensions`
|
||||
- `strategy`
|
||||
- `use_bm25_search`
|
||||
|
||||
**Entfernte Felder aus `AzureOpenAIConfig`:**
|
||||
- `embedding_deployment`
|
||||
- `embedding_batch_size`
|
||||
|
||||
**Entfernte Validierungen in `load_config`:**
|
||||
- `AZURE_OPENAI_EMBEDDING_API_KEY` Prüfung
|
||||
- `embedding_dimensions == 3072` Prüfung
|
||||
- `strategy` Validierung
|
||||
- `inference` Parsing
|
||||
|
||||
**Neue Validierung:**
|
||||
- `AZURE_OPENAI_LLM_API_KEY` ist jetzt immer erforderlich (nicht nur bei auto_tagging)
|
||||
- `chat_deployment` ist jetzt erforderlich
|
||||
|
||||
### 7. Entfernte Dateien
|
||||
|
||||
| Datei | Grund |
|
||||
|-------|-------|
|
||||
| `src/teamlandkarte_mcp/cache/embedding_cache.py` | Keine Embeddings mehr |
|
||||
| Zugehörige Tests für EmbeddingCache | Keine Embeddings mehr |
|
||||
|
||||
### 8. config.toml Änderungen
|
||||
|
||||
**Entfernte Sektionen:**
|
||||
- `[embedding_cache]` komplett
|
||||
|
||||
**Entfernte Felder aus `[matching.similarity]`:**
|
||||
- `embedding_model`
|
||||
- `embedding_dimensions`
|
||||
- `strategy`
|
||||
- `use_bm25_search`
|
||||
|
||||
**Entfernte Felder aus `[azure_openai]`:**
|
||||
- `embedding_deployment`
|
||||
- `embedding_batch_size`
|
||||
|
||||
**Entfernte Felder aus `[matching]`:**
|
||||
- `[matching.inference]` komplett
|
||||
|
||||
**Neue Pflichtfelder in `[azure_openai]`:**
|
||||
- `chat_deployment` (bereits vorhanden als optionales Feld, wird Pflicht)
|
||||
|
||||
**Neue Umgebungsvariable (Pflicht):**
|
||||
- `AZURE_OPENAI_LLM_API_KEY` (ersetzt `AZURE_OPENAI_EMBEDDING_API_KEY`)
|
||||
|
||||
**Entfernte Umgebungsvariable:**
|
||||
- `AZURE_OPENAI_EMBEDDING_API_KEY`
|
||||
|
||||
## Datenmodelle
|
||||
|
||||
### LLM Role Similarity Request/Response
|
||||
|
||||
```json
|
||||
// System Prompt: _ROLE_SIMILARITY_SYSTEM_PROMPT
|
||||
// User Message:
|
||||
"Role A: Software Engineer\nRole B: Backend Developer"
|
||||
|
||||
// Expected Response:
|
||||
{"similarity": 0.75}
|
||||
```
|
||||
|
||||
### LLM Role Inference Request/Response
|
||||
|
||||
```json
|
||||
// System Prompt: _ROLE_INFERENCE_SYSTEM_PROMPT
|
||||
// User Message:
|
||||
"Task: Implementierung einer REST-API für Benutzerverwaltung\n\nAvailable roles: [\"Backend Developer\", \"Frontend Developer\", \"DevOps Engineer\", \"Data Engineer\"]"
|
||||
|
||||
// Expected Response:
|
||||
{"role": "Backend Developer", "confidence": 0.92}
|
||||
```
|
||||
|
||||
### In-Run Role Similarity Cache
|
||||
|
||||
```python
|
||||
# Symmetrischer Cache innerhalb eines Matching-Runs
|
||||
# Key: tuple(sorted((role_a_lower, role_b_lower)))
|
||||
# Value: float (0.0 - 1.0)
|
||||
_role_similarity_cache: dict[tuple[str, str], float] = {}
|
||||
```
|
||||
|
||||
Der Cache wird pro `SimilarityEngine`-Instanz gehalten und lebt für die Dauer des Server-Prozesses. Da Rollennamen stabil sind (aus der DB), ist kein TTL nötig.
|
||||
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: BM25 Zero-Score für fehlenden Token-Overlap
|
||||
|
||||
*For any* set of required competences and candidate competences where no candidate shares any token with a required competence, `compute_competence_similarity` shall return a score of 0.0 for that required competence.
|
||||
|
||||
**Validates: Requirements 1.1**
|
||||
|
||||
### Property 2: Matcher baut BM25-Index bedingungslos
|
||||
|
||||
*For any* non-empty list of filtered candidates with competences, the Matcher shall always build a global BM25 index and use it for competence scoring, producing results where candidates with no token overlap receive score 0.0.
|
||||
|
||||
**Validates: Requirements 2.6**
|
||||
|
||||
### Property 3: infer_primary_role Ausgabe-Validität
|
||||
|
||||
*For any* non-empty task text and non-empty role list from the database, if `infer_primary_role` returns a non-None result, the returned role name must be an element of the database role list and the confidence must be a float in [0.0, 1.0].
|
||||
|
||||
**Validates: Requirements 3.1, 3.4**
|
||||
|
||||
### Property 4: Graceful Degradation bei LLM-Fehler
|
||||
|
||||
*For any* LLM call that raises an exception, `compute_role_similarity` shall return 0.0 and `infer_primary_role` shall return None, without propagating the exception to the caller.
|
||||
|
||||
**Validates: Requirements 3.5, 5.6**
|
||||
|
||||
### Property 5: compute_role_similarity Wertebereich
|
||||
|
||||
*For any* two non-empty, non-"(unknown)" role names, `compute_role_similarity` shall return a float in the closed interval [0.0, 1.0]. For identical role names (case-insensitive), it shall return 1.0.
|
||||
|
||||
**Validates: Requirements 5.1**
|
||||
|
||||
### Property 6: Rollen-Similarity-Cache ist symmetrisch und idempotent
|
||||
|
||||
*For any* role pair (A, B), calling `compute_role_similarity(A, B)` and then `compute_role_similarity(B, A)` shall return the same score, and the second call shall not invoke the LLM (cache hit). Repeated calls with the same pair shall always return the same cached value.
|
||||
|
||||
**Validates: Requirements 5.8**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### LLM-Fehler in compute_role_similarity
|
||||
|
||||
- Bei jeder Exception (Timeout, API-Fehler, JSON-Parse-Fehler) wird `0.0` zurückgegeben
|
||||
- Fehler wird geloggt (LOGGER.warning)
|
||||
- Kein Eintrag im Cache für fehlgeschlagene Aufrufe (Retry bei nächstem Aufruf möglich)
|
||||
|
||||
### LLM-Fehler in infer_primary_role
|
||||
|
||||
- Bei jeder Exception wird `None` zurückgegeben
|
||||
- Fehler wird geloggt (LOGGER.warning)
|
||||
- Caller (MCP-Tool) zeigt leere Tabelle an
|
||||
|
||||
### Ungültige LLM-Antworten
|
||||
|
||||
- **Role Similarity**: Wenn `similarity` nicht im JSON oder nicht parsebar → 0.0
|
||||
- **Role Inference**: Wenn `role` nicht in der DB-Liste → None
|
||||
- **Role Inference**: Wenn `confidence` nicht parsebar → 0.0 (aber Rolle wird trotzdem zurückgegeben wenn valide)
|
||||
|
||||
### Leere/Ungültige Eingaben
|
||||
|
||||
- `compute_role_similarity` mit None/leer/"(unknown)" → 0.0 (kein LLM-Aufruf)
|
||||
- `infer_primary_role` mit leerem Text → None (kein LLM-Aufruf)
|
||||
- `infer_primary_role` mit leerer Rollenliste aus DB → None (kein LLM-Aufruf)
|
||||
- `compute_competence_similarity` mit leerer Required-Liste → leeres Dict
|
||||
- `compute_competence_similarity` mit leerer Candidate-Liste → alle Scores 0.0
|
||||
|
||||
### Konfigurationsfehler
|
||||
|
||||
- Fehlende `AZURE_OPENAI_LLM_API_KEY` → `ConfigError` beim Laden (fail-fast)
|
||||
- Fehlender `chat_deployment` → `ConfigError` beim Laden (fail-fast)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Property-Based Tests (fast-check / Hypothesis)
|
||||
|
||||
Bibliothek: **Hypothesis** (Python PBT-Standard)
|
||||
|
||||
Konfiguration: Mindestens 100 Iterationen pro Property-Test.
|
||||
|
||||
Jeder Property-Test wird mit einem Kommentar getaggt:
|
||||
```
|
||||
# Feature: remove-embedding-competence-similarity, Property {N}: {title}
|
||||
```
|
||||
|
||||
| Property | Test-Ansatz | Generator |
|
||||
|----------|-------------|-----------|
|
||||
| 1: BM25 Zero-Score | Generiere disjunkte Token-Sets für required/candidate, prüfe Score == 0.0 | `st.lists(st.text(alphabet=st.characters(whitelist_categories=('L',)), min_size=3))` |
|
||||
| 2: Matcher BM25 unconditional | Generiere Capacities + Requirements, prüfe dass Ergebnis BM25-Charakteristik hat (0.0 bei no-overlap) | Custom Capacity/Requirements strategies |
|
||||
| 3: infer_primary_role Validität | Generiere Task-Texte + Role-Listen, mocke LLM mit zufälliger valider Antwort, prüfe Output-Constraints | `st.text(min_size=1)`, `st.lists(st.text(min_size=1), min_size=1)` |
|
||||
| 4: Graceful Degradation | Generiere zufällige Exceptions, prüfe dass 0.0/None zurückkommt | `st.sampled_from([TimeoutError, RuntimeError, ValueError, json.JSONDecodeError])` |
|
||||
| 5: Role Similarity Wertebereich | Generiere Rollenpaare, mocke LLM mit zufälligem Score, prüfe [0.0, 1.0] und Identitäts-Case | `st.text(min_size=1, max_size=50)` |
|
||||
| 6: Cache Symmetrie | Generiere Rollenpaare, rufe in beiden Reihenfolgen auf, prüfe gleichen Score + nur 1 LLM-Call | `st.text(min_size=1, max_size=50)` |
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit Tests fokussieren auf:
|
||||
|
||||
- **Spezifische Beispiele**: Bekannte Rollenpaare (z.B. "Backend Developer" vs "Software Engineer") mit gemocktem LLM
|
||||
- **Edge Cases**: Leere Strings, None-Werte, "(unknown)", Whitespace-only
|
||||
- **Integration**: Config-Loading ohne Embedding-Felder, Server-Startup ohne Preload
|
||||
- **Regressions**: Sicherstellen dass BM25+RRF-Ergebnisse identisch zum bisherigen `use_bm25_search=True`-Pfad sind
|
||||
|
||||
### Integrationstests
|
||||
|
||||
- End-to-End Matching-Run mit gemocktem LLM-Client
|
||||
- Config-Loading aus minimaler TOML-Datei (ohne Embedding-Sektionen)
|
||||
- Server-Startup ohne `AZURE_OPENAI_EMBEDDING_API_KEY` (darf nicht mehr geprüft werden)
|
||||
|
||||
### Nicht getestet (bewusst ausgelassen)
|
||||
|
||||
- Prompt-Qualität (subjektiv, erfordert manuelles Review)
|
||||
- LLM-Antwort-Genauigkeit (abhängig vom Modell, nicht deterministisch)
|
||||
- Startup-Performance-Verbesserung (Benchmark, kein Unit-Test)
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# Anforderungsdokument
|
||||
|
||||
## Einleitung
|
||||
|
||||
Dieses Dokument beschreibt die Anforderungen für die Entfernung der Embedding-basierten Kompetenz-Similarity zugunsten von BM25 als einzigem Kompetenz-Matching-Verfahren, die Umstellung der Rollen-Inferenz und Rollen-Similarity auf einen LLM-Ansatz sowie die vollständige Entfernung der Embedding-Infrastruktur. Das System wird vollständig BM25 + LLM-basiert.
|
||||
|
||||
## Glossar
|
||||
|
||||
- **SimilarityEngine**: Modul in `matching/similarity.py`, das Ähnlichkeitsberechnungen für Kompetenzen und Rollen durchführt.
|
||||
- **VocabularyCache**: Modul in `matching/vocabulary.py`, das Vokabular-Embeddings vorhält und Inferenz-Funktionen bereitstellt.
|
||||
- **BM25**: Probabilistisches Ranking-Verfahren für Textähnlichkeit basierend auf Termfrequenz und inverser Dokumentfrequenz.
|
||||
- **RRF**: Reciprocal Rank Fusion – Verfahren zur Kombination mehrerer Rankings.
|
||||
- **AutoTagger**: LLM-basiertes Modul zur Erweiterung von Kompetenzlisten vor dem BM25-Scoring.
|
||||
- **AzureOpenAIClient**: Client-Wrapper für Azure OpenAI API-Aufrufe (Embeddings und Chat Completions).
|
||||
- **SimilarityConfig**: Konfigurationsklasse für die Similarity-Engine in `config.py`.
|
||||
- **infer_primary_role**: Funktion, die einer Aufgabe die passendste Rolle aus der Datenbank zuordnet.
|
||||
- **LLM**: Large Language Model – hier Azure OpenAI Chat Completion.
|
||||
- **Preload**: Eageres Vorladen von Embeddings beim Server-Start.
|
||||
- **EmbeddingCache**: Cache-Modul für gespeicherte Embedding-Vektoren.
|
||||
- **compute_role_similarity**: Funktion in der SimilarityEngine, die die semantische Ähnlichkeit zwischen einer geforderten Rolle und einer Kandidaten-Rolle berechnet.
|
||||
|
||||
## Anforderungen
|
||||
|
||||
### Anforderung 1: Entfernung der Embedding-basierten Kompetenz-Similarity
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass BM25+RRF das einzige Verfahren für Kompetenz-Matching ist, damit die Codebasis vereinfacht wird und keine Embedding-Kosten für Kompetenz-Vergleiche anfallen.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. THE SimilarityEngine SHALL use BM25+RRF as the sole method for computing competence similarity scores.
|
||||
2. WHEN compute_competence_similarity is called, THE SimilarityEngine SHALL execute the BM25+RRF path without checking a strategy flag or use_bm25_search parameter.
|
||||
3. THE SimilarityEngine SHALL no longer contain the `_per_skill_similarity` method for embedding-based per-skill competence matching.
|
||||
4. THE SimilarityEngine SHALL no longer contain the `_aggregate_similarity` method for embedding-based aggregate competence matching.
|
||||
5. THE SimilarityEngine SHALL no longer accept a `strategy` parameter in its constructor for competence similarity strategy selection.
|
||||
6. THE SimilarityEngine SHALL no longer accept a `use_bm25_search` parameter in its constructor.
|
||||
7. THE SimilarityEngine SHALL remove the `prefetch_embeddings` and `_embed` methods, since role similarity also switches to LLM and no embedding use cases remain.
|
||||
|
||||
### Anforderung 2: Entfernung des Konfigurationsparameters use_bm25_search
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass der Parameter `use_bm25_search` aus der Konfiguration entfernt wird, da BM25 nun immer aktiv ist und der Parameter redundant geworden ist.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. THE SimilarityConfig SHALL no longer contain the field `use_bm25_search`.
|
||||
2. THE SimilarityConfig SHALL no longer contain the field `strategy` (da nur noch BM25 verwendet wird).
|
||||
3. WHEN the configuration is loaded, THE Config-Loader SHALL not read or validate `use_bm25_search` from the TOML file.
|
||||
4. WHEN the configuration is loaded, THE Config-Loader SHALL not read or validate `matching.similarity.strategy` from the TOML file.
|
||||
5. THE SimilarityEngine SHALL no longer expose a `use_bm25_search` property.
|
||||
6. THE Matcher SHALL build the global BM25 index unconditionally for all filtered candidates without checking a `use_bm25_search` flag.
|
||||
|
||||
### Anforderung 3: LLM-basierte Rollen-Inferenz
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass `infer_primary_role` einen LLM-Ansatz (Chat Completion) verwendet anstelle von Embedding-Cosine-Similarity, damit die Rollenzuordnung kontextbezogener und genauer erfolgt.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. WHEN infer_primary_role is called, THE VocabularyCache SHALL use an LLM chat completion to determine the best matching role for a given task text.
|
||||
2. WHEN infer_primary_role is called, THE VocabularyCache SHALL provide the list of all available role names from the database as context to the LLM.
|
||||
3. WHEN infer_primary_role is called, THE VocabularyCache SHALL provide the task text (title and/or description) as input to the LLM.
|
||||
4. THE VocabularyCache SHALL return the role name and a confidence score from the LLM response.
|
||||
5. IF the LLM call fails, THEN THE VocabularyCache SHALL return None rather than raising an unhandled exception.
|
||||
6. THE VocabularyCache SHALL no longer require a task embedding as input parameter for infer_primary_role.
|
||||
7. THE VocabularyCache SHALL accept the task text directly as input parameter for infer_primary_role.
|
||||
8. WHEN infer_primary_role is called, THE VocabularyCache SHALL instruct the LLM to select exactly one role from the provided list and return a structured JSON response.
|
||||
9. THE infer_primary_role tool in mcp_server.py SHALL call the new LLM-based infer_primary_role without first generating a task embedding.
|
||||
|
||||
### Anforderung 4: Entfernung des Embedding-Preloads offener Tasks
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass Embeddings offener Tasks nicht mehr beim Server-Start vorgeladen werden und auch nicht mehr on-demand erzeugt werden, da keine Embedding-basierte Verarbeitung mehr stattfindet.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. THE MCP-Server SHALL no longer preload embeddings for open tasks during startup.
|
||||
2. THE `_startup_preload_embeddings` function SHALL be removed entirely.
|
||||
3. THE VocabularyCache SHALL no longer provide an `ensure_task_embedding` method, since task embeddings are not needed in the BM25 + LLM architecture.
|
||||
4. THE MCP-Server SHALL no longer preload role or competence vocabulary embeddings at startup, since all similarity computations now use BM25 or LLM.
|
||||
5. THE startup time of the MCP-Server SHALL be reduced by eliminating all embedding preload loops.
|
||||
|
||||
### Anforderung 5: Umstellung der Rollen-Similarity im Matcher auf LLM
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass die Rollen-Similarity im Matcher (`compute_role_similarity`) ebenfalls einen LLM-Ansatz (Chat Completion) nutzt anstelle von Embedding-Cosine-Similarity, damit das gesamte System ohne Embeddings auskommt und konsistent BM25 + LLM-basiert ist.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. WHEN compute_role_similarity is called, THE SimilarityEngine SHALL use an LLM chat completion to determine the semantic similarity between the required role and the candidate role.
|
||||
2. WHEN compute_role_similarity is called, THE SimilarityEngine SHALL provide both role names to the LLM and request a numeric similarity score between 0.0 and 1.0.
|
||||
3. THE SimilarityEngine SHALL instruct the LLM to return a structured JSON response containing the similarity score.
|
||||
4. THE SimilarityEngine SHALL no longer use embedding cosine similarity for role comparisons.
|
||||
5. THE SimilarityEngine SHALL no longer call `_embed` or `prefetch_embeddings` for role similarity computation.
|
||||
6. IF the LLM call fails, THEN THE SimilarityEngine SHALL return a default similarity score of 0.0 rather than raising an unhandled exception.
|
||||
7. THE VocabularyCache SHALL no longer preload role vocabulary embeddings at startup, since they are not needed for LLM-based role similarity.
|
||||
8. THE SimilarityEngine SHALL cache LLM-based role similarity results for identical role pairs within a matching run to avoid redundant API calls.
|
||||
|
||||
### Anforderung 6: Vollständige Entfernung der Embedding-Infrastruktur
|
||||
|
||||
**User Story:** Als Entwickler möchte ich, dass die gesamte Embedding-Infrastruktur entfernt wird, da weder Kompetenz-Matching noch Rollen-Similarity noch Rollen-Inferenz Embeddings benötigen und das System vollständig BM25 + LLM-basiert ist.
|
||||
|
||||
#### Akzeptanzkriterien
|
||||
|
||||
1. THE SimilarityEngine SHALL remove the `prefetch_embeddings` method entirely.
|
||||
2. THE SimilarityEngine SHALL remove the `_embed` method entirely.
|
||||
3. THE VocabularyCache SHALL remove `_preload_competence_vocab` entirely.
|
||||
4. THE VocabularyCache SHALL remove `_preload_role_vocab` (or equivalent role embedding preload logic) entirely.
|
||||
5. THE VocabularyCache SHALL remove `infer_competences` if embedding-based competence inference is no longer used.
|
||||
6. THE EmbeddingCache module SHALL be removed entirely, since no code path requires cached embeddings.
|
||||
7. THE AzureOpenAIClient SHALL remove the embedding API method (e.g. `get_embeddings` or equivalent batch embedding call), retaining only chat completion methods.
|
||||
8. THE config.py SHALL remove embedding-related configuration fields (e.g. `embedding_model`, `embedding_dimensions`, embedding batch size settings).
|
||||
9. THE config.py SHALL remove the `InferenceConfig` dataclass if `max_competences` and `min_similarity` are no longer used.
|
||||
10. THE SimilarityConfig SHALL remove any fields related to embedding thresholds or embedding model selection.
|
||||
11. THE config.toml SHALL remove embedding-related configuration entries.
|
||||
12. THE MCP-Server SHALL remove the `_startup_preload_embeddings` function entirely if no embedding preloads remain.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Implementation Plan: Entfernung Embedding-basierter Similarity – Umstellung auf BM25 + LLM
|
||||
|
||||
## Overview
|
||||
|
||||
Incremental removal of all embedding infrastructure and replacement with BM25 + LLM. Tasks are ordered to avoid breaking the system mid-way: first add new LLM-based methods, then remove embedding code paths, then clean up config and delete dead modules.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. Simplify AzureOpenAIClient to remove embedding methods
|
||||
- [x] 1.1 Remove `embeddings` and `get_embeddings_batch` methods from `AzureOpenAIClient`
|
||||
- Remove the `self._emb` AsyncAzureOpenAI client instance
|
||||
- Remove constructor parameters: `embedding_api_key`, `embedding_deployment`, `embedding_batch_size`
|
||||
- Keep only `chat_completion` method and its supporting `self._chat` client
|
||||
- Make `self._chat` the primary client (no longer optional/conditional)
|
||||
- Remove the guard clause in `chat_completion` that checks for missing config
|
||||
- _Requirements: 6.7_
|
||||
|
||||
- [x] 1.2 Update `AzureOpenAIClient` constructor signature
|
||||
- New required params: `endpoint`, `api_version`, `chat_deployment`, `llm_api_key`
|
||||
- Keep optional: `timeout_s`, `max_retries`, `cost_tracker`
|
||||
- Remove cost tracker calls for embedding requests (`log_embedding_request`, `log_embedding_batch_request`)
|
||||
- _Requirements: 6.7_
|
||||
|
||||
- [-] 2. Refactor SimilarityEngine to remove embedding methods and add LLM role similarity
|
||||
- [x] 2.1 Remove embedding-related methods and properties from `SimilarityEngine`
|
||||
- Remove methods: `prefetch_embeddings`, `_embed`, `get_embedding_for_cache_key`, `get_embeddings_for_cache_keys`, `_aggregate_similarity`, `_per_skill_similarity`
|
||||
- Remove properties: `embedding_model`, `embedding_dimensions`, `use_bm25_search`
|
||||
- Remove constructor parameters: `cache`, `embedding_model`, `embedding_dimensions`, `strategy`, `use_bm25_search`
|
||||
- Remove module-level helpers: `_cache_key`, `cosine_similarity`
|
||||
- Update constructor to accept only: `client`, `cost_tracker`, `use_auto_tagging`, `auto_tagger`
|
||||
- Add `_role_similarity_cache: dict[tuple[str, str], float]` to constructor
|
||||
- _Requirements: 1.3, 1.4, 1.5, 1.6, 1.7, 6.1, 6.2_
|
||||
|
||||
- [x] 2.2 Simplify `compute_competence_similarity` to always use BM25+RRF
|
||||
- Remove any strategy/conditional branching
|
||||
- Always call `_bm25_rrf_similarity` directly (with optional auto-tagging expansion)
|
||||
- _Requirements: 1.1, 1.2_
|
||||
|
||||
- [x] 2.3 Implement LLM-based `compute_role_similarity`
|
||||
- Replace embedding cosine similarity with LLM chat completion
|
||||
- Add `_ROLE_SIMILARITY_SYSTEM_PROMPT` constant
|
||||
- Implement symmetric cache key: `tuple(sorted((role_a_lower, role_b_lower)))`
|
||||
- Return 0.0 for bad/empty/None/"(unknown)" roles without LLM call
|
||||
- Return 1.0 for identical roles (case-insensitive) without LLM call
|
||||
- Parse JSON response, clamp score to [0.0, 1.0]
|
||||
- On any exception: return 0.0, do not cache failed results
|
||||
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.8_
|
||||
|
||||
- [x] 2.4 Add `clear_role_cache` method
|
||||
- Clears `_role_similarity_cache` dict
|
||||
- _Requirements: 5.8_
|
||||
|
||||
- [x] 2.5 Write property test for BM25 zero-score on disjoint tokens
|
||||
- **Property 1: BM25 Zero-Score für fehlenden Token-Overlap**
|
||||
- **Validates: Requirements 1.1**
|
||||
|
||||
- [x] 2.6 Write property test for role similarity value range and identity
|
||||
- **Property 5: compute_role_similarity Wertebereich**
|
||||
- **Validates: Requirements 5.1**
|
||||
|
||||
- [x] 2.7 Write property test for role similarity cache symmetry
|
||||
- **Property 6: Rollen-Similarity-Cache ist symmetrisch und idempotent**
|
||||
- **Validates: Requirements 5.8**
|
||||
|
||||
- [-] 3. Refactor VocabularyCache to use LLM-based role inference
|
||||
- [x] 3.1 Rewrite `VocabularyCache` class
|
||||
- Remove all embedding-related methods: `preload`, `_preload_role_vocab`, `_preload_competence_vocab`, `ensure_task_embedding`, `infer_competences`
|
||||
- Remove old `infer_primary_role` (embedding-based)
|
||||
- Remove properties: `roles`, `competences`
|
||||
- Remove constructor dependencies on `SimilarityEngine` and `EmbeddingCache`
|
||||
- New constructor accepts only: `db: DBClient`, `client: AzureOpenAIClient`
|
||||
- Remove module-level helpers: `role_vocab_cache_key`, `competence_vocab_cache_key`, `task_id_cache_key`, `VocabItem`
|
||||
- _Requirements: 4.3, 6.3, 6.4, 6.5_
|
||||
|
||||
- [x] 3.2 Implement new LLM-based `infer_primary_role`
|
||||
- Accept `task_text: str` keyword argument (no more `task_embedding`)
|
||||
- Fetch all role names from DB via `self._db.get_all_role_names()`
|
||||
- Build LLM prompt with task text and available roles list
|
||||
- Parse JSON response: `{"role": "...", "confidence": 0.0-1.0}`
|
||||
- Validate returned role exists in DB role list
|
||||
- Return `None` on empty text, empty role list, or any exception
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8_
|
||||
|
||||
- [x] 3.3 Write property test for infer_primary_role output validity
|
||||
- **Property 3: infer_primary_role Ausgabe-Validität**
|
||||
- **Validates: Requirements 3.1, 3.4**
|
||||
|
||||
- [x] 3.4 Write property test for graceful degradation on LLM failure
|
||||
- **Property 4: Graceful Degradation bei LLM-Fehler**
|
||||
- **Validates: Requirements 3.5, 5.6**
|
||||
|
||||
- [x] 4. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [-] 5. Update Matcher to unconditionally build BM25 index
|
||||
- [x] 5.1 Remove `use_bm25_search` conditional in `Matcher.match`
|
||||
- Remove `if self._sim.use_bm25_search` guard around BM25 index construction
|
||||
- Always build global BM25 index when `filtered` is non-empty
|
||||
- _Requirements: 2.6_
|
||||
|
||||
- [x] 5.2 Write property test for unconditional BM25 index building
|
||||
- **Property 2: Matcher baut BM25-Index bedingungslos**
|
||||
- **Validates: Requirements 2.6**
|
||||
|
||||
- [x] 6. Update MCP Server to remove embedding preload and wire new components
|
||||
- [x] 6.1 Remove embedding preload infrastructure from `mcp_server.py`
|
||||
- Remove `_startup_preload_embeddings` function
|
||||
- Remove `_deferred_preload` function and `_preload_started` flag
|
||||
- Remove `_ensure_preloaded` function
|
||||
- Remove all `await _ensure_preloaded()` calls in tool handlers
|
||||
- Remove `EmbeddingCache` import and instantiation
|
||||
- _Requirements: 4.1, 4.2, 4.4, 4.5, 6.12_
|
||||
|
||||
- [x] 6.2 Update component wiring in `build_server`
|
||||
- Construct `AzureOpenAIClient` with new simplified signature (no embedding params)
|
||||
- Construct `SimilarityEngine` with new signature (client, cost_tracker, use_auto_tagging, auto_tagger)
|
||||
- Construct `VocabularyCache` with new signature (db, client)
|
||||
- _Requirements: 6.7_
|
||||
|
||||
- [x] 6.3 Update `infer_primary_role` tool handler
|
||||
- Remove embedding generation step
|
||||
- Call `vocab_cache.infer_primary_role(task_text=text)` directly with task text
|
||||
- _Requirements: 3.9_
|
||||
|
||||
- [x] 6.4 Remove or simplify `validate_task_requirements` tool if it depends on embeddings
|
||||
- Remove embedding-based `infer_competences` usage
|
||||
- Either remove the tool entirely or replace with a DB-field-only version
|
||||
- _Requirements: 6.5_
|
||||
|
||||
- [x] 7. Simplify config.py and config.toml
|
||||
- [x] 7.1 Remove embedding-related dataclasses and fields from `config.py`
|
||||
- Delete `EmbeddingCacheConfig` dataclass
|
||||
- Delete `InferenceConfig` dataclass
|
||||
- Remove from `AzureOpenAIConfig`: `embedding_deployment`, `embedding_batch_size`
|
||||
- Remove from `SimilarityConfig`: `embedding_model`, `embedding_dimensions`, `strategy`, `use_bm25_search`
|
||||
- Remove `inference` field from `MatchingConfig`
|
||||
- Remove `embedding_cache` field from `AppConfig`
|
||||
- _Requirements: 2.1, 2.2, 6.8, 6.9, 6.10_
|
||||
|
||||
- [x] 7.2 Update `load_config` / `_parse_azure_openai` validation
|
||||
- Remove `AZURE_OPENAI_EMBEDDING_API_KEY` environment variable check
|
||||
- Make `AZURE_OPENAI_LLM_API_KEY` always required
|
||||
- Make `chat_deployment` required
|
||||
- Remove `embedding_dimensions == 3072` validation
|
||||
- Remove `strategy` validation
|
||||
- Remove `[matching.inference]` parsing
|
||||
- Remove `[embedding_cache]` parsing
|
||||
- _Requirements: 2.3, 2.4_
|
||||
|
||||
- [x] 7.3 Update `config.toml` and `config.toml.example`
|
||||
- Remove `[embedding_cache]` section
|
||||
- Remove from `[matching.similarity]`: `embedding_model`, `embedding_dimensions`, `strategy`, `use_bm25_search`
|
||||
- Remove from `[azure_openai]`: `embedding_deployment`, `embedding_batch_size`
|
||||
- Remove `[matching.inference]` section
|
||||
- _Requirements: 6.11_
|
||||
|
||||
- [x] 8. Delete EmbeddingCache module and related tests
|
||||
- [x] 8.1 Delete `src/teamlandkarte_mcp/cache/embedding_cache.py`
|
||||
- _Requirements: 6.6_
|
||||
|
||||
- [x] 8.2 Remove or update tests that reference embedding functionality
|
||||
- Delete tests for `EmbeddingCache`
|
||||
- Update tests for `SimilarityEngine` to use new constructor
|
||||
- Update tests for `VocabularyCache` to use new constructor
|
||||
- Update tests for `AzureOpenAIClient` to use new constructor
|
||||
- Remove any test fixtures that create embedding mocks
|
||||
- _Requirements: 6.6_
|
||||
|
||||
- [x] 9. Final checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
||||
- Each task references specific requirements for traceability
|
||||
- Checkpoints ensure incremental validation
|
||||
- Property tests validate universal correctness properties from the design document
|
||||
- Order ensures no broken intermediate states: new LLM methods added before old embedding paths removed
|
||||
Reference in New Issue
Block a user