This commit is contained in:
2026-07-27 00:56:18 +03:30
commit 1c75afe0fd
244 changed files with 27710 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Protocol, TypeVar
from pydantic import BaseModel
from resume_agent.models import CareerProfile, TailoringPackage
from resume_agent.prompts import (
AUDIT_PROMPT,
PROFILE_PROMPT,
PROFILE_REPAIR_PROMPT,
REVISION_PROMPT,
TAILOR_PROMPT,
)
T = TypeVar("T", bound=BaseModel)
class StructuredLLM(Protocol):
def parse(self, schema: type[T], instructions: str, input_text: str) -> T: ...
def build_profile(llm: StructuredLLM, resume_text: str, about: str = "") -> CareerProfile:
source = {
"resume": resume_text,
"candidate_notes": about,
}
profile = llm.parse(
CareerProfile,
PROFILE_PROMPT,
json.dumps(source, ensure_ascii=False),
)
try:
validate_profile(profile)
except ValueError as exc:
if profile.facts:
raise
repair_source = {
**source,
"previous_validation_error": str(exc),
"previous_result": profile.model_dump(mode="json"),
}
profile = llm.parse(
CareerProfile,
PROFILE_REPAIR_PROMPT,
json.dumps(repair_source, ensure_ascii=False),
)
try:
validate_profile(profile)
except ValueError as repair_exc:
if not profile.facts:
raise ValueError(
"The LLM could not extract evidence facts from "
f"{len(resume_text):,} characters of resume text after two attempts. "
"Try another model with reliable structured-output support, or simplify "
"the resume Markdown."
) from repair_exc
raise
return profile
def tailor_resume(
llm: StructuredLLM,
profile: CareerProfile,
job_text: str,
tailoring_strength: int = 50,
) -> TailoringPackage:
if not 0 <= tailoring_strength <= 100:
raise ValueError("Tailoring strength must be between 0 and 100.")
payload = {
"canonical_profile": profile.model_dump(mode="json"),
"job_post": job_text,
"tailoring_strength": tailoring_strength,
}
draft = llm.parse(
TailoringPackage,
_tailoring_prompt(tailoring_strength),
json.dumps(payload, ensure_ascii=False),
)
validate_package(profile, draft)
audit_payload = {
"canonical_profile": profile.model_dump(mode="json"),
"proposed_package": draft.model_dump(mode="json"),
"tailoring_strength": tailoring_strength,
}
audited = llm.parse(
TailoringPackage,
AUDIT_PROMPT,
json.dumps(audit_payload, ensure_ascii=False),
)
validate_package(profile, audited)
return audited
def _tailoring_prompt(strength: int) -> str:
if strength <= 20:
guidance = (
"Stay very close to the source wording and organization. Make only small "
"relevance edits and prefer concise, directly quoted evidence."
)
elif strength <= 70:
guidance = (
"Reorder and rewrite supported facts for clear job relevance while retaining "
"the candidate's original meaning and normal resume length."
)
else:
guidance = (
"Maximize truthful job alignment. Use the fullest relevant detail available "
"in the evidence, stronger active phrasing, and job-post terminology only "
"where directly supported. Include more supported bullets when useful."
)
return (
f"{TAILOR_PROMPT}\n\n"
f"Tailoring strength: {strength}/100.\n"
f"{guidance}\n"
"This setting changes editing intensity only. It never permits fabricated, "
"exaggerated, inferred, or unsupported claims."
)
def revise_tailored_resume(
llm: StructuredLLM,
profile: CareerProfile,
current: TailoringPackage,
instruction: str,
) -> TailoringPackage:
instruction = instruction.strip()
if len(instruction) < 3:
raise ValueError("Describe how you want the tailored resume revised.")
revision_payload = {
"canonical_profile": profile.model_dump(mode="json"),
"current_tailored_package": current.model_dump(mode="json"),
"candidate_request": instruction,
}
revised = llm.parse(
TailoringPackage,
REVISION_PROMPT,
json.dumps(revision_payload, ensure_ascii=False),
)
_validate_revision(profile, current, revised)
audit_payload = {
"canonical_profile": profile.model_dump(mode="json"),
"original_job_analysis": current.job.model_dump(mode="json"),
"candidate_request": instruction,
"proposed_package": revised.model_dump(mode="json"),
}
audited = llm.parse(
TailoringPackage,
AUDIT_PROMPT,
json.dumps(audit_payload, ensure_ascii=False),
)
_validate_revision(profile, current, audited)
return audited
def _validate_revision(
profile: CareerProfile,
current: TailoringPackage,
revised: TailoringPackage,
) -> None:
validate_package(profile, revised)
if revised.job != current.job:
raise ValueError("A resume revision cannot change the original job analysis.")
def validate_profile(profile: CareerProfile) -> None:
ids = [fact.id for fact in profile.facts]
if not ids:
raise ValueError("The profile contains no evidence facts.")
if len(ids) != len(set(ids)):
raise ValueError("The profile contains duplicate evidence IDs.")
if any(not fact.source_excerpt.strip() for fact in profile.facts):
raise ValueError("Every profile fact must include a source excerpt.")
def validate_package(profile: CareerProfile, package: TailoringPackage) -> None:
valid_ids = {fact.id for fact in profile.facts}
backed_items = list(package.resume.summary)
for section in package.resume.sections:
backed_items.extend(section.items)
if not backed_items:
raise ValueError("The tailored resume contains no evidence-backed content.")
for item in backed_items:
if not item.evidence_ids:
raise ValueError(f"Resume claim has no evidence: {item.text}")
unknown = set(item.evidence_ids) - valid_ids
if unknown:
raise ValueError(
f"Resume claim references unknown evidence IDs: {', '.join(sorted(unknown))}"
)
if package.resume.contact != profile.contact:
raise ValueError("The tailored resume changed the candidate's contact information.")
def save_json(model: BaseModel, path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(model.model_dump_json(indent=2), encoding="utf-8")
def load_profile(path: Path) -> CareerProfile:
return CareerProfile.model_validate_json(path.read_text(encoding="utf-8"))