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.
28 KiB
Change Proposal: Replace Heuristics with Azure OpenAI Embeddings and LLM
- Change ID:
replace-heuristics-with-azure-openai - Status: Implemented
- Target:
teamlandkarte-mcp - Author: Thomas Handke
- Date: 2026-02-13
Summary
Replace the current FastMCP sampling (unavailable) and deterministic heuristic fallbacks with direct Azure OpenAI API integration for:
- Competence and role similarity matching via embeddings (
text-embedding-3-large, 3072 dimensions) - Requirements extraction and validation via LLM (
gpt-4.1)
Additionally:
- Rename
database.toml→config.toml(and template accordingly) - Implement persistent embedding cache using a local SQLite database
- Support two similarity scoring strategies (per-skill vs. aggregate) via configuration
- Remove all FastMCP sampling code and heuristic fallbacks completely
Motivation
Current State Problems
-
FastMCP sampling is unavailable: The
mcppackage does not expose aFastMCP.samplingAPI, forcing the system to rely entirely on weak deterministic heuristics. -
Heuristic limitations:
- Role inference: simple keyword matching (
"cloud" → "Cloud Engineer") - Competence extraction: small hardcoded keyword list (~15 terms)
- Competence similarity: exact normalized match (1.0) or substring (0.7), otherwise 0.0
- No true semantic understanding
- Role inference: simple keyword matching (
-
Poor matching quality:
- Synonyms not recognized (e.g., "React.js" vs "ReactJS")
- Related skills missed (e.g., "FastAPI" vs "REST API Development")
- Role extraction frequently returns
"(unknown)"
-
Maintenance burden: Heuristic keyword lists require manual updates
Proposed Benefits
- Semantic matching: True similarity via embeddings (e.g., "Kubernetes" ↔ "K8s", "Python" ↔ "Python 3")
- Robust extraction: LLM-backed role and competence extraction from free text
- Better user experience: More accurate Top/Good/Partial/Low categorization
- Reduced maintenance: No manual keyword list updates
- Production-ready: Direct API integration, no dependency on MCP sampling feature
Goals
- Replace
TaskAnalyzer.semantic_competence_similaritywith embedding-based similarity - Replace
TaskAnalyzer.extract_ranked_roleswith Azure OpenAI LLM - Replace
TaskAnalyzer._extract_requirements_from_descriptionwith Azure OpenAI LLM - Replace
TaskAnalyzer.apply_requirement_updatewith Azure OpenAI LLM - Implement persistent embedding cache (SQLite)
- Support two similarity strategies via config
- Remove all FastMCP sampling and heuristic code
- Rename
database.toml→config.toml
Non-Goals
- Changing the overall matching workflow (confirmation gate, guided capture, etc.)
- Changing the scoring weights (competence 0.8, role 0.2)
- Changing the categorical thresholds (Top/Good/Partial/Low)
- Supporting multiple embedding models
- Supporting other LLM providers (only Azure OpenAI)
Proposed Changes
1. Configuration Changes
1.1 Rename configuration file
database.toml→config.tomldatabase.toml.example→config.toml.example- Update all references in code, docs, README
1.2 New configuration sections in config.toml
[azure_openai]
endpoint = "https://aiservice-ca00361106.cognitiveservices.azure.com/"
api_version_embeddings = "2024-02-01"
api_version_llm = "2024-12-01-preview"
[azure_openai.embeddings]
model = "text-embedding-3-large"
# Azure deployment name is equal to model name
deployment = "text-embedding-3-large"
[azure_openai.llm]
model = "gpt-4.1"
# Azure deployment name is equal to model name
deployment = "gpt-4.1"
temperature = 0.2
max_tokens = 2000
[embedding_cache]
enabled = true
db_path = "embeddings_cache.db"
ttl_days = 30
[matching.similarity]
# Strategy: "per_skill" or "aggregate"
# - per_skill: Match each required skill to best candidate skill (current behavior)
# - aggregate: Compare average embedding of all required vs all candidate skills
strategy = "per_skill"
# Chat model/deployment name (Azure OpenAI)
chat_model = "gpt-4.1"
1.3 Environment variables (.env)
AZURE_OPENAI_EMBEDDING_API_KEY="..."
AZURE_OPENAI_LLM_API_KEY="..."
2. Embedding Cache Implementation
Create src/teamlandkarte_mcp/cache/embedding_cache.py:
class EmbeddingCache:
"""Persistent embedding cache using SQLite.
Schema:
embeddings(
id INTEGER PRIMARY KEY,
text TEXT UNIQUE NOT NULL,
model TEXT NOT NULL,
embedding BLOB NOT NULL, -- UTF-8 bytes of JSON-serialized list[float]
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Notes:
- Store embeddings as UTF-8 encoded JSON in a BLOB column for portability.
- Serialize with: json.dumps(embedding).encode("utf-8")
- Deserialize with: json.loads(blob.decode("utf-8"))
"""
def __init__(self, db_path: str, ttl_days: int = 30):
"""Initialize cache, create DB if not exists."""
def get(self, text: str, model: str) -> Optional[list[float]]:
"""Retrieve cached embedding, return None if missing/expired."""
def put(self, text: str, model: str, embedding: list[float]) -> None:
"""Store embedding in cache."""
def cleanup_expired(self) -> int:
"""Remove embeddings older than TTL, return count deleted."""
3. Azure OpenAI Client Layer
Create src/teamlandkarte_mcp/azure/openai_client.py:
class AzureOpenAIClient:
"""Wrapper for Azure OpenAI API calls using AsyncAzureOpenAI client."""
def __init__(self, config: AzureOpenAIConfig, embedding_cache: EmbeddingCache):
"""Initialize AsyncAzureOpenAI clients for embeddings and LLM.
Uses openai.AsyncAzureOpenAI for all async API calls.
"""
async def get_embedding(self, text: str) -> list[float]:
"""Get embedding for text, using cache if available.
Raises:
AzureAPIError: If API call fails (no fallback).
"""
async def get_embeddings_batch(self, texts: list[str]) -> list[list[float]]:
"""Get embeddings for multiple texts (not batched per user requirement)."""
async def chat_completion(
self,
system: str,
user: str,
response_format: dict = None
) -> str:
"""LLM completion with structured JSON response.
Raises:
AzureAPIError: If API call fails (no fallback).
"""
4. Similarity Computation
Create src/teamlandkarte_mcp/matching/similarity.py:
def cosine_similarity(vec_a: list[float], vec_b: list[float]) -> float:
"""Compute cosine similarity between two vectors."""
class SimilarityEngine:
"""Compute semantic similarity using embeddings."""
def __init__(
self,
client: AzureOpenAIClient,
strategy: str = "per_skill"
):
"""Initialize with Azure client and strategy."""
async def compute_competence_similarity(
self,
required: list[str],
candidate: list[str],
) -> dict[str, dict[str, Any]]:
"""Compute similarity using configured strategy.
Returns same format as current semantic_competence_similarity:
{required_comp: {best_match: str|null, score: float, rationale: str}}
Behavioral guarantees:
- `rationale` is always included (even when best_match is null).
- If `required` is empty, return {}.
- If `candidate` is empty, return an entry per required competence with:
best_match=null, score=0.0, and a short rationale.
"""
async def _per_skill_similarity(
self, required: list[str], candidate: list[str]
) -> dict[str, dict[str, Any]]:
"""For each required skill, find best matching candidate skill."""
async def _aggregate_similarity(
self, required: list[str], candidate: list[str]
) -> dict[str, dict[str, Any]]:
"""Compare average embeddings of required vs candidate skills."""
async def compute_role_similarity(
self, required_role: str, candidate_role: str
) -> float:
"""Compute role similarity (0.0 to 1.0).
Behavioral guarantees:
- If either role is empty/None, return 0.0.
- If either role is "(unknown)" (case-insensitive, trimmed), return 0.0.
- Otherwise compute cosine similarity of the role embeddings.
Note:
- Role similarity returns only a float score. Rationales are provided by
`compute_competence_similarity()` results.
"""
5. TaskAnalyzer Refactoring
Remove:
_sample_json()method_heuristic_competences_from_text()static method- All heuristic fallback code in:
extract_ranked_roles()_extract_requirements_from_description()apply_requirement_update()semantic_competence_similarity()
Replace with:
class TaskAnalyzer:
"""Task text analyzer using Azure OpenAI."""
def __init__(self, azure_client: AzureOpenAIClient):
self._client = azure_client
async def extract_ranked_roles(
self, description: str, limit: int = 5
) -> list[RankedRole]:
"""Extract ranked roles using Azure OpenAI LLM.
Raises:
AzureAPIError: If API call fails.
"""
async def _extract_requirements_from_description(
self, description: str
) -> ExtractedRequirements:
"""Extract requirements using Azure OpenAI LLM.
Raises:
AzureAPIError: If API call fails.
"""
async def apply_requirement_update(
self, current: Requirements, change_description: str
) -> Requirements:
"""Update requirements using Azure OpenAI LLM.
Raises:
AzureAPIError: If API call fails.
"""
Additional behavior change:
- The current
TaskAnalyzerraisesAnalysisErroronly when MCP sampling is present but fails/returns invalid JSON, and otherwise silently falls back to heuristics. - After this change, heuristics are removed. Any Azure OpenAI failure (HTTP error, timeout after retries, invalid JSON/schema) will result in an analysis exception (e.g.
AzureAPIErrorand/or a narrowerAnalysisError) and should be surfaced to callers.
Testing impact:
- Existing tests that currently assert heuristic fallback behavior in
TaskAnalyzermust be updated. - Replace “heuristic fallback expected” assertions with either:
- mocked Azure responses (unit tests), or
- explicit error expectations when Azure is unavailable.
6. Matcher & Scorer Integration
Update src/teamlandkarte_mcp/matching/matcher.py:
Key Changes:
- Accept
SimilarityEnginein constructor - Replace
TaskAnalyzer.semantic_competence_similarity()calls withSimilarityEngine.compute_competence_similarity() - Replace the existing
_role_similarity()function withSimilarityEngine.compute_role_similarity() - Maintain current scoring weights (competence 0.8, role 0.2)
Details:
- The
Matchercurrently usesTaskAnalyzer.semantic_competence_similarity()for competence matching (per-skill, best-match strategy) - It also has a standalone
_role_similarity()function for role matching (currently uses simple string comparison) - Both will be replaced by
SimilarityEnginemethods, which provide semantic similarity via embeddings - The
SimilarityEngine.compute_competence_similarity()returns the same dict structure as currentsemantic_competence_similarity(), ensuring drop-in compatibility - The
SimilarityEngine.compute_role_similarity()returns a float (0.0 to 1.0), matching current_role_similarity()signature
class Matcher:
"""Capacity matcher with semantic similarity."""
def __init__(
self,
analyzer: TaskAnalyzer,
similarity_engine: SimilarityEngine,
config: MatchingConfig
):
"""Initialize matcher with analyzer, similarity engine, and config."""
self._analyzer = analyzer
self._similarity = similarity_engine
self._config = config
async def _compute_match_score(
self,
required: Requirements,
capacity: Capacity
) -> MatchScore:
"""Compute match score using SimilarityEngine.
Competence matching:
comp_sim = await self._similarity.compute_competence_similarity(
required.competences, capacity.competences
)
Role matching:
role_score = await self._similarity.compute_role_similarity(
required.role, capacity.role
)
Final score: (0.8 * avg_comp_score) + (0.2 * role_score)
"""
Owning module note:
- The end-to-end match scoring aggregation currently lives in
src/teamlandkarte_mcp/matching/matcher.py(with supporting score shaping asserted bytests/test_scorer.py).
7. Server Initialization Changes
Update src/teamlandkarte_mcp/mcp_server.py:
# Initialize Azure OpenAI components
embedding_cache = EmbeddingCache(
db_path=cfg.embedding_cache.db_path,
ttl_days=cfg.embedding_cache.ttl_days,
) if cfg.embedding_cache.enabled else None
azure_client = AzureOpenAIClient(cfg.azure_openai, embedding_cache)
similarity_engine = SimilarityEngine(
azure_client,
strategy=cfg.matching.similarity.strategy,
)
# Initialize analyzer with Azure client
analyzer = TaskAnalyzer(azure_client)
# Initialize matcher with similarity engine
matcher = Matcher(analyzer, similarity_engine, cfg.matching)
8. Cost Estimation & Monitoring
Add cost tracking module src/teamlandkarte_mcp/azure/cost_tracker.py:
class CostTracker:
"""Track and estimate Azure OpenAI API costs."""
# Pricing (as of 2026-02, may change)
EMBEDDING_COST_PER_1K_TOKENS = 0.00013 # text-embedding-3-large
LLM_INPUT_COST_PER_1K_TOKENS = 0.03 # gpt-4.1
LLM_OUTPUT_COST_PER_1K_TOKENS = 0.06 # gpt-4.1
def log_embedding_request(self, text: str, cached: bool):
"""Log embedding request for cost tracking."""
def log_llm_request(self, input_tokens: int, output_tokens: int):
"""Log LLM request for cost tracking."""
def get_session_costs(self) -> dict:
"""Return estimated costs for current session."""
Add to README.md:
## Azure OpenAI Cost Estimation
Typical matching workflow costs (estimated):
| Operation | API Calls | Estimated Cost |
|-----------|-----------|----------------|
| Single capacity match (100 candidates) | ~200 embeddings (mostly cached), 0 LLM | $0.001 - $0.01 |
| Task requirements extraction | 0 embeddings, 1 LLM call (~1000 tokens) | $0.03 - $0.06 |
| Role inference | 0 embeddings, 1 LLM call (~500 tokens) | $0.015 - $0.03 |
With embedding caching enabled (default), repeated searches are significantly cheaper.
**Monthly cost estimate** (100 searches/day, 20 days):
- Without cache: ~$60-120/month
- With cache (90% hit rate): ~$10-20/month
Testing Strategy
Unit Tests (Mocked Azure)
All unit tests should mock Azure OpenAI API responses to avoid API costs and ensure deterministic behavior:
Embedding Mocks:
- Use fixed-dimension vectors matching
text-embedding-3-large(3072 dimensions) - Create realistic patterns:
- Similar terms:
[0.9, 0.8, 0.1, ..., 0.0]and[0.85, 0.82, 0.15, ..., 0.0]→ high cosine similarity (~0.95) - Different terms:
[0.9, 0.1, 0.0, ..., 0.0]and[0.1, 0.9, 0.0, ..., 0.0]→ low cosine similarity (~0.1)
- Similar terms:
- Use
unittest.mock.AsyncMockorpytest-mockforAsyncAzureOpenAIclient
LLM Mocks:
- Mock
chat.completions.create()to return structured JSON responses - Example:
{"roles": [{"role": "Backend Developer", "confidence": 0.9, "rationale": "..."}], ...}
Example Mock Pattern:
@pytest.mark.asyncio
async def test_compute_competence_similarity():
mock_client = AsyncMock()
mock_client.get_embedding.side_effect = [
[0.9, 0.8, 0.1] + [0.0] * 3069, # "Python"
[0.85, 0.82, 0.15] + [0.0] * 3069, # "Python 3"
]
engine = SimilarityEngine(mock_client, strategy="per_skill")
result = await engine.compute_competence_similarity(["Python"], ["Python 3"])
assert result["Python"]["score"] > 0.9
Integration Tests (Real Azure)
- Mark all integration tests with
@pytest.mark.integration - Configure
pytest.ini:[pytest] markers = integration: marks tests that call real Azure OpenAI API (deselect with '-m "not integration"') - Run integration tests manually before deployment:
pytest -m integration - Run fast tests in CI:
pytest -m "not integration" - Integration tests should use small, cheap prompts to minimize costs
Test Coverage Goals
- Unit tests: ≥90% coverage for all new code (cache, client, similarity, analyzer)
- Integration tests: Cover happy path + common error scenarios (API timeout, invalid credentials)
- Manual tests: Full end-to-end workflow in Cherry Studio (search → filter → confirm)
Implementation Task List
Phase 1: Configuration & Infrastructure (3-4 hours)
- 1.1 Rename
database.toml→config.tomland template - 1.2 Update all file references in code
- 1.3 Add
[azure_openai]section to config model (src/config.py) - 1.4 Add
[embedding_cache]section to config model - 1.5 Add
[matching.similarity]section to config model - 1.6 Update
config.toml.examplewith new sections - 1.7 Load
AZURE_OPENAI_EMBEDDING_API_KEYfrom.env - 1.8 Load
AZURE_OPENAI_LLM_API_KEYfrom.env - 1.9 Add
openaipackage topyproject.tomldependencies
Phase 2: Embedding Cache (2-3 hours)
- 2.1 Create
src/teamlandkarte_mcp/cache/embedding_cache.py - 2.2 Implement
EmbeddingCache.__init__(create DB/schema if not exists) - 2.3 Implement
EmbeddingCache.get()(with TTL check) - 2.4 Implement
EmbeddingCache.put() - 2.5 Implement
EmbeddingCache.cleanup_expired() - 2.6 Add SQLite schema migration support (if DB exists but schema old)
- 2.7 Add unit tests for
EmbeddingCache - 2.8 Add
.gitignoreentry forembeddings_cache.db
Phase 3: Azure OpenAI Client (3-4 hours)
Testing Strategy:
-
Unit tests should mock Azure API responses with realistic embedding vectors (3072 dimensions for text-embedding-3-large)
-
Use fixed seed embeddings for deterministic test behavior (e.g.,
[0.1, 0.2, ..., 0.0]patterns) -
Integration tests marked with
@pytest.mark.integrationuse real API calls -
3.1 Create
src/teamlandkarte_mcp/azure/__init__.py -
3.2 Create
src/teamlandkarte_mcp/azure/openai_client.py -
3.3 Implement
AzureOpenAIClient.__init__ -
3.4 Implement
AzureOpenAIClient.get_embedding()with cache integration -
3.5 Implement
AzureOpenAIClient.get_embeddings_batch()(individual calls) -
3.6 Implement
AzureOpenAIClient.chat_completion()with retry logic -
3.7 Add
AzureAPIErrorexception class -
3.8 Add unit tests for client (mocked API)
-
3.9 Add integration test with real API (marked as
@pytest.mark.integration)
Phase 4: Similarity Engine (4-5 hours)
- 4.1 Create
src/teamlandkarte_mcp/matching/similarity.py - 4.2 Implement
cosine_similarity()function - 4.3 Implement
SimilarityEngine.__init__ - 4.4 Implement
SimilarityEngine._per_skill_similarity() - 4.5 Implement
SimilarityEngine._aggregate_similarity() - 4.6 Implement
SimilarityEngine.compute_competence_similarity()(router) - 4.7 Implement
SimilarityEngine.compute_role_similarity() - 4.8 Add unit tests for similarity computation (mocked embeddings)
- 4.9 Add integration test comparing both strategies
Phase 5: TaskAnalyzer Refactoring (3-4 hours)
- 5.1 Remove
TaskAnalyzer.__init__(self, mcp: FastMCP)signature - 5.2 Add new
TaskAnalyzer.__init__(self, azure_client: AzureOpenAIClient) - 5.3 Remove
_sample_json()method completely - 5.4 Remove
_heuristic_competences_from_text()method completely - 5.5 Refactor
extract_ranked_roles()to useazure_client.chat_completion() - 5.6 Remove heuristic fallback from
extract_ranked_roles() - 5.7 Refactor
_extract_requirements_from_description()to use LLM - 5.8 Remove heuristic fallback from
_extract_requirements_from_description() - 5.9 Refactor
apply_requirement_update()to use LLM - 5.10 Remove heuristic fallback from
apply_requirement_update() - 5.11 Remove
semantic_competence_similarity()method (replaced bySimilarityEngine) - 5.12 Update all
TaskAnalyzerunit tests
Phase 6: Matcher & Scorer Integration (2-3 hours)
- 6.1 Update
Matcher.__init__to acceptSimilarityEngine - 6.2 Replace competence similarity calls with
SimilarityEngine.compute_competence_similarity() - 6.3 Replace role similarity calls with
SimilarityEngine.compute_role_similarity() - 6.4 Update scorer aggregation logic if needed
- 6.5 Update integration tests for matching pipeline
Phase 7: Server Initialization (2 hours)
- 7.1 Update
build_server()inmcp_server.pyto load Azure config - 7.2 Initialize
EmbeddingCacheinstance - 7.3 Initialize
AzureOpenAIClientinstance - 7.4 Initialize
SimilarityEngineinstance - 7.5 Update
TaskAnalyzerinstantiation - 7.6 Update
Matcherinstantiation - 7.7 Add startup logging for Azure OpenAI connection
- 7.8 Add graceful error handling if Azure credentials missing
Phase 8: Cost Tracking & Monitoring (2 hours)
- 8.1 Create
src/teamlandkarte_mcp/azure/cost_tracker.py - 8.2 Implement
CostTrackerclass with logging methods - 8.3 Integrate cost tracking into
AzureOpenAIClient - 8.4 Add periodic cost report logging (stderr)
- 8.5 Add cost summary to tool outputs (optional, via config)
Phase 9: Documentation (2-3 hours)
- 9.1 Update
README.mdwith Azure OpenAI setup instructions - 9.2 Add Azure cost estimation section to
README.md - 9.3 Update
docs/troubleshooting.mdwith Azure API error guidance - 9.4 Document similarity strategies (
per_skillvsaggregate) - 9.5 Update
config.toml.examplewith comprehensive comments - 9.6 Update OpenSpec architecture docs
- 9.7 Add migration guide from old
database.tomlto newconfig.toml
Phase 10: Testing & Validation (3-4 hours)
- 10.1 Configure
pytest.iniwith integration marker:markers = integration: marks tests that call real Azure OpenAI API (deselect with '-m "not integration"') - 10.2 Run full test suite after refactoring (
pytest -m "not integration"for fast CI) - 10.3 Add new integration tests for Azure API paths (marked with
@pytest.mark.integration) - 10.4 Test embedding cache persistence across server restarts
- 10.5 Test both similarity strategies with real data
- 10.6 Validate cost tracking accuracy
- 10.7 Test error handling when Azure API unavailable
- 10.8 Performance benchmark: compare cache hit/miss scenarios
- 10.9 Manual validation in Cherry Studio
Phase 11: Cleanup (1 hour)
- 11.1 Remove all commented-out FastMCP sampling code
- 11.2 Remove unused imports (
from mcp.server.fastmcp import FastMCPfromTaskAnalyzer) - 11.3 Update type hints and docstrings
- 11.4 Run linter and fix style issues
- 11.5 Final code review
Impact Analysis
Changed Files
New files:
config.toml(renamed fromdatabase.toml)config.toml.example(renamed fromdatabase.toml.example)src/teamlandkarte_mcp/cache/embedding_cache.pysrc/teamlandkarte_mcp/azure/__init__.pysrc/teamlandkarte_mcp/azure/openai_client.pysrc/teamlandkarte_mcp/azure/cost_tracker.pysrc/teamlandkarte_mcp/matching/similarity.pyembeddings_cache.db(generated at runtime, gitignored)
Modified files:
src/teamlandkarte_mcp/config.py(new config sections)src/teamlandkarte_mcp/matching/task_analyzer.py(complete refactor)src/teamlandkarte_mcp/matching/matcher.py(similarity engine integration)src/teamlandkarte_mcp/mcp_server.py(initialization changes)README.md(setup instructions, cost docs)docs/troubleshooting.md(Azure error guidance)pyproject.toml(addopenaidependency).gitignore(addembeddings_cache.db,config.toml)- All OpenSpec architecture/design docs
Deleted code:
- All FastMCP sampling code in
TaskAnalyzer - All heuristic fallback code in
TaskAnalyzer _sample_json(),_heuristic_competences_from_text()methods
Breaking Changes
- Configuration file renamed: Users must rename
database.toml→config.toml - New required environment variables:
AZURE_OPENAI_EMBEDDING_API_KEY,AZURE_OPENAI_LLM_API_KEY - Hard dependency on Azure OpenAI: No offline/fallback mode
- New dependency:
openaiPython package - TaskAnalyzer constructor signature changed:
TaskAnalyzer.__init__(self, mcp: FastMCP)→TaskAnalyzer.__init__(self, azure_client: AzureOpenAIClient)- This affects any code that instantiates
TaskAnalyzerdirectly - Server initialization in
mcp_server.pymust be updated
- This affects any code that instantiates
- Matcher constructor signature changed:
Matcher.__init__(self, analyzer: TaskAnalyzer, config: MatchingConfig)→Matcher.__init__(self, analyzer: TaskAnalyzer, similarity_engine: SimilarityEngine, config: MatchingConfig)- Adds new required
similarity_engineparameter - Server initialization must pass
SimilarityEngineinstance
- Adds new required
Migration Path
- Rename
database.toml→config.toml - Add new Azure OpenAI sections to
config.toml - Add Azure API keys to
.env - Install updated dependencies:
uv sync - First run will create
embeddings_cache.dbautomatically
Security & Constraints
Security
- API keys in
.env: Never commit.envorconfig.tomlwith credentials - Embedding cache: Contains only embeddings, not raw capacity data (safe to persist)
- Network: All API calls over HTTPS to Azure OpenAI endpoint
- Error messages: Do not log API keys in error messages
Operational Constraints
- Azure OpenAI dependency: System will fail if Azure API unavailable (no fallback)
- API rate limits: Azure OpenAI enforces rate limits (TPM/RPM); implement retry with backoff
- Cost: Real monetary cost per API call (mitigated by caching)
- Latency: First search for a capacity will be slower (embedding generation); subsequent searches fast (cached)
Configuration Defaults
[embedding_cache]
enabled = true
db_path = "embeddings_cache.db"
ttl_days = 30
[matching.similarity]
strategy = "per_skill" # Maintains current behavior
Cost Estimation Details
API Pricing (as of 2026-02)
| Service | Model | Cost |
|---|---|---|
| Embeddings | text-embedding-3-large | $0.00013 per 1K tokens |
| LLM (input) | gpt-4.1 | $0.03 per 1K tokens |
| LLM (output) | gpt-4.1 | $0.06 per 1K tokens |
Typical Workflow Costs
Scenario 1: Task requirements extraction
- Input: ~500 tokens (task description)
- Output: ~300 tokens (JSON with roles/competences/dates)
- Cost: (500 × $0.03 / 1000) + (300 × $0.06 / 1000) = $0.033
Scenario 2: Capacity matching (100 candidates, 5 required skills)
- First run (cold cache):
- Required skills: 5 × ~10 tokens = 50 tokens → ~$0.0000065
- Candidate skills: 100 candidates × 3 skills avg × 10 tokens = 3000 tokens → ~$0.00039
- Total: ~$0.0004 (negligible)
- Subsequent runs (warm cache): $0 (all cached)
Scenario 3: Role inference
- Input: ~400 tokens
- Output: ~200 tokens
- Cost: ~$0.024
Monthly Estimates
Light usage (20 searches/month, 10 extractions):
- Embeddings: ~$0.05
- LLM: ~$0.50
- Total: ~$0.55/month
Moderate usage (100 searches/month, 50 extractions):
- Embeddings: ~$0.20 (with 90% cache hit rate)
- LLM: ~$2.50
- Total: ~$2.70/month
Heavy usage (500 searches/month, 200 extractions):
- Embeddings: ~$1.00 (with 90% cache hit rate)
- LLM: ~$10.00
- Total: ~$11/month
Open Questions
None (all clarifications provided by user).
Approval & Timeline
- Estimated effort: 28-35 hours (1 week full-time or 2 weeks part-time)
- Risk level: Medium (API dependency, cost implications, major refactor)
- Approval required: Yes (architecture change, new external dependency)
Next Steps
- Review and approve this proposal
- Create detailed implementation branch
- Implement phases 1-11 sequentially
- Conduct thorough testing (unit + integration + manual)
- Document migration guide
- Deploy to staging environment
- Monitor costs and performance
- Deploy to production