Implements docs/api-contract-v2.md. Migration 002 adds follow-up fields. EchoTransport replaced by SMTP->Clipboard selection behind unchanged approval gate. Scheduler (APScheduler) daily 07:00 fetch+score, env-gated, default off. Test image installs workspace packages; build context moved to repo root. Recovered and committed by integration lead after W2 worker hit iteration limit.
161 lines
No EOL
5.8 KiB
Python
161 lines
No EOL
5.8 KiB
Python
"""LLM gateway integration.
|
|
|
|
Per the task spec: LLM usage via packages.llm_gateway. Since T2 hasn't built
|
|
the gateway yet, this module provides a minimal mock-mode interface that the
|
|
API endpoints call. When packages.llm_gateway is available (importable), it
|
|
delegates to the real gateway. Otherwise, it returns deterministic canned
|
|
outputs per task name.
|
|
|
|
Task names used by the API:
|
|
- cv_assist: returns suggestions for a CV section bullet
|
|
- cl_critique: returns critique comments for a cover letter
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import time
|
|
from typing import Any
|
|
|
|
# Try to import the real llm_gateway package
|
|
try:
|
|
from packages.llm_gateway.client import run_task as _real_run_task # type: ignore
|
|
HAS_REAL_GATEWAY = True
|
|
except Exception:
|
|
HAS_REAL_GATEWAY = False
|
|
|
|
|
|
def _has_api_key() -> bool:
|
|
return bool(
|
|
os.environ.get("LLM_PRIMARY_KEY")
|
|
or os.environ.get("OLLAMA_API_KEY")
|
|
)
|
|
|
|
|
|
# Deterministic mock outputs per task name
|
|
MOCK_OUTPUTS: dict[str, dict[str, Any]] = {
|
|
"cv_assist": {
|
|
"suggestions": [
|
|
"Improved bullet: Led cross-functional team of 8 to deliver feature X 2 weeks ahead of schedule.",
|
|
"Alternative: Streamlined process reducing cycle time by 30% via automation.",
|
|
]
|
|
},
|
|
"cl_critique": {
|
|
"comments": [
|
|
{
|
|
"quote": "I am writing to apply",
|
|
"suggestion": "Consider a stronger opening that references the specific role.",
|
|
"severity": "medium",
|
|
},
|
|
{
|
|
"quote": "I have experience",
|
|
"suggestion": "Quantify with a specific achievement rather than a generic claim.",
|
|
"severity": "low",
|
|
},
|
|
]
|
|
},
|
|
"score": {
|
|
"score": 72,
|
|
"rationale": {
|
|
"match": 0.72,
|
|
"factors": {"skills": 0.8, "location": 0.6, "experience": 0.75},
|
|
},
|
|
"red_flags": [],
|
|
},
|
|
"cv_extract": {
|
|
"drafts": [
|
|
{
|
|
"kind": "experience",
|
|
"title": "Software Engineer",
|
|
"org": "Extracted Company",
|
|
"location": "Malmo",
|
|
"start_date": "2020-01",
|
|
"end_date": None,
|
|
"bullets": ["Developed web applications", "Led team of 3"],
|
|
"tags": ["python", "javascript"],
|
|
},
|
|
{
|
|
"kind": "education",
|
|
"title": "MSc Computer Science",
|
|
"org": "Lund University",
|
|
"location": "Lund",
|
|
"start_date": "2016-09",
|
|
"end_date": "2018-06",
|
|
"bullets": ["Specialized in distributed systems"],
|
|
"tags": ["algorithms", "distributed systems"],
|
|
},
|
|
]
|
|
},
|
|
"interview_prep": {
|
|
"content": (
|
|
"# Interview Prep\n\n"
|
|
"## Q1: Tell us about yourself\n"
|
|
"**Suggested angle:** Highlight your experience with Python and FastAPI, "
|
|
"and your ability to deliver features on time.\n\n"
|
|
"## Q2: Why are you interested in this role?\n"
|
|
"**Suggested angle:** Reference the specific technologies mentioned in the "
|
|
"posting and your experience with similar stacks.\n\n"
|
|
"## Q3: Describe a challenging project\n"
|
|
"**Suggested angle:** Use the STAR method. Reference your experience building "
|
|
"distributed systems at Lund University.\n\n"
|
|
"## Q4: How do you handle tight deadlines?\n"
|
|
"**Suggested angle:** Mention your track record of delivering 2 weeks ahead "
|
|
"of schedule and your automation-first approach.\n\n"
|
|
"## Q5: What are your salary expectations?\n"
|
|
"**Suggested angle:** Research market rates for the Skane region. "
|
|
"Be prepared to give a range.\n\n"
|
|
"## Q6: Tell us about a time you failed\n"
|
|
"**Suggested angle:** Pick something real but not catastrophic. Show what "
|
|
"you learned and how you changed your approach.\n\n"
|
|
"## Q7: How do you stay current with technology?\n"
|
|
"**Suggested angle:** Mention your tags: python, javascript, distributed "
|
|
"systems. Talk about hands-on side projects.\n\n"
|
|
"## Q8: Describe your ideal work environment\n"
|
|
"**Suggested angle:** Be honest but flexible. Mention collaboration and "
|
|
"autonomy.\n\n"
|
|
"## Q9: What questions do you have for us?\n"
|
|
"**Suggested angle:** Ask about team structure, current projects, and "
|
|
"growth opportunities.\n\n"
|
|
"## Q10: Why should we hire you?\n"
|
|
"**Suggested angle:** Summarize your top 3 qualifications matching the "
|
|
"posting requirements. Be specific."
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
def run_task(
|
|
task: str,
|
|
prompt: str,
|
|
schema: dict[str, Any] | None = None,
|
|
telemetry_sink: callable | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Run an LLM task. Uses mock mode when no API key is present.
|
|
|
|
Returns a dict with the task result. If a telemetry_sink callable is
|
|
provided, it is called with a dict of token/cost info.
|
|
"""
|
|
if HAS_REAL_GATEWAY and _has_api_key():
|
|
return _real_run_task(task, prompt, schema)
|
|
|
|
# Mock mode
|
|
time.sleep(0.01) # simulate latency
|
|
result = MOCK_OUTPUTS.get(task, {"result": "mock"})
|
|
|
|
# Validate against schema if provided (basic check)
|
|
# In real gateway this would be jsonschema validation
|
|
|
|
if telemetry_sink is not None:
|
|
telemetry_sink({
|
|
"task": task,
|
|
"model": "mock",
|
|
"provider": "mock",
|
|
"input_tokens": len(prompt) // 4, # rough estimate
|
|
"output_tokens": len(json.dumps(result)) // 4,
|
|
"cost_usd": 0.0,
|
|
"duration_ms": 10,
|
|
})
|
|
|
|
return result |