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.
96 lines
No EOL
2.7 KiB
Python
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,
|
|
} |