How I Stopped My CV Tool From Lying to Users
The hallucination problem in AI CV tools isn't a prompt problem. It's an architecture problem.
Abdulyekeen Maadan
April 20, 2026 · Updated May 19, 2026 · 6 min read
When I first built NextRole NG, the AI was confidently inserting CRM experience, Next.js, and Ionic into CVs of candidates who had never touched those tools. The output looked impressive. It was fiction.
A candidate who submitted that CV to GTBank would get caught in the background check and blacklisted. The tool wasn't just unhelpful — it was actively harmful.
Every fix I tried at the prompt level made things marginally better and then worse in a different way. It took me a while to realise I was solving the wrong problem. The hallucination wasn't coming from bad prompts. It was coming from bad architecture.
The Root Cause Nobody Talks About
Most AI CV tools make one LLM call. They pass in the CV, pass in the job description, and ask the model to produce an optimized CV. The problem is that this puts the model in two incompatible modes simultaneously — extraction mode and generation mode — inside the same call.
In extraction mode, the model should be conservative and faithful. It should only report what is actually in the document. In generation mode, the model is rewarded for producing impressive, relevant output. When you combine these in one call, generation mode wins. The model starts with what's in the CV and then drifts toward what would make the output look good.
This is not a prompt engineering failure. You cannot write a system instruction that fully overrides this dynamic because the tension is structural. The model is optimizing for output quality within the same call where it's supposed to be reporting facts. Asking it to "never invent skills" while simultaneously asking it to "rewrite these bullets to be more impressive" is asking it to hold two opposing objectives at the same time.
The fix is to separate these modes architecturally.
The Two-Phase Solution
Phase 1 has one job: extract verified facts. Nothing else. It receives the CV — either as raw text or as a PDF passed directly to Gemini's multimodal API — and returns a structured JSON object I call CVFacts. This contains the candidate's roles with their original bullets, tools they actually used, achievements with real numbers, education with actual grades, and projects with real links.
The Phase 1 prompt has no generative instruction in it at all. It is purely extractive. The model is not asked to improve anything, reframe anything, or make anything sound better. It is asked to report what is there.
Phase 2 receives CVFacts as its ground truth. It never sees the original CV. It can only work with what Phase 1 extracted. The prompt is explicit: you may combine similar responsibilities, reframe tasks into achievements, infer impact without inventing numbers, and prioritize tools based on job relevance. You may not create fake companies, create fake roles, or introduce tools not present in CVFacts.
This architectural separation does something a single-call system cannot: it forces the model to commit to facts before it enters generation mode. By the time Phase 2 starts rewriting, the facts are locked. The model cannot drift toward impressive fiction because the fiction has no entry point — it would have to contradict the structured data it received as input, which models are strongly disinclined to do.
Architecture Overview
CVFactsCVFacts onlyThe Layered Constraint System
Architecture alone is not enough. Phase 2 still has generative freedom within the facts it receives, and that freedom can produce subtle distortions — injecting a tool from one role into another role's bullets, pattern-matching to a training example instead of the candidate's actual experience, softening honest assessments into corporate-friendly vagueness.
Each of these failure modes got its own constraint layer:
Cross-role tool contamination
The Phase 2 prompt receives roles as a structured block — title, company, duration, and original bullets per role — not as a flat list. An explicit rule states that a tool may only appear in a bullet for the role where it appeared in the original content. A candidate who used Power BI at Cowrywise and Excel in a freelance role cannot have Power BI appear in their freelance bullets.
Absent keyword injection
When a job description mentions Tableau and the candidate has never used Tableau, the naive approach is to stuff Tableau into bullets anyway. My prompt reframes missing keywords not as targets but as hard exclusions: these skills are absent from the candidate's CV — do not introduce them under any circumstance. If the JD requires them and they are genuinely absent, reflect that in the match score and weak areas only.
Strong bullet preservation
If a bullet already follows APR structure (Action-Process-Result) with a real metric, the model is instructed to enhance it minimally — not simplify it, not split it, not remove the number. The goal is improvement, not replacement.
Honest alignment scoring
If the model returns alignment: weak but recommended_action: proceed, a post-generation verification layer catches the contradiction and corrects it programmatically before the response reaches the user. The AI doesn't get the final word — code does.
What This Produces
The result is a system where the AI operates as a constrained editor rather than a creative writer. It cannot invent. It can only reframe, prioritize, and sharpen what is already there.
This matters beyond the technical implementation. The trust model for a CV tool is different from most AI products. If a writing assistant invents a sentence, the user reads it and decides whether it sounds right. If a CV tool invents a skill, the user submits it to an employer, gets caught, and gets blacklisted in an industry. The cost of hallucination is not a bad paragraph — it is a real person's career.
The constraint system is not just good engineering. It is the product's ethical foundation. No fake experience is a hard rule, not a guideline, and the architecture enforces it at every layer.
I built NextRole NG in roughly two weeks after completing a 30-day Go bootcamp. The hallucination problem took longer to solve than the entire rest of the pipeline. That felt disproportionate until I understood why — because the hallucination problem is not a technical problem. It is a product integrity problem that happens to have a technical solution.
If you are building anything where AI output has real consequences for real people, the question worth asking is not "how do I prompt this better." It is "what does the architecture need to look like so the model cannot lie even if it wanted to."
"The room you’re in is already deciding how far you go."
Start a Conversation