jobhunt-platform/packages/llm-gateway/src/llm_gateway/mock.py
hermes 8d8a863300 T2: packages/artifacts + packages/llm-gateway + docker-compose.yml + .env.example
packages/artifacts:
- render_cv_pdf(profile, sections) -> bytes: data-driven CV PDF generation
  using fpdf2 with bundled DejaVuSans TTF for unicode (Swedish chars tested).
  Jinja2 template for layout data prep, adapted from build_cv.py approach.
- render_cover_letter(text, profile) -> bytes: simple cover letter PDF.
- hash_bytes(b) -> str: sha256 hex digest.
- next_version(existing) -> int: version numbering helper.
- 16 tests, all passing: PDF validity, Swedish characters, hash stability,
  cover letter rendering, hash correctness, version logic.

packages/llm-gateway:
- Async-first Gateway class with provider config from env.
- Mock mode default when no API key env present (deterministic canned outputs
  per task name, defined in mock.py).
- Telemetry sink injectable (async or sync callable, receives TelemetryRow).
- Budget guard raises BudgetExceeded BEFORE any provider call is made.
- Retry policy: max 2 retries on 429/5xx, then fallback provider for STRONG
  tasks only. CHEAP tasks never use fallback (paid provider protection).
- Schema validation via jsonschema; SchemaValidationError on mismatch.
- Task class routing: CHEAP (score, extract, cv_assist) vs STRONG (critique,
  cl_critique, research). Model routing per task class.
- Paid provider detection heuristic; warns on paid fallback config.
- 31 tests, all passing: mock determinism, schema pass/fail, budget guard
  (mock + real mode), telemetry sink (async/sync/none), config from env,
  provider calls with mocked HTTP (retry, fallback, no-fallback-for-cheap).

docker-compose.yml:
- postgres:16 service, user/pass/db = jobhunt, host port 5433->5432,
  named volume jobhunt_pgdata, healthcheck.

.env.example:
- DATABASE_URL, LLM provider config (primary + fallback), task budgets,
  API and web settings.
2026-07-30 17:59:56 +00:00

96 lines
No EOL
2.7 KiB
Python

"""Mock mode for the LLM gateway.
When no API key is configured, the gateway returns deterministic canned
outputs per task name. This allows the API and tests to run offline.
"""
from __future__ import annotations
import json
import time
import uuid
# Deterministic canned outputs per task name.
# Each entry is a dict that will be returned as the task result.
MOCK_OUTPUTS: dict[str, dict] = {
"score": {
"score": 75,
"rationale": {
"match": "good",
"reasons": ["skills align", "location matches"],
},
},
"extract": {
"company": "Example Corp",
"title": "Software Engineer",
"location": "Stockholm",
"requirements": ["Python", "PostgreSQL", "Docker"],
},
"cv_assist": {
"suggestions": [
"Led a team of 5 developers to deliver a critical integration",
"Reduced API latency by 40% through caching and query optimization",
],
},
"cl_critique": {
"comments": [
{
"quote": "I am a hard worker",
"suggestion": "Replace generic claim with a specific achievement metric",
"severity": "medium",
},
{
"quote": "Dear Sir/Madam",
"suggestion": "Address the hiring manager by name if known",
"severity": "low",
},
],
},
"critique": {
"comments": [
{
"quote": "sample text",
"suggestion": "improve clarity",
"severity": "low",
},
],
},
"research": {
"summary": "The company is a mid-size tech firm focused on cloud infrastructure.",
"key_points": ["Founded in 2015", "Series B funding", "Remote-first culture"],
},
}
# Default mock output for unknown task names.
DEFAULT_MOCK_OUTPUT: dict = {
"result": "mock output",
"task": "unknown",
}
def get_mock_output(task: str) -> dict:
"""Return a deterministic mock output for *task*.
For unknown tasks, returns DEFAULT_MOCK_OUTPUT with the task name filled in.
"""
if task in MOCK_OUTPUTS:
# Return a copy so callers cannot mutate the canned data.
return json.loads(json.dumps(MOCK_OUTPUTS[task]))
result = json.loads(json.dumps(DEFAULT_MOCK_OUTPUT))
result["task"] = task
return result
def mock_telemetry_row(task: str, model: str) -> dict:
"""Build a mock telemetry row dict for offline mode."""
return {
"id": str(uuid.uuid4()),
"task": task,
"model": model,
"provider": "mock",
"input_tokens": 0,
"output_tokens": 0,
"cost_usd": 0.0,
"duration_ms": 0,
"mock": True,
}