"""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." ), }, "email_classify": { "classification": "interview_invite", "state_proposal": "interviewing", "reason": "The email mentions an interview invitation.", }, "cv_tailor": { "tailored_cv": { "summary": "Senior Python Developer with 6+ years building scalable backend systems.", "skills": [ "Python", "Fast API", "PostgreSQL", "Docker", "Kubernetes", "AWS", ], "experience": [ { "company": "TechCorp", "role": "Senior Backend Engineer", "bullets": [ "Led migration of monolith to microservices using Fast API", "Reduced API latency by 40% through query optimization and caching", ], }, ], }, "change_log": [ {"action": "reordered", "detail": "Moved Python and Fast API to top of skills"}, {"action": "rephrased", "detail": "Rewrote first experience bullet to emphasize Fast API"}, ], }, "deadline_extract": { "apply_by": None, }, } 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