From 2fdb3dd0ce41444632a26de62c4f17520d36a79b Mon Sep 17 00:00:00 2001 From: Devang Borkar Date: Fri, 26 Jun 2026 10:39:19 -0700 Subject: [PATCH 1/2] feat: add oltp observability support --- .env.example | 10 +++ README.md | 27 +++++++- backend/api_service/ai_service.py | 92 ++++++++++++--------------- backend/app.py | 2 + backend/observability.py | 100 ++++++++++++++++++++++++++++++ backend/requirements.txt | 7 +++ 6 files changed, 183 insertions(+), 55 deletions(-) create mode 100644 backend/observability.py diff --git a/.env.example b/.env.example index d5e90fd..2ff0b8b 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,13 @@ OPENROUTER_API_KEY=your_openrouter_api_key_here # Optional OpenRouter attribution headers # OPENROUTER_HTTP_REFERER=https://your-app-domain.com # OPENROUTER_APP_TITLE=Cover Letter Generator + +# Optional PilotCrew Observability OTLP trace export +# Minimum setup +# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://pilotcrew.example.com/v1/traces +# OTEL_EXPORTER_OTLP_HEADERS=x-pilotcrew-ingest-key=your_pilotcrew_ingest_key +# OTEL_SERVICE_NAME=cover-letter-generator + +# Optional metadata for filtering and release comparison +# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,pilotcrew.deployment_id=2026-06-25.1 +# PILOTCREW_OBSERVABILITY_ENABLED=false diff --git a/README.md b/README.md index a88a82b..441c8d1 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ A web app that generates personalized cover letters from job descriptions and user details, then renders the output into a downloadable PDF. -The app now uses **OpenRouter only** for LLM generation, with the allowed model list defined in YAML and surfaced in the UI dropdown. +The app uses **OpenRouter through the OpenAI-compatible SDK** for LLM generation, with the allowed model list defined in YAML and surfaced in the UI dropdown. ## Features -- OpenRouter-powered cover letter generation +- OpenRouter-powered cover letter generation through the OpenAI SDK - Job application question answering using the same resume/projects context - Resume PDF tailoring from structured `resume.yaml` - YAML-driven model allowlist (`config/model.yaml`) @@ -61,6 +61,29 @@ OPENROUTER_API_KEY=your_openrouter_api_key_here # OPENROUTER_APP_TITLE=Cover Letter Generator ``` +To export traces to PilotCrew Observability, add the OTLP endpoint, ingest key +header, and service name: + +```bash +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://pilotcrew.example.com/v1/traces +OTEL_EXPORTER_OTLP_HEADERS=x-pilotcrew-ingest-key=your_pilotcrew_ingest_key +OTEL_SERVICE_NAME=cover-letter-generator +``` + +Trace export is enabled when `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is set. The +backend uses standard OpenTelemetry Flask/httpx instrumentation for service +spans and OpenInference OpenAI instrumentation for OpenRouter LLM calls. The +OpenInference spans carry prompt, response, model, and usage attributes when the +SDK/provider response exposes them. + +Optional metadata can be added later for filtering and release comparison: + +```bash +OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,pilotcrew.deployment_id=2026-06-25.1 +``` + +Set `PILOTCREW_OBSERVABILITY_ENABLED=false` to force-disable trace export. + ### 4. Configure model catalog Edit `config/model.yaml`: diff --git a/backend/api_service/ai_service.py b/backend/api_service/ai_service.py index 7c36e48..b2e1b2f 100644 --- a/backend/api_service/ai_service.py +++ b/backend/api_service/ai_service.py @@ -5,7 +5,7 @@ import re import traceback -import httpx +from openai import APIStatusError, OpenAI from backend.api_service.model_config import ( get_base_url, @@ -48,6 +48,37 @@ } EXPERIENCE_OWNED_PROJECT_IDS = {"pilotcrew-gen-eval", "lh-multimodal-svc"} + +def build_openrouter_client(): + return OpenAI( + api_key=OPENROUTER_API_KEY, + base_url=get_base_url().rstrip("/"), + timeout=120.0, + ) + + +def extract_chat_completion_text(completion): + choices = completion.choices or [] + if not choices: + raise RuntimeError("OpenRouter response did not include any choices") + + message = choices[0].message + response_text = parse_openrouter_content(message.content) + if not response_text: + raise RuntimeError("No response text received from OpenRouter") + return response_text + + +def create_openrouter_completion(**kwargs): + try: + completion = build_openrouter_client().chat.completions.create(**kwargs) + return extract_chat_completion_text(completion) + except APIStatusError as exc: + logger.error("OpenRouter API error %s: %s", exc.status_code, exc.response.text) + raise RuntimeError( + f"OpenRouter API request failed with status {exc.status_code}" + ) from exc + def get_pydantic_json_schema(model): if hasattr(model, "model_json_schema"): return model.model_json_schema() @@ -206,7 +237,7 @@ def call_openrouter(system_instruction, prompt, selected_model, enable_web_searc if not is_allowed_model(selected_model): raise ValueError(f"Model '{selected_model}' is not allowed by server configuration") - payload = { + completion_kwargs = { "model": selected_model, "messages": [ {"role": "system", "content": system_instruction}, @@ -226,40 +257,11 @@ def call_openrouter(system_instruction, prompt, selected_model, enable_web_searc ], } if enable_web_search: - payload["tools"] = [WEB_SEARCH_TOOL] - - headers = { - "Authorization": f"Bearer {OPENROUTER_API_KEY}", - "Content-Type": "application/json", - } - http_referer = os.environ.get("OPENROUTER_HTTP_REFERER") - app_title = os.environ.get("OPENROUTER_APP_TITLE") - if http_referer: - headers["HTTP-Referer"] = http_referer - if app_title: - headers["X-Title"] = app_title + completion_kwargs["extra_body"] = {"tools": [WEB_SEARCH_TOOL]} endpoint = f"{get_base_url().rstrip('/')}/chat/completions" logger.info(f"Calling OpenRouter chat completions at: {endpoint}") - response = httpx.post(endpoint, headers=headers, json=payload, timeout=120.0) - - if response.status_code >= 400: - logger.error(f"OpenRouter API error {response.status_code}: {response.text}") - raise RuntimeError(f"OpenRouter API request failed with status {response.status_code}") - - response_data = response.json() - choices = response_data.get("choices") or [] - if not choices: - logger.error("OpenRouter response did not include any choices") - raise RuntimeError("OpenRouter response did not include any choices") - - message = choices[0].get("message", {}) - response_text = parse_openrouter_content(message.get("content")) - if not response_text: - logger.error("No response text received from OpenRouter") - raise RuntimeError("No response text received from OpenRouter") - - return response_text + return create_openrouter_completion(**completion_kwargs) def call_openrouter_json( @@ -273,7 +275,7 @@ def call_openrouter_json( if not OPENROUTER_API_KEY: raise RuntimeError("OPENROUTER_API_KEY not configured") - payload = { + completion_kwargs = { "model": selected_model, "messages": [ {"role": "system", "content": system_instruction}, @@ -283,27 +285,11 @@ def call_openrouter_json( "temperature": temperature, } if enable_web_search: - payload["tools"] = [WEB_SEARCH_TOOL] + completion_kwargs["extra_body"] = {"tools": [WEB_SEARCH_TOOL]} - headers = { - "Authorization": f"Bearer {OPENROUTER_API_KEY}", - "Content-Type": "application/json", - } endpoint = f"{get_base_url().rstrip('/')}/chat/completions" - response = httpx.post(endpoint, headers=headers, json=payload, timeout=120.0) - if response.status_code >= 400: - raise RuntimeError(f"OpenRouter API request failed with status {response.status_code}") - - response_data = response.json() - choices = response_data.get("choices") or [] - if not choices: - raise RuntimeError("OpenRouter response did not include any choices") - - message = choices[0].get("message", {}) - response_text = parse_openrouter_content(message.get("content")) - if not response_text: - raise RuntimeError("No response text received from OpenRouter") - return response_text + logger.info(f"Calling OpenRouter chat completions at: {endpoint}") + return create_openrouter_completion(**completion_kwargs) def parse_questions(questions): diff --git a/backend/app.py b/backend/app.py index c3ef4e0..d1449a5 100644 --- a/backend/app.py +++ b/backend/app.py @@ -16,6 +16,7 @@ generate_resume_bullets, ) from backend.api_service.model_config import get_default_model, get_models, is_allowed_model, load_model_config +from backend.observability import configure_observability from pdf_service.pdf_generator import generate_cover_letter_pdf from pdf_service.resume_generator import ( compile_tex_to_pdf, @@ -44,6 +45,7 @@ def json_dumps_for_prompt(payload, limit=6000): app = Flask(__name__, static_folder='../frontend/build') CORS(app) +configure_observability(app, logger) load_model_config() diff --git a/backend/observability.py b/backend/observability.py new file mode 100644 index 0000000..3990b44 --- /dev/null +++ b/backend/observability.py @@ -0,0 +1,100 @@ +import logging +import os +from typing import Mapping +from urllib.parse import unquote + +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.instrumentation.flask import FlaskInstrumentor +from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from openinference.instrumentation.openai import OpenAIInstrumentor + +_CONFIGURED = False + + +def _is_enabled() -> bool: + enabled = os.environ.get("PILOTCREW_OBSERVABILITY_ENABLED", "true").strip().lower() + return enabled not in {"0", "false", "no", "off"} + + +def _get_otlp_endpoint() -> str: + traces_endpoint = ( + os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") + or "" + ).strip() + if traces_endpoint: + return traces_endpoint + + otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "").strip().rstrip("/") + if not otlp_endpoint: + return "" + return f"{otlp_endpoint}/v1/traces" + + +def _build_headers() -> Mapping[str, str]: + raw_headers = ( + os.environ.get("OTEL_EXPORTER_OTLP_TRACES_HEADERS") + or os.environ.get("OTEL_EXPORTER_OTLP_HEADERS") + or "" + ).strip() + return _parse_key_value_list(raw_headers) + + +def _parse_key_value_list(raw_value: str) -> dict[str, str]: + values: dict[str, str] = {} + if not raw_value: + return values + + for item in raw_value.split(","): + key, separator, value = item.partition("=") + if separator and key.strip() and value.strip(): + values[key.strip()] = unquote(value.strip()) + return values + + +def _build_resource_attributes(): + attributes = _parse_key_value_list(os.environ.get("OTEL_RESOURCE_ATTRIBUTES", "")) + attributes["service.name"] = ( + os.environ.get("OTEL_SERVICE_NAME") + or attributes.get("service.name") + or "cover-letter-generator" + ) + + service_namespace = os.environ.get("OTEL_SERVICE_NAMESPACE") + app_version = os.environ.get("APP_VERSION") + if service_namespace: + attributes["service.namespace"] = service_namespace + if app_version: + attributes["service.version"] = app_version + + return Resource.create(attributes) + + +def configure_observability(app, logger: logging.Logger) -> None: + global _CONFIGURED + if _CONFIGURED: + return + + if not _is_enabled(): + logger.info("PilotCrew observability disabled by PILOTCREW_OBSERVABILITY_ENABLED") + return + + endpoint = _get_otlp_endpoint() + if not endpoint: + logger.info("PilotCrew observability OTLP endpoint not configured; skipping trace export") + return + + headers = _build_headers() + provider = TracerProvider(resource=_build_resource_attributes()) + exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers or None) + provider.add_span_processor(BatchSpanProcessor(exporter)) + trace.set_tracer_provider(provider) + + FlaskInstrumentor().instrument_app(app) + HTTPXClientInstrumentor().instrument() + OpenAIInstrumentor().instrument(tracer_provider=provider) + _CONFIGURED = True + logger.info("PilotCrew observability exporting traces to %s", endpoint) diff --git a/backend/requirements.txt b/backend/requirements.txt index 59313d2..b717743 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,6 +2,13 @@ Flask==3.0.2 Flask-Cors==4.0.0 gunicorn==23.0.0 httpx==0.28.1 +openai==2.44.0 +opentelemetry-api==1.26.0 +opentelemetry-sdk==1.26.0 +opentelemetry-exporter-otlp-proto-http==1.26.0 +opentelemetry-instrumentation-flask==0.47b0 +opentelemetry-instrumentation-httpx==0.47b0 +openinference-instrumentation-openai==0.1.52 pypdf==4.0.2 pydantic==2.7.4 PyYAML==5.4.1 From c5a402617d71b8e4e46309319f48dcfe0dede67b Mon Sep 17 00:00:00 2001 From: Devang Borkar Date: Sun, 28 Jun 2026 15:49:05 -0700 Subject: [PATCH 2/2] fix: improve prompts --- .../api_service/prompts/cover_letter_sys.txt | 26 ++++++++++++++----- .../api_service/prompts/full_resume_sys.txt | 16 ++++++++++++ .../prompts/question_answer_sys.txt | 25 +++++++++++++----- .../prompts/resume_bullet_points_sys.txt | 26 ++++++++++++++----- 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/backend/api_service/prompts/cover_letter_sys.txt b/backend/api_service/prompts/cover_letter_sys.txt index 2bdd6ec..e26c617 100644 --- a/backend/api_service/prompts/cover_letter_sys.txt +++ b/backend/api_service/prompts/cover_letter_sys.txt @@ -8,19 +8,31 @@ Core behavior: - If the resume and project list disagree on employment or education facts, prefer the resume. - Never invent employers, dates, work authorization, locations, metrics, tools, products, or company facts. +Default positioning: +- Unless the job description clearly points elsewhere, position Devang as an AI infrastructure and backend systems engineer. +- The strongest recurring market signals to surface when supported are backend/API engineering, scalability/reliability, performance/latency, product or user-facing delivery, LLM evaluation, full-stack engineering, RAG/retrieval, observability/monitoring/tracing, AI agents, Python, Docker/containerized execution, deterministic verifiers/grading, cloud platforms, model training/serving/inference, security/auth/guardrails, PostgreSQL/pgvector, and distributed systems. +- Do not make the letter read like a generic LLM enthusiasm pitch. Balance AI/ML language with concrete backend, platform, evaluation, and shipped-system evidence. + +Role-angle guidance: +- For AI infrastructure, agent platform, eval, or ML systems roles, lead with PilotCrew AI, LLM evaluation infrastructure, agent runners, tool-use traces, deterministic grading, prompt optimization, observability, and CausalFlow when relevant. +- For backend, platform, full-stack, cloud, or product engineering roles, lead with Hexaview backend/API work, Gitartha-Engine, Loan-Portal, ResShare, REST APIs, PostgreSQL/pgvector, Go/FastAPI/Flask, Docker, reliability, and user-facing workflow delivery. +- For ML research or model-quality roles, lead with PRM, CausalFlow, LearnHaus, LLM-as-Judge, LoRA fine-tuning, benchmark design, experimentation, and model-evaluation evidence. +- For security, cyber, or guardrail roles, lead with Cyber-Bench, deterministic verification, ResShare authorization/access isolation, tool-use monitoring, and backend reliability. Do not overstate cybersecurity experience beyond the provided evidence. Internal process: 1. Extract the 3-5 most important requirements from the job description. -2. Scan the full project evidence bank and resume. -3. Internally rank the strongest 2-3 matching projects or experiences. -4. Use only the strongest evidence in the final letter. Do not mention weakly related projects. -5. If web search is available, use it only for company, product, team, or recent company-context facts. Do not use web search for Devang's personal facts. -6. Use at most one concrete company fact from search, and only if it is directly relevant to the role. If search results are thin or ambiguous, write a grounded role-fit sentence based on the job description instead. +2. Choose the best role angle from the guidance above. +3. Scan the full project evidence bank and resume. +4. Internally rank the strongest 2-3 matching projects or experiences for that role angle. +5. Use only the strongest evidence in the final letter. Do not mention weakly related projects. +6. If web search is available, use it only for company, product, team, or recent company-context facts. Do not use web search for Devang's personal facts. +7. Use at most one concrete company fact from search, and only if it is directly relevant to the role. If search results are thin or ambiguous, write a grounded role-fit sentence based on the job description instead. Evidence standards: - Every skill claim should be backed by a concrete project, work experience, technology, metric, or shipped system. - Prefer quantified evidence: latency, throughput, accuracy, F1, users, requests, retrieval time, cloud deployment, model size, or reliability outcome. - Mirror job-description keywords only when they accurately describe Devang's experience. +- Prefer the terms employers are repeatedly selecting for when they are supported: backend APIs, scalable systems, reliability, performance, LLM evaluation, observability, RAG/vector search, deterministic verifiers, Dockerized execution, cloud deployment, distributed systems, PostgreSQL/pgvector, security/auth, and model-quality workflows. - Avoid inflated or generic language such as "pioneering," "democratize," "mission-driven," "fast learner," "passionate," and "perfect fit" unless the job description itself uses that language and the claim is supported. Output format: @@ -28,7 +40,7 @@ Output format: - Output is pasted directly into a finished application document. Write as finalized prose only: no meta-commentary, no assistant-style sign-offs, and no invitations to elaborate (for example never write lines like "If you want I can explain more about the projects above," "feel free to ask," "let me know if you would like detail on," or similar). - Do not include header, address, date, greeting, salutation, signature, citations, raw URLs, markdown headings, or bullet characters. - Write 3-4 short paragraphs. -- Paragraph 1: name the role if it is clear and connect the role's main need to Devang's strongest matching evidence. +- Paragraph 1: name the role if it is clear and connect the role's main need to Devang's strongest matching evidence. Do not start with "I am writing to express my interest" unless the custom instructions explicitly ask for a traditional opening. - Paragraph 2: map 2-3 job requirements to specific resume/project evidence in prose. - Paragraph 3: include one company/product/team-specific sentence if web search or the job description provides a reliable fact; otherwise focus on role fit. -- Paragraph 4: close confidently with interview-oriented momentum. \ No newline at end of file +- Paragraph 4: close confidently with interview-oriented momentum. diff --git a/backend/api_service/prompts/full_resume_sys.txt b/backend/api_service/prompts/full_resume_sys.txt index 28dad78..76d529f 100644 --- a/backend/api_service/prompts/full_resume_sys.txt +++ b/backend/api_service/prompts/full_resume_sys.txt @@ -8,6 +8,7 @@ Your job: - Choose the strongest projects from the provided project catalog for the target role. - Use the available one-page space. Do not produce a sparse or overly cautious resume. - Keep the resume within one page, but make the first draft full and substantive. +- Unless the target role clearly points elsewhere, make the resume read as AI infrastructure plus backend systems engineering rather than a generic LLM resume. Evidence rules: - Use only the provided resume.yaml data, project catalog, job description, personal details, custom instructions, and reliable company research when available. @@ -18,9 +19,24 @@ Evidence rules: - Every experience entry must use an id from the mandatory experience list. - Do not duplicate mandatory work experience as a project. If a body of work is already represented by an experience entry, keep it in Experience and choose different projects. +Market-aligned emphasis: +- High-frequency application themes to surface when supported: backend/API engineering, REST APIs, scalability/reliability, performance/latency, product or user-facing delivery, LLM evaluation, full-stack engineering, RAG/retrieval, vector search, observability/monitoring/tracing, AI agents, Python, Docker/containerized execution, deterministic verifiers/grading, cloud platforms, model training/serving/inference, security/auth/guardrails, testing/code quality, PostgreSQL/pgvector, and distributed systems. +- Replace vague terms like "Harness Engineering" with clearer ATS terms such as LLM evaluation, agentic workflows, deterministic graders, tool-calling, observability, and backend APIs. +- Keep coding-agent tool names secondary unless the job description explicitly asks for them. + +Role-angle project selection: +- First choose the role angle from the job description, then choose projects that support it. +- AI infrastructure, agent platform, or eval roles: prioritize CausalFlow, cyber-bench, data-science-bench, gemini-hardwarebench, and PilotCrew experience/supporting projects when available. +- Backend, platform, full-stack, or product roles: prioritize gitartha-engine, loan-portal, resshare, and Hexaview experience. +- ML research or model-quality roles: prioritize prm-on-device, causalflow, and PilotCrew/LearnHaus experience. +- Security, guardrail, or cyber-adjacent roles: prioritize cyber-bench, resshare, and backend/security evidence from experience. +- Developer tooling roles: prioritize llm-linter and cover-letter-generator when they fit, plus PilotCrew tooling/eval experience. +- If a listed project id is not present in the provided project_catalog, do not invent it. Choose the strongest available catalog-backed substitute. + One-page constraints: - Use exactly 5 skill groups unless a retry asks you to shorten. - Keep each skill group ATS-rich and comma-separated. +- Prefer skill groups similar to: Languages; Backend/Infra; AI/ML Systems; ML/Frameworks; Cloud/APIs. Adjust names to fit the target role, but include supported high-frequency terms such as REST APIs, PostgreSQL/pgvector, observability, distributed systems, LLM evaluation, RAG/vector search, deterministic graders, Docker, AWS/GCP, and CI/CD when relevant. - Include exactly the mandatory experience entries. - Use 3 bullets for the current or strongest role. - Use 2 bullets for each other experience entry. diff --git a/backend/api_service/prompts/question_answer_sys.txt b/backend/api_service/prompts/question_answer_sys.txt index 8488a44..192d534 100644 --- a/backend/api_service/prompts/question_answer_sys.txt +++ b/backend/api_service/prompts/question_answer_sys.txt @@ -8,18 +8,31 @@ Core directives: - Match job-description terminology when it accurately describes Devang's experience. - Keep answers concise, specific, and professional. Default to 2-5 sentences unless the question asks for a different length. - If context is not enough to answer accurately, say that briefly instead of guessing. +- Most questions are expected to be motivation, company-fit, role-fit, experience-proof, behavioral, or "anything else to add" prompts. Optimize for fast, polished application answers, not exhaustive essays. + +Default positioning: +- Unless the question or job description clearly points elsewhere, frame Devang as an AI infrastructure and backend systems engineer. +- Favor supported evidence around backend/API engineering, scalable systems, performance, user-facing product delivery, LLM evaluation, agentic systems, RAG/retrieval, observability, deterministic verifiers, Dockerized execution, cloud deployment, PostgreSQL/pgvector, distributed systems, security/auth, and model-quality workflows. +- Balance AI language with concrete engineering evidence. Do not answer as if Devang is only an LLM prompt engineer. Internal process for each question: -1. Classify the question as logistics, yes/no, experience proof, motivation, behavioral, company-specific, or short-form. -2. For yes/no or logistics questions, answer directly in the first sentence, then add only necessary support. -3. For experience-proof questions, scan the full project evidence bank and resume, internally rank the strongest matching evidence, and cite the best 1-2 projects or work experiences. -4. For company-specific motivation questions, use web search only for company, product, team, or current company-context facts. Do not use web search for Devang's personal facts. -5. Use at most one concrete company fact from search, and only if it strengthens the answer. If search results are thin or ambiguous, answer from the job description only. -6. Omit the classification and analysis from the final JSON. +1. Classify the question as why-company, why-role-fit, why-you, experience proof, behavioral, company-specific, short-form, or logistics/yes-no. +2. Choose the best role angle: AI infrastructure/agent evaluation, backend/platform, ML research, full-stack/product, security/cyber eval, developer tooling, or data/ML infrastructure. +3. For why-company questions, combine one reliable company/product/team hook with one concrete reason Devang's background fits that work. If company research is thin, use a specific role requirement from the job description instead. +4. For why-role-fit or why-you questions, map the top 2 role requirements to Devang's strongest supported evidence. Prefer PilotCrew for AI infra/evals, Hexaview for production backend/API scale, CausalFlow or PRM for research/model-quality roles, and Gitartha-Engine, Loan-Portal, or ResShare for backend/full-stack/product roles. +5. For experience-proof questions, cite the best 1-2 projects or work experiences and include tools, systems, scale, or metrics when supported. +6. For behavioral questions, answer with a compact situation/action/result structure without labeling it STAR. +7. For "anything else" or final-pitch questions, give a short closing pitch that reinforces the role angle and avoids repeating the cover letter. +8. For rare yes/no or logistics questions, answer directly in the first sentence, then add only necessary support. +9. Use web search only for company, product, team, or current company-context facts. Do not use web search for Devang's personal facts. +10. Use at most one concrete company fact from search per answer, and only if it strengthens the answer. If search results are thin or ambiguous, answer from the job description only. +11. Omit the classification and analysis from the final JSON. Style rules: - Start direct answers with wording like "Yes, ...", "No, ...", "I am available ...", or "My strongest relevant experience is ..." when that fits the question. - Prefer concrete evidence over broad adjectives. +- For motivation and fit answers, start with the actual fit instead of a generic phrase like "I am excited about this opportunity." +- Use employer language when truthful: backend APIs, scalable systems, reliability, observability, LLM evaluation, agent workflows, RAG, vector search, cloud deployment, deterministic grading, and user-facing systems. - Avoid inflated wording such as "pioneering," "democratize," "mission-driven," "fast learner," "passionate," and "perfect fit" unless the question or job description specifically calls for that framing. - Do not include citations, raw URLs, markdown, or bullet characters inside answer text. diff --git a/backend/api_service/prompts/resume_bullet_points_sys.txt b/backend/api_service/prompts/resume_bullet_points_sys.txt index 00223ce..8930bbd 100644 --- a/backend/api_service/prompts/resume_bullet_points_sys.txt +++ b/backend/api_service/prompts/resume_bullet_points_sys.txt @@ -14,10 +14,21 @@ Evidence rules: - If resume.yaml and the project evidence bank disagree, prefer resume.yaml for title, employer, dates, education, and current bullet facts. - Use job-description language only when it accurately describes Devang's real experience. - Include as many important job-description keywords and phrases as possible across the rewritten bullets when they are truthfully supported by the resume entries or project evidence bank. -- Prefer exact JD terminology for supported skills, tools, responsibilities, and environments, such as customer-facing deployment, integration, AI/ML solutions, RAG, evaluation, cloud platforms, MLOps, monitoring, CI/CD, security, compliance, scalable systems, and enterprise software. +- Prefer exact JD terminology for supported skills, tools, responsibilities, and environments, such as backend/API engineering, REST APIs, customer-facing deployment, integration, AI/ML systems, LLM evaluation, RAG/retrieval, vector search, observability/monitoring/tracing, cloud platforms, Dockerized execution, CI/CD, security/auth, compliance, scalable systems, distributed systems, PostgreSQL/pgvector, performance/latency optimization, and enterprise software. - Prefer concrete evidence: shipped systems, tools, models, frameworks, scale, accuracy, users, requests, benchmarks, cloud deployments, reliability work, and measurable outcomes. - If an entry has weak relevance to the job, make it broadly strong and truthful instead of forcing unsupported keywords. +Default positioning: +- Unless the job description clearly points elsewhere, make the resume read like AI infrastructure plus backend systems engineering, not a generic LLM resume. +- Surface high-frequency application themes when supported: backend APIs, scalability/reliability, performance/latency, product or user-facing workflows, LLM evaluation, full-stack engineering, RAG/retrieval, observability, AI agents, Python, Docker, deterministic verifiers/grading, cloud deployment, model training/serving/inference, testing/code quality, PostgreSQL/pgvector, and distributed systems. +- Keep Coding Agent tool names secondary. They are less valuable than concrete platform, backend, evaluation, and reliability terms. + +Role-angle emphasis: +- AI infrastructure, agent platform, or eval roles: emphasize PilotCrew AI, LLM evaluation infrastructure, agent runners, tool-use traces, deterministic grading, observability, Dockerized execution, prompt optimization, and CausalFlow. +- Backend, platform, full-stack, or product roles: emphasize Hexaview API scale, REST APIs, microservices, Gitartha-Engine, Loan-Portal, ResShare, Go/FastAPI/Flask, PostgreSQL/pgvector, auth, reliability, performance, and user-facing workflows. +- ML research or model-quality roles: emphasize PRM, CausalFlow, LearnHaus, LLM-as-Judge, LoRA fine-tuning, benchmark design, experiments, accuracy, and model-evaluation evidence. +- Security, guardrail, or cyber-adjacent roles: emphasize Cyber-Bench, deterministic verifiers, auth/access isolation, tool-use monitoring, backend reliability, and ResShare security evidence. Do not overstate security depth beyond the evidence. + Resume bullet style: - Write in resume-bullet style, not first-person prose. - Start each bullet with a strong action verb. @@ -32,12 +43,13 @@ Resume bullet style: Internal process: 1. Extract the role's main technical requirements and seniority signal from the job description. -2. Build an internal keyword checklist from the job description and mark which keywords are supported by the provided evidence. -3. Review every provided resume target id, title, organization, current bullets, and required bullet count. -4. Choose the strongest truthful emphasis for each entry based on the role. -5. Rewrite every entry's bullets while preserving the id and exact bullet count. -6. Compare your rewritten bullets to the original bullets. If a rewritten bullet keeps the same sentence structure or only swaps a few words, rewrite it again. -7. Before answering, check that every provided id appears exactly once and each bullets array has the required length. +2. Choose the strongest role angle from the guidance above. +3. Build an internal keyword checklist from the job description and mark which keywords are supported by the provided evidence. +4. Review every provided resume target id, title, organization, current bullets, and required bullet count. +5. Choose the strongest truthful emphasis for each entry based on the role. +6. Rewrite every entry's bullets while preserving the id and exact bullet count. +7. Compare your rewritten bullets to the original bullets. If a rewritten bullet keeps the same sentence structure or only swaps a few words, rewrite it again. +8. Before answering, check that every provided id appears exactly once and each bullets array has the required length. Output rules: - Return valid JSON only.