Wire master resume improver prompt to OpenRouter optimizer and add explicit server logs and UI feedback
This commit is contained in:
parent
e84871926b
commit
62b07080ed
2 changed files with 55 additions and 22 deletions
|
|
@ -74,7 +74,10 @@ function polishSummaryStrict(currentSummary: string, title?: string): string {
|
||||||
// OpenRouter / Small Model Integration (Guaranteed anti-hallucination prompt)
|
// OpenRouter / Small Model Integration (Guaranteed anti-hallucination prompt)
|
||||||
async function optimizeWithOpenRouter(payload: { profile: any; work: any[]; skills: string[] }) {
|
async function optimizeWithOpenRouter(payload: { profile: any; work: any[]; skills: string[] }) {
|
||||||
const apiKey = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY;
|
const apiKey = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY;
|
||||||
if (!apiKey) return null;
|
if (!apiKey) {
|
||||||
|
console.log("[Resume Optimizer] No OPENROUTER_API_KEY found; applying deterministic local polish.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const isRouter = !!process.env.OPENROUTER_API_KEY;
|
const isRouter = !!process.env.OPENROUTER_API_KEY;
|
||||||
const endpoint = isRouter
|
const endpoint = isRouter
|
||||||
|
|
@ -84,25 +87,42 @@ async function optimizeWithOpenRouter(payload: { profile: any; work: any[]; skil
|
||||||
const model =
|
const model =
|
||||||
process.env.OPENROUTER_MODEL ||
|
process.env.OPENROUTER_MODEL ||
|
||||||
process.env.OPENAI_MODEL ||
|
process.env.OPENAI_MODEL ||
|
||||||
(isRouter ? "meta-llama/llama-3.2-3b-instruct" : "gpt-4o-mini");
|
(isRouter ? "google/gemini-2.5-flash" : "gpt-4o-mini");
|
||||||
|
|
||||||
const systemPrompt = `You are an accurate, truth-grounded ATS resume editor.
|
console.log(`[Resume Optimizer] Invoking LLM via ${endpoint} using model: ${model}`);
|
||||||
CRITICAL INTEGRITY INSTRUCTIONS:
|
|
||||||
1. NEVER invent, fabricate, exaggerate, or assume any facts, tools, employers, degrees, metrics, dates, or certifications.
|
const systemPrompt = `You are a master executive resume optimizer and ATS specialist.
|
||||||
2. Only rewrite the candidate's existing text to improve grammar, conciseness, and use strong action verbs.
|
OBJECTIVE:
|
||||||
3. Keep the exact same company names, titles, and real duties.
|
Transform the candidate's existing summary and work experience bullets into professional, compelling, high-impact statements that pass Workday, Taleo, Greenhouse, and Lever ATS screens.
|
||||||
4. If a bullet point is short or simple, polish its grammar; DO NOT add fake percentages, fake dollar amounts, or unmentioned technologies.
|
|
||||||
5. Return strictly a JSON object with this format:
|
CORE RULES:
|
||||||
|
1. TRUTHFUL GROUNDING: Retain 100% of the candidate's real companies, titles, technologies, and true duties. Do NOT invent new employers or claim experience they do not have.
|
||||||
|
2. GRAMMATICAL & STYLISTIC UPGRADE:
|
||||||
|
- Begin every bullet point with a high-impact, past-tense active verb (e.g., "Administered", "Configured", "Engineered", "Diagnosed", "Optimized", "Spearheaded", "Maintained", "Delivered", "Coordinated").
|
||||||
|
- Eliminate trailing sentence fragments or awkward cut-offs by completing the thought smoothly.
|
||||||
|
- Clarify scope, technical context, and impact.
|
||||||
|
3. OUTPUT FORMAT:
|
||||||
|
Return strictly valid JSON with this exact schema:
|
||||||
{
|
{
|
||||||
"profile": { "summary": "polished summary" },
|
"profile": {
|
||||||
|
"summary": "Polished, cohesive professional summary (2-3 sentences)"
|
||||||
|
},
|
||||||
"work": [
|
"work": [
|
||||||
{ "position": "...", "company": "...", "summary": "...", "highlights": ["polished bullet 1", "polished bullet 2"] }
|
{
|
||||||
|
"position": "Original or slightly standardized title",
|
||||||
|
"company": "Company name",
|
||||||
|
"summary": "Short role summary (or empty string)",
|
||||||
|
"highlights": [
|
||||||
|
"Polished, strong action bullet 1",
|
||||||
|
"Polished, strong action bullet 2"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), 12000);
|
const timeout = setTimeout(() => controller.abort(), 20000);
|
||||||
|
|
||||||
const res = await fetch(endpoint, {
|
const res = await fetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -114,13 +134,12 @@ CRITICAL INTEGRITY INSTRUCTIONS:
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model,
|
model,
|
||||||
temperature: 0.1,
|
temperature: 0.15,
|
||||||
max_tokens: 1800,
|
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: systemPrompt },
|
{ role: "system", content: systemPrompt },
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `Polish and improve the grammar/action-verbs of this resume while keeping ALL facts 100% true:\n\n${JSON.stringify(payload)}`,
|
content: `Enhance and polish this resume with strong action verbs and clean syntax while preserving authentic facts:\n\n${JSON.stringify(payload, null, 2)}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
response_format: { type: "json_object" },
|
response_format: { type: "json_object" },
|
||||||
|
|
@ -130,17 +149,25 @@ CRITICAL INTEGRITY INSTRUCTIONS:
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
console.warn("OpenRouter API returned non-200:", res.status, await res.text());
|
const errText = await res.text();
|
||||||
|
console.error("[Resume Optimizer] LLM API returned non-200:", res.status, errText);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
const rawContent = json.choices?.[0]?.message?.content;
|
let rawContent = json.choices?.[0]?.message?.content;
|
||||||
if (!rawContent) return null;
|
if (!rawContent) {
|
||||||
|
console.warn("[Resume Optimizer] No content returned in LLM choices");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.parse(rawContent);
|
rawContent = rawContent.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
||||||
|
|
||||||
|
const parsed = JSON.parse(rawContent);
|
||||||
|
console.log("[Resume Optimizer] Successfully enhanced resume via LLM");
|
||||||
|
return parsed;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.warn("OpenRouter enhancement skipped or timed out, using deterministic polish:", err.message);
|
console.error("[Resume Optimizer] LLM call failed or timed out:", err.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -291,12 +291,18 @@ export function ResumeEditor() {
|
||||||
if (result.data.profile) setProfile(result.data.profile);
|
if (result.data.profile) setProfile(result.data.profile);
|
||||||
if (result.data.work) setWork(result.data.work);
|
if (result.data.work) setWork(result.data.work);
|
||||||
if (result.data.skills) setSkills(result.data.skills);
|
if (result.data.skills) setSkills(result.data.skills);
|
||||||
setUploadMessage("Resume bullet points & summary enhanced with high-impact ATS action verbs!");
|
setUploadMessage("Resume bullet points & summary enhanced with high-impact ATS action verbs.");
|
||||||
setTimeout(() => setUploadMessage(""), 4000);
|
setTimeout(() => setUploadMessage(""), 5000);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const errJson = await res.json().catch(() => ({}));
|
||||||
|
setUploadMessage(errJson.error || "Optimization service unavailable. Check API credentials.");
|
||||||
|
setTimeout(() => setUploadMessage(""), 5000);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Optimize error", err);
|
console.error("Optimize error", err);
|
||||||
|
setUploadMessage("Network error during resume optimization.");
|
||||||
|
setTimeout(() => setUploadMessage(""), 5000);
|
||||||
} finally {
|
} finally {
|
||||||
setOptimizing(false);
|
setOptimizing(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue