generated from kgod/ai-review-template
feat: initialize resume agent with OfferPai sync
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
"""Light entry expansion: pure LLM expander, fallback composition, and factory.
|
||||
|
||||
The RAG knowledge base was removed (it only ever served the deep-optimization track).
|
||||
Expansion is the model rewriting the user's own confirmed facts; every candidate still
|
||||
passes through claim validation so unconfirmed additions never silently enter a resume.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .claim_validator import partition_entry_text, quantified_fact_contexts
|
||||
from .entry_expander import EntryExpander, RuleBasedEntryExpander
|
||||
from .experience_optimizer import (
|
||||
_fact_text_is_preserved,
|
||||
normalize_fact_ledger,
|
||||
required_material_fact_ids,
|
||||
)
|
||||
from .llm_services import (
|
||||
LLMServiceError,
|
||||
OpenAICompatibleStructuredClient,
|
||||
StrictSchema,
|
||||
log_ai_event,
|
||||
)
|
||||
from .resume_expansion_prompts import (
|
||||
_EDUCATION_PROMPT,
|
||||
_EXPANSION_REPAIR_PROMPT,
|
||||
_repair_prompt,
|
||||
_system_prompt,
|
||||
)
|
||||
from .settings import Settings
|
||||
|
||||
__all__ = [
|
||||
"EntryExpansionOutput",
|
||||
"OpenAIEntryExpander",
|
||||
"FallbackEntryExpander",
|
||||
"build_expander",
|
||||
"_EDUCATION_PROMPT",
|
||||
"_EXPANSION_REPAIR_PROMPT",
|
||||
"_system_prompt",
|
||||
"_entry_fact_ledger",
|
||||
"_entry_facts",
|
||||
]
|
||||
|
||||
|
||||
class EntryExpansionOutput(StrictSchema):
|
||||
optimized_description: str
|
||||
changes: list[str] = Field(max_length=5)
|
||||
exemplar_titles: list[str] = Field(max_length=3)
|
||||
|
||||
|
||||
class OpenAIEntryExpander:
|
||||
"""LLM expander over user-confirmed facts only (no retrieval)."""
|
||||
|
||||
def __init__(self, completion: Any) -> None:
|
||||
self.completion = completion
|
||||
|
||||
def expand(self, entry: dict[str, Any], *, context: dict[str, Any]) -> dict[str, Any]:
|
||||
facts_text = _entry_facts(entry)
|
||||
fact_ledger = _entry_fact_ledger(entry)
|
||||
entry_type = str(context.get("entry_type") or "")
|
||||
primary_description = str(entry.get("description") or "").strip()
|
||||
output: EntryExpansionOutput = self.completion.complete(
|
||||
schema=EntryExpansionOutput,
|
||||
schema_name="entry_expansion",
|
||||
system_prompt=_system_prompt(entry_type),
|
||||
payload={
|
||||
"entry_facts": facts_text,
|
||||
"primary_description": primary_description,
|
||||
"entry_type": entry_type or None,
|
||||
"target_position": context.get("target_position"),
|
||||
"instruction": context.get("instruction"),
|
||||
"protected_quantity_facts": quantified_fact_contexts(facts_text),
|
||||
},
|
||||
)
|
||||
|
||||
candidate = output.optimized_description.strip()
|
||||
repair_reason: str | None = None
|
||||
if not candidate and primary_description:
|
||||
repair_reason = "empty_result"
|
||||
log_ai_event(
|
||||
"entry_expansion_repair_started",
|
||||
entry_type=entry_type,
|
||||
reason_code=repair_reason,
|
||||
)
|
||||
repaired: EntryExpansionOutput = self.completion.complete(
|
||||
schema=EntryExpansionOutput,
|
||||
schema_name="entry_expansion_repair",
|
||||
system_prompt=_repair_prompt(entry_type),
|
||||
payload={
|
||||
"entry_facts": facts_text,
|
||||
"primary_description": primary_description,
|
||||
"entry_type": entry_type or None,
|
||||
"target_position": context.get("target_position"),
|
||||
"instruction": context.get("instruction"),
|
||||
"protected_quantity_facts": quantified_fact_contexts(facts_text),
|
||||
"rejected_candidate": "",
|
||||
"rejected_reason": repair_reason,
|
||||
},
|
||||
)
|
||||
output = repaired
|
||||
candidate = repaired.optimized_description.strip()
|
||||
|
||||
optimized, suggestions, warnings = partition_entry_text(candidate, fact_ledger)
|
||||
if not optimized and primary_description:
|
||||
# A model result composed only of unconfirmed additions must not become a failed
|
||||
# card operation. Preserve the user's confirmed text and surface the additions.
|
||||
optimized = primary_description
|
||||
warnings.append("candidate_contains_unconfirmed_additions")
|
||||
if optimized:
|
||||
missing = _missing_material_facts(fact_ledger, optimized)
|
||||
if missing:
|
||||
optimized, extra_suggestions, extra_warnings = self._repair_material_omissions(
|
||||
optimized,
|
||||
missing,
|
||||
fact_ledger,
|
||||
facts_text=facts_text,
|
||||
primary_description=primary_description,
|
||||
entry_type=entry_type,
|
||||
context=context,
|
||||
)
|
||||
suggestions.extend(extra_suggestions)
|
||||
warnings.extend(extra_warnings)
|
||||
if not optimized:
|
||||
fallback_reason = "repair_failed" if repair_reason else "insufficient_facts"
|
||||
log_ai_event(
|
||||
"entry_expansion_rejected",
|
||||
level=logging.WARNING,
|
||||
entry_type=entry_type,
|
||||
reason_code=fallback_reason,
|
||||
)
|
||||
return {
|
||||
"optimized_description": "",
|
||||
"changes": [],
|
||||
"unconfirmed_suggestions": suggestions,
|
||||
"validation_warnings": list(dict.fromkeys(warnings)),
|
||||
"source": "ai_expanded",
|
||||
"generation_source": "llm",
|
||||
"fallback_reason": fallback_reason,
|
||||
}
|
||||
|
||||
return {
|
||||
"optimized_description": optimized,
|
||||
"changes": [item.strip() for item in output.changes if item.strip()][:5],
|
||||
"unconfirmed_suggestions": suggestions[:6],
|
||||
"validation_warnings": list(dict.fromkeys(warnings)),
|
||||
"source": "ai_expanded",
|
||||
"generation_source": "llm",
|
||||
}
|
||||
|
||||
|
||||
def _repair_material_omissions(
|
||||
self,
|
||||
optimized: str,
|
||||
missing: list[str],
|
||||
fact_ledger: list[dict[str, str]],
|
||||
*,
|
||||
facts_text: str,
|
||||
primary_description: str,
|
||||
entry_type: str,
|
||||
context: dict[str, Any],
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
"""One repair pass for candidates that dropped confirmed material facts.
|
||||
|
||||
Feature lists, product intros, and outcomes must not vanish while the
|
||||
tech stack survives. The pre-repair candidate is kept when the repair
|
||||
call fails or partitions to nothing: an omission never vetoes the draft.
|
||||
"""
|
||||
try:
|
||||
repaired: EntryExpansionOutput = self.completion.complete(
|
||||
schema=EntryExpansionOutput,
|
||||
schema_name="entry_expansion_repair",
|
||||
system_prompt=_repair_prompt(entry_type),
|
||||
payload={
|
||||
"entry_facts": facts_text,
|
||||
"primary_description": primary_description,
|
||||
"entry_type": entry_type or None,
|
||||
"target_position": context.get("target_position"),
|
||||
"instruction": context.get("instruction"),
|
||||
"protected_quantity_facts": quantified_fact_contexts(facts_text),
|
||||
"rejected_candidate": optimized,
|
||||
"rejected_reason": "material_fact_omitted",
|
||||
"omitted_facts": missing,
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
log_ai_event(
|
||||
"entry_expansion_coverage_repair_failed",
|
||||
level=logging.WARNING,
|
||||
entry_type=entry_type,
|
||||
reason_code=getattr(exc, "reason_code", type(exc).__name__),
|
||||
)
|
||||
return optimized, [], ["material_fact_omitted"]
|
||||
repaired_text, extra_suggestions, _ = partition_entry_text(
|
||||
repaired.optimized_description.strip(), fact_ledger
|
||||
)
|
||||
if not repaired_text:
|
||||
return optimized, [], ["material_fact_omitted"]
|
||||
if _missing_material_facts(fact_ledger, repaired_text):
|
||||
return repaired_text, extra_suggestions, ["material_fact_omitted_after_repair"]
|
||||
return repaired_text, extra_suggestions, []
|
||||
|
||||
|
||||
def _missing_material_facts(facts: list[dict[str, str]], narrative: str) -> list[str]:
|
||||
ledger = normalize_fact_ledger(facts)
|
||||
required = set(required_material_fact_ids(ledger))
|
||||
return [
|
||||
fact["text"]
|
||||
for fact in ledger
|
||||
if fact["id"] in required and not _fact_text_is_preserved(fact["id"], ledger, narrative)
|
||||
]
|
||||
|
||||
|
||||
class FallbackEntryExpander:
|
||||
def __init__(self, primary: EntryExpander, fallback: EntryExpander) -> None:
|
||||
self.primary = primary
|
||||
self.fallback = fallback
|
||||
|
||||
def expand(self, entry: dict[str, Any], *, context: dict[str, Any]) -> dict[str, Any]:
|
||||
try:
|
||||
return self.primary.expand(entry, context=context)
|
||||
except Exception as exc:
|
||||
reason = exc.reason_code if isinstance(exc, LLMServiceError) else type(exc).__name__.lower()[:48]
|
||||
log_ai_event(
|
||||
"entry_expansion_failed",
|
||||
level=logging.ERROR,
|
||||
entry_type=str(context.get("entry_type") or ""),
|
||||
reason_code=reason,
|
||||
trace_id=getattr(exc, "trace_id", None),
|
||||
exception=type(exc).__name__,
|
||||
)
|
||||
fallback = self.fallback.expand(entry, context=context)
|
||||
optimized = str(fallback.get("optimized_description") or "").strip()
|
||||
if optimized:
|
||||
return {
|
||||
**fallback,
|
||||
"source": str(fallback.get("source") or "rule_polish"),
|
||||
"generation_source": "rule_fallback",
|
||||
"fallback_reason": reason,
|
||||
}
|
||||
return {
|
||||
"optimized_description": "",
|
||||
"changes": [],
|
||||
"unconfirmed_suggestions": [],
|
||||
"source": "rule_polish",
|
||||
"generation_source": "unavailable",
|
||||
"fallback_reason": reason,
|
||||
}
|
||||
|
||||
|
||||
def _entry_facts(entry: dict[str, Any]) -> str:
|
||||
return "\n".join(item["text"] for item in _entry_fact_ledger(entry))
|
||||
|
||||
|
||||
def _entry_fact_ledger(entry: dict[str, Any]) -> list[dict[str, str]]:
|
||||
keys = (
|
||||
"title", "organization", "role", "company", "position", "project_name", "project_role",
|
||||
"school", "major", "degree", "start_date", "end_date_or_present", "name", "award", "date",
|
||||
"description",
|
||||
)
|
||||
ledger: list[dict[str, str]] = []
|
||||
for key in keys:
|
||||
value = str(entry.get(key) or "").strip()
|
||||
if value:
|
||||
ledger.append({"id": f"entry_{key}", "field": key, "text": value})
|
||||
for index, value in enumerate(entry.get("highlights") or [], start=1):
|
||||
clean = str(value).strip()
|
||||
if clean:
|
||||
ledger.append({"id": f"entry_highlight_{index}", "field": "highlight", "text": clean})
|
||||
return ledger
|
||||
|
||||
|
||||
def build_expander(settings: Settings, client: Any | None = None) -> EntryExpander:
|
||||
rules = RuleBasedEntryExpander()
|
||||
if not settings.use_openai:
|
||||
return rules
|
||||
completion = OpenAICompatibleStructuredClient(settings, client)
|
||||
return FallbackEntryExpander(OpenAIEntryExpander(completion), rules)
|
||||
Reference in New Issue
Block a user