diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 883b9f6..d88a348 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", "@uiw/react-json-view": "^2.0.0-alpha.31", + "jsonrepair": "^3.12.0", "mermaid": "^11.6.0", "mui-markdown": "^2.0.1", "prism-react-renderer": "^2.4.1", @@ -14170,6 +14171,14 @@ "node": ">=0.10.0" } }, + "node_modules/jsonrepair": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.12.0.tgz", + "integrity": "sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 768b763..eed0df5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", "@uiw/react-json-view": "^2.0.0-alpha.31", + "jsonrepair": "^3.12.0", "mermaid": "^11.6.0", "mui-markdown": "^2.0.1", "prism-react-renderer": "^2.4.1", diff --git a/frontend/src/ChatBubble.tsx b/frontend/src/ChatBubble.tsx index 12b56c1..0ac00a4 100644 --- a/frontend/src/ChatBubble.tsx +++ b/frontend/src/ChatBubble.tsx @@ -19,11 +19,12 @@ interface ChatBubbleProps { className?: string; title?: string; expanded?: boolean; + expandable?: boolean; onExpand?: () => void; } function ChatBubble(props: ChatBubbleProps) { - const { role, children, sx, className, title, onExpand }: ChatBubbleProps = props; + const { role, children, sx, className, title, onExpand, expandable }: ChatBubbleProps = props; const [expanded, setExpanded] = useState((props.expanded === undefined) ? true : props.expanded); const theme = useTheme(); @@ -48,15 +49,7 @@ function ChatBubble(props: ChatBubbleProps) { } } - const styles = { - 'user': { - ...defaultStyle, - backgroundColor: theme.palette.background.default, // Warm Gray (#D3CDBF) - border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre (#D4A017) - borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`, // Rounded, flat bottom-right for user - alignSelf: 'flex-end', // Right-aligned for user - color: theme.palette.primary.main, // Midnight Blue (#1A2536) for text - }, + const styles: any = { 'assistant': { ...defaultStyle, backgroundColor: theme.palette.primary.main, // Midnight Blue (#1A2536) @@ -64,52 +57,6 @@ function ChatBubble(props: ChatBubbleProps) { borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Rounded, flat bottom-left for assistant color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for text }, - 'system': { - ...defaultStyle, - backgroundColor: '#EDEAE0', // Soft warm gray that plays nice with #D3CDBF - border: `1px dashed ${theme.palette.custom.highlight}`, // Golden Ochre - borderRadius: defaultRadius, - maxWidth: '90%', - minWidth: '90%', - alignSelf: 'center', - color: theme.palette.text.primary, // Charcoal Black - fontStyle: 'italic', - }, - 'info': { - ...defaultStyle, - backgroundColor: '#BFD8D8', // Softened Dusty Teal - border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal - borderRadius: defaultRadius, - color: theme.palette.text.primary, // Charcoal Black (#2E2E2E) — much better contrast - opacity: 0.95, - }, - 'status': { - ...defaultStyle, - backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal - border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal - borderRadius: '4px', - maxWidth: '75%', - minWidth: '75%', - alignSelf: 'center', - color: theme.palette.secondary.dark, // Darker dusty teal for text - fontWeight: 500, // Slightly bolder than normal - fontSize: '0.95rem', // Slightly smaller - padding: '8px 12px', - opacity: 0.9, - transition: 'opacity 0.3s ease-in-out', // Smooth fade effect for appearing/disappearing - }, - 'error': { - ...defaultStyle, - backgroundColor: '#F8E7E7', // Soft light red background - border: `1px solid #D83A3A`, // Prominent red border - borderRadius: defaultRadius, - maxWidth: '90%', - minWidth: '90%', - alignSelf: 'center', - color: '#8B2525', // Deep red text for good contrast - padding: '10px 16px', - boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', // Subtle shadow with red tint - }, 'content': { ...defaultStyle, backgroundColor: '#F5F2EA', // Light cream background for easy reading @@ -124,31 +71,93 @@ function ChatBubble(props: ChatBubbleProps) { lineHeight: '1.3', // More compact line height fontFamily: theme.typography.fontFamily, // Consistent font with your theme }, - 'thinking': { - ...defaultStyle + 'error': { + ...defaultStyle, + backgroundColor: '#F8E7E7', // Soft light red background + border: `1px solid #D83A3A`, // Prominent red border + borderRadius: defaultRadius, + maxWidth: '90%', + minWidth: '90%', + alignSelf: 'center', + color: '#8B2525', // Deep red text for good contrast + padding: '10px 16px', + boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', // Subtle shadow with red tint }, - 'streaming': { - ...defaultStyle + 'fact-check': 'qualifications', + 'job-description': 'content', + 'job-requirements': 'qualifications', + 'info': { + ...defaultStyle, + backgroundColor: '#BFD8D8', // Softened Dusty Teal + border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal + borderRadius: defaultRadius, + color: theme.palette.text.primary, // Charcoal Black (#2E2E2E) — much better contrast + opacity: 0.95, }, - 'processing': { - ...defaultStyle + 'processing': "status", + 'qualifications': { + ...defaultStyle, + backgroundColor: theme.palette.primary.light, // Lighter shade, e.g., Soft Blue (#2A3B56) + border: `1px solid ${theme.palette.secondary.main}`, // Keep Dusty Teal (#4A7A7D) for contrast + borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Unchanged + color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for readable text + }, + 'resume': 'content', + 'searching': 'status', + 'status': { + ...defaultStyle, + backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal + border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal + borderRadius: '4px', + maxWidth: '75%', + minWidth: '75%', + alignSelf: 'center', + color: theme.palette.secondary.dark, // Darker dusty teal for text + fontWeight: 500, // Slightly bolder than normal + fontSize: '0.95rem', // Slightly smaller + padding: '8px 12px', + opacity: 0.9, + transition: 'opacity 0.3s ease-in-out', // Smooth fade effect for appearing/disappearing + }, + 'streaming': "assistant", + 'system': { + ...defaultStyle, + backgroundColor: '#EDEAE0', // Soft warm gray that plays nice with #D3CDBF + border: `1px dashed ${theme.palette.custom.highlight}`, // Golden Ochre + borderRadius: defaultRadius, + maxWidth: '90%', + minWidth: '90%', + alignSelf: 'center', + color: theme.palette.text.primary, // Charcoal Black + fontStyle: 'italic', + }, + 'thinking': "status", + 'user': { + ...defaultStyle, + backgroundColor: theme.palette.background.default, // Warm Gray (#D3CDBF) + border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre (#D4A017) + borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`, // Rounded, flat bottom-right for user + alignSelf: 'flex-end', // Right-aligned for user + color: theme.palette.primary.main, // Midnight Blue (#1A2536) for text }, }; - styles["thinking"] = styles["status"] - styles["streaming"] = styles["assistant"] - styles["processing"] = styles["status"] + for (const [key, value] of Object.entries(styles)) { + if (typeof (value) === "string") { + (styles as any)[key] = styles[value]; + } + } const icons: any = { - "searching": , - "thinking": , - // "streaming": , - "tooling": , - "processing": , "error": , "info": , + "processing": , + // "streaming": , + "searching": , + "thinking": , + "tooling": , }; - if (role === 'content' && title) { + if (expandable || (role === 'content' && title)) { return ( } slotProps={{ content: { sx: { fontWeight: 'bold', fontSize: '1.1rem', m: 0, p: 0, display: 'flex', justifyItems: 'center' } } }} > - {title} + {title || ""} {children} @@ -170,7 +179,7 @@ function ChatBubble(props: ChatBubbleProps) { } return ( - + {icons[role] !== undefined && icons[role]} {children} diff --git a/frontend/src/Document.tsx b/frontend/src/Document.tsx index 3cf8fed..6225d3f 100644 --- a/frontend/src/Document.tsx +++ b/frontend/src/Document.tsx @@ -6,6 +6,7 @@ interface DocumentProps { title: string; expanded?: boolean; filepath: string; + content?: string; setSnack: SetSnackType; submitQuery?: MessageSubmitQuery; connectionBase: string; @@ -14,13 +15,13 @@ interface DocumentProps { } const Document = (props: DocumentProps) => { - const { setSnack, submitQuery, connectionBase, filepath, title, expanded, disableCopy, onExpand } = props; + const { setSnack, submitQuery, connectionBase, filepath, content, title, expanded, disableCopy, onExpand } = props; const [document, setDocument] = useState(""); // Get the markdown useEffect(() => { - if (document !== "") { + if (document !== "" || !filepath) { return; } const fetchDocument = async () => { @@ -46,7 +47,6 @@ const Document = (props: DocumentProps) => { }, [document, setDocument, filepath]) return ( - <> { m: 0, flexGrow: 0, }, - message: { role: 'content', title: title, content: document }, + message: { role: 'content', title: title, content: document || content || "" }, connectionBase, submitQuery, setSnack, @@ -64,8 +64,6 @@ const Document = (props: DocumentProps) => { disableCopy, onExpand, }} /> - {/* */} - ); }; diff --git a/frontend/src/Mermaid.tsx b/frontend/src/Mermaid.tsx index 49376cd..ddfd2ab 100644 --- a/frontend/src/Mermaid.tsx +++ b/frontend/src/Mermaid.tsx @@ -35,7 +35,6 @@ const Mermaid: React.FC = (props: MermaidProps) => { const renderMermaid = async () => { if (containerRef.current && visible && chart) { try { - console.log("Rendering Mermaid"); await mermaid.initialize(mermaidConfig || defaultMermaidConfig); await mermaid.run({ nodes: [containerRef.current] }); } catch (e) { diff --git a/frontend/src/Message.tsx b/frontend/src/Message.tsx index ac3ad3a..edf26ac 100644 --- a/frontend/src/Message.tsx +++ b/frontend/src/Message.tsx @@ -29,7 +29,22 @@ import { SetSnackType } from './Snack'; import { CopyBubble } from './CopyBubble'; import { Scrollable } from './Scrollable'; -type MessageRoles = 'info' | 'user' | 'assistant' | 'system' | 'status' | 'error' | 'content' | 'thinking' | 'processing' | 'streaming'; +type MessageRoles = + 'assistant' | + 'content' | + 'error' | + 'fact-check' | + 'info' | + 'job-description' | + 'job-requirements' | + 'processing' | + 'qualifications' | + 'resume' | + 'status' | + 'streaming' | + 'system' | + 'thinking' | + 'user'; type MessageData = { role: MessageRoles, @@ -44,7 +59,9 @@ type MessageData = { id?: string, isProcessing?: boolean, actions?: string[], - metadata?: MessageMetaData + metadata?: MessageMetaData, + expanded?: boolean, + expandable?: boolean, }; interface MessageMetaData { @@ -73,7 +90,7 @@ type MessageList = MessageData[]; interface MessageProps { sx?: SxProps, message: MessageData, - expanded?: boolean, + // expanded?: boolean, // Provided as part of MessageData onExpand?: () => void, submitQuery?: MessageSubmitQuery, sessionId?: string, @@ -226,7 +243,6 @@ const MessageMeta = (props: MessageMetaProps) => { const Message = (props: MessageProps) => { const { message, submitQuery, sx, className, onExpand } = props; - const messageExpanded = props.expanded; const [expanded, setExpanded] = useState(false); const textFieldRef = useRef(null); @@ -248,9 +264,7 @@ const Message = (props: MessageProps) => { return ( = ({ if (messages === undefined || messages.length === 0) { return []; } - console.log("filterJobDescriptionMessages disabled", messages) - if (messages.length > 1) { + + if (messages.length > 2) { setHasResume(true); + setHasFacts(true); } - messages[0].role = 'content'; - messages[0].title = 'Job Description'; - messages[0].disableCopy = false; + if (messages.length > 0) { + messages[0].role = 'content'; + messages[0].title = 'Job Description'; + messages[0].disableCopy = false; + messages[0].expandable = true; + } - return messages; + if (messages.length > 3) { + // messages[2] is Show job requirements + messages[3].role = 'job-requirements'; + messages[3].title = 'Job Requirements'; + messages[3].disableCopy = false; + messages[3].expanded = true; + messages[3].expandable = true; + } - // let reduced = messages.filter((m, i) => { - // const keep = (m.metadata?.origin || m.origin || "no origin") === 'job_description'; - // if ((m.metadata?.origin || m.origin || "no origin") === 'resume') { - // setHasResume(true); - // } - // // if (!keep) { - // // console.log(`filterJobDescriptionMessages: ${i + 1} filtered:`, m); - // // } else { - // // console.log(`filterJobDescriptionMessages: ${i + 1}:`, m); - // // } + /* Filter out the 2nd and 3rd (0-based) */ + const filtered = messages.filter((m, i) => i !== 1 && i !== 2); - // return keep; - // }); - - // /* If Resume hasn't occurred yet and there is still more than one message, - // * resume has been generated. */ - // if (!hasResume && reduced.length > 1) { - // setHasResume(true); - // } - - // if (reduced.length > 0) { - // // First message is always 'content' - // reduced[0].title = 'Job Description'; - // reduced[0].role = 'content'; - // setHasJobDescription(true); - // } - - // /* Filter out any messages which the server injected for state management */ - // reduced = reduced.filter(m => m.display !== "hide"); - - // return reduced; - }, [setHasResume/*, setHasJobDescription, hasResume*/]); + return filtered; + }, [setHasResume, setHasFacts]); const filterResumeMessages = useCallback((messages: MessageList): MessageList => { if (messages === undefined || messages.length === 0) { return []; } - console.log("filterResumeMessages disabled") - if (messages.length > 3) { - setHasFacts(true); + + if (messages.length > 1) { + // messages[0] is Show Qualifications + messages[1].role = 'qualifications'; + messages[1].title = 'Candidate qualifications'; + messages[1].disableCopy = false; + messages[1].expanded = false; + messages[1].expandable = true; } - return messages; - // let reduced = messages.filter((m, i) => { - // const keep = (m.metadata?.origin || m.origin || "no origin") === 'resume'; - // if ((m.metadata?.origin || m.origin || "no origin") === 'fact_check') { - // setHasFacts(true); - // } - // if (!keep) { - // console.log(`filterResumeMessages: ${i + 1} filtered:`, m); - // } else { - // console.log(`filterResumeMessages: ${i + 1}:`, m); - // } - // return keep; - // }); + if (messages.length > 3) { + // messages[2] is Show Resume + messages[3].role = 'resume'; + messages[3].title = 'Generated Resume'; + messages[3].disableCopy = false; + messages[3].expanded = true; + messages[3].expandable = true; + } - // /* If there is more than one message, it is user: "...JOB_DESCRIPTION...", assistant: "...RESUME..." - // * which means a resume has been generated. */ - // if (reduced.length > 1) { - // /* Remove the assistant message from the UI */ - // if (reduced[0].role === "user") { - // reduced.splice(0, 1); - // } - // } + /* Filter out the 1st and 3rd messages (0-based) */ + const filtered = messages.filter((m, i) => i !== 0 && i !== 2); - // /* If Fact Check hasn't occurred yet and there is still more than one message, - // * facts have have been generated. */ - // if (!hasFacts && reduced.length > 1) { - // setHasFacts(true); - // } - - // /* Filter out any messages which the server injected for state management */ - // reduced = reduced.filter(m => m.display !== "hide"); - - // /* If there are any messages, there is a resume */ - // if (reduced.length > 0) { - // // First message is always 'content' - // reduced[0].title = 'Resume'; - // reduced[0].role = 'content'; - // setHasResume(true); - // } - - // return reduced; - }, [/*setHasResume, hasFacts,*/ setHasFacts]); + return filtered; + }, []); const filterFactsMessages = useCallback((messages: MessageList): MessageList => { if (messages === undefined || messages.length === 0) { return []; } - console.log("filterFactsMessages disabled") - return messages; - // messages.forEach((m, i) => console.log(`filterFactsMessages: ${i + 1}:`, m)) + if (messages.length > 1) { + // messages[0] is Show verification + messages[1].role = 'fact-check'; + messages[1].title = 'Fact Check'; + messages[1].disableCopy = false; + messages[1].expanded = false; + messages[1].expandable = true; + } - // const reduced = messages.filter(m => { - // return (m.metadata?.origin || m.origin || "no origin") === 'fact_check'; - // }); + /* Filter out the 1st (0-based) */ + const filtered = messages.filter((m, i) => i !== 0); - // /* If there is more than one message, it is user: "Fact check this resume...", assistant: "...FACT CHECK..." - // * which means facts have been generated. */ - // if (reduced.length > 1) { - // /* Remove the user message from the UI */ - // if (reduced[0].role === "user") { - // reduced.splice(0, 1); - // } - // // First message is always 'content' - // reduced[0].title = 'Fact Check'; - // reduced[0].role = 'content'; - // setHasFacts(true); - // } - - // return reduced; - }, [/*setHasFacts*/]); + return filtered; + }, []); const jobResponse = useCallback(async (message: MessageData) => { console.log('onJobResponse', message); + if (message.actions && message.actions.includes("job_description")) { + await jobConversationRef.current.fetchHistory(); + } if (message.actions && message.actions.includes("resume_generated")) { await resumeConversationRef.current.fetchHistory(); setHasResume(true); @@ -238,12 +195,29 @@ const ResumeBuilder: React.FC = ({ , ]; + const jobDescriptionPreamble: MessageList = [{ + role: 'info', + content: `Once you paste a job description and press **Generate Resume**, the system will perform the following actions: + +1. **RAG**: Collects information from the RAG database relavent to the job description +2. **Isolated Analysis**: Three sub-stages + 1. **Job Analysis**: Extracts requirements from job description only + 2. **Candidate Analysis**: Catalogs qualifications from resume/context only + 3. **Mapping Analysis**: Identifies legitimate matches between requirements and qualifications +3. **Resume Generation**: Uses mapping output to create a tailored resume with evidence-based content +4. **Verification**: Performs fact-checking to catch any remaining fabrications + 1. **Re-generation**: If verification does not pass, a second attempt is made to correct any issues` + }]; + + if (!hasJobDescription) { return = ({ }, [connectionBase, sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts]); return ( - = (props: StyledMarkdownProp return ; } if (className === "lang-json") { - return - { - if (typeof (children) === "string" && children.match("\n")) { - return
{children}
- } - }} - /> -
; + try { + const fixed = jsonrepair(content); + return + + { + if (typeof (children) === "string" && children.match("\n")) { + return
{children}
+ } + }} + /> +
+
+ } catch (e) { + console.log("jsonrepair error", e); + }; } return
{element.children}
; }, diff --git a/frontend/src/VectorVisualizer.tsx b/frontend/src/VectorVisualizer.tsx index 2a778de..99cb83e 100644 --- a/frontend/src/VectorVisualizer.tsx +++ b/frontend/src/VectorVisualizer.tsx @@ -18,7 +18,6 @@ import { Scrollable } from './Scrollable'; import { StyledMarkdown } from './StyledMarkdown'; import './VectorVisualizer.css'; -import { calculatePoint } from 'mermaid/dist/utils'; interface Metadata { doc_type?: string; diff --git a/src/utils/agents/job_description.py b/src/utils/agents/job_description.py index 966b684..430fc11 100644 --- a/src/utils/agents/job_description.py +++ b/src/utils/agents/job_description.py @@ -191,16 +191,25 @@ class JobDescription(Agent): if message.status == "error": return + # Add the "Job requirements" message + if "generate_factual_tailored_resume" in message.metadata and "job_requirements" in message.metadata["generate_factual_tailored_resume"]: + new_message = Message(prompt="Show job requirements") + job_requirements = message.metadata["generate_factual_tailored_resume"]["job_requirements"]["results"] + new_message.response = f"```json\n\n{json.dumps(job_requirements, indent=2)}\n```\n" + new_message.status = "done" + self.conversation.add(new_message) + self.system_prompt = system_user_qualifications resume_agent = self.context.get_agent(agent_type="resume") fact_check_agent = self.context.get_agent(agent_type="fact_check") if not resume_agent: + # Add the "Generated Resume" message if "generate_factual_tailored_resume" in message.metadata and "analyze_candidate_qualifications" in message.metadata["generate_factual_tailored_resume"]: resume_agent = self.context.get_or_create_agent(agent_type="resume", resume=message.response) resume_message = Message(prompt="Show candidate qualifications") qualifications = message.metadata["generate_factual_tailored_resume"]["analyze_candidate_qualifications"]["results"] - resume_message.response = f"# Candidate qualifications\n\n```json\n\n{json.dumps(qualifications, indent=2)}\n```\n" + resume_message.response = f"```json\n\n{json.dumps(qualifications, indent=2)}\n```\n" resume_message.status = "done" resume_agent.conversation.add(resume_message) @@ -212,6 +221,8 @@ class JobDescription(Agent): message.response = "Resume generated." message.actions.append("resume_generated") + + # Add the "Fact Check" message if "generate_factual_tailored_resume" in message.metadata and "verify_resume" in message.metadata["generate_factual_tailored_resume"]: if "second_pass" in message.metadata["generate_factual_tailored_resume"]["verify_resume"]: verification = message.metadata["generate_factual_tailored_resume"]["verify_resume"]["second_pass"]["results"] @@ -221,7 +232,7 @@ class JobDescription(Agent): fact_check_agent = self.context.get_or_create_agent(agent_type="fact_check", facts=json.dumps(verification, indent=2)) fact_check_message = message.model_copy() fact_check_message.prompt = "Show verification" - fact_check_message.response = f"# Resume verfication\n\n```json\n\n{json.dumps(verification, indent=2)}\n```\n" + fact_check_message.response = f"```json\n\n{json.dumps(verification, indent=2)}\n```\n" fact_check_message.status = "done" fact_check_agent.conversation.add(fact_check_message) @@ -868,45 +879,78 @@ class JobDescription(Agent): async def correct_resume_issues(self, message: Message, generated_resume: str, verification_results: Dict, skills_mapping: Dict, candidate_qualifications: Dict, original_header: str, metadata: Dict[str, Any]) -> AsyncGenerator[Message, None]: """Correct issues in the resume based on verification results.""" if verification_results["verification_results"]["overall_assessment"] == "APPROVED": - message.status = "done" - message.status = generated_resume - yield message - return + message.status = "done" + message.status = generated_resume + yield message + return - system_prompt = """ - You are a professional resume editor with a focus on factual accuracy. Your task is to correct - the identified issues in a tailored resume according to the verification report. - - ## INSTRUCTIONS: - - 1. Make ONLY the changes specified in the verification report - 2. Ensure all corrections maintain factual accuracy based on the skills mapping - 3. Do not introduce any new claims or skills not present in the verification data - 4. Maintain the original format and structure of the resume - 5. DO NOT directly list the verification report or skills mapping - 6. Provide ONLY the fully corrected resume - 7. DO NOT provide Verification Results or other additional information beyond the corrected resume - - ## PROCESS: - - 1. For each issue in the verification report: - - Identify the problematic text in the resume - - Replace it with the suggested correction - - Ensure the correction is consistent with the rest of the resume - - 2. After making all corrections: - - Review the revised resume for consistency - - Ensure no factual inaccuracies have been introduced - - Check that all formatting remains professional - - Return the fully corrected resume. - """ - - prompt = f"Original Resume:\n{generated_resume}\n\n" - prompt += f"Verification Results:\n{json.dumps(verification_results, indent=2)}\n\n" - prompt += f"Skills Mapping:\n{json.dumps(skills_mapping, indent=2)}\n\n" - prompt += f"Candidate Qualifications:\n{json.dumps(candidate_qualifications, indent=2)}\n\n" - prompt += f"Original Resume Header:\n{original_header}" + system_prompt = """\ +You are a professional resume editor with a focus on factual accuracy. Your task is to correct identified issues in a tailored resume according to the verification report. + +## REFERENCE DATA: +The following sections contain reference information for you to use when making corrections. This information should NOT be included in your output resume: + +1. Original Resume - The resume you will correct +2. Verification Results - Issues that need correction +3. Skills Mapping - How candidate skills align with job requirements +4. Candidate Qualifications - Verified information about the candidate's background +5. Original Resume Header - The formatting of the resume header + +## INSTRUCTIONS: + +1. Make ONLY the changes specified in the verification report +2. Ensure all corrections maintain factual accuracy based on the skills mapping +3. Do not introduce any new claims or skills not present in the verification data +4. Maintain the original format and structure of the resume +5. Provide ONLY the fully corrected resume as your output +6. DO NOT include any of the reference data sections in your output +7. DO NOT include any additional comments, explanations, or notes in your output + +## PROCESS: + +1. For each issue in the verification report: + - Identify the problematic text in the resume + - Replace it with the suggested correction + - Ensure the correction is consistent with the rest of the resume + +2. After making all corrections: + - Review the revised resume for consistency + - Ensure no factual inaccuracies have been introduced + - Check that all formatting remains professional + +Your output should contain ONLY the corrected resume text with no additional explanations or context. +""" + prompt = """ +## REFERENCE DATA + +### Original Resume: +""" + prompt += generated_resume + prompt += """ + +### Verification Results: +""" + prompt += json.dumps(verification_results, indent=2) + prompt += """ + +### Skills Mapping: +""" + prompt += json.dumps(skills_mapping, indent=2) + prompt += """ + +### Candidate Qualifications: +""" + prompt += json.dumps(candidate_qualifications, indent=2) + prompt += """ + +### Original Resume Header: +""" + prompt += generated_resume + prompt += """ + +## TASK +Based on the reference data above, please create a corrected version of the resume that addresses all issues identified in the verification report. Return ONLY the corrected resume. +""" metadata["system_prompt"] = system_prompt metadata["prompt"] = prompt