feat: improve resume optimization and import reliability

This commit is contained in:
hyp
2026-08-10 11:26:47 +08:00
parent 1c762fd092
commit d8db0d8792
35 changed files with 1861 additions and 661 deletions
+230 -138
View File
@@ -8,7 +8,7 @@ from threading import Lock
from typing import Any, Callable
from uuid import uuid4
from .database import Database
from .database import Database, SessionRevisionConflict
from .enrichment import prepare_rewrite_confirmation, process_rewrite_confirmation
from .fsm import (
FSMError,
@@ -914,7 +914,9 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
def component_event(
self, session_id: str, request: ComponentEventRequest
) -> ActionResponse:
with self.database.transaction(immediate=True) as connection:
# Snapshot state before model-backed transition work. The write below
# verifies these versions before persisting to prevent stale results.
with self.database.transaction() as connection:
session = self.database.fetch_session(connection, session_id)
if session is None:
raise FSMError("session_not_found", "Session not found", status_code=404)
@@ -934,109 +936,166 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
"Use POST /sessions/{session_id}/create for resume creation",
status_code=422,
)
if Stage(session["stage"]) == Stage.BUILDER_CONVERSATION:
resume = self.database.fetch_resume(connection, session_id)
if resume is None:
raise FSMError("resume_not_created", "Create the resume before using Builder cards")
transition = builder_conversation.process_component_event(
session["profile"],
block["data"],
request.action,
request.payload,
resume["content"],
self.skill_suggester,
)
elif Stage(session["stage"]) == Stage.CONTENT_READY and block["data"].get(
"confirmation_kind"
) == "rewrite":
transition = process_rewrite_confirmation(
session["profile"], request.action, request.payload
)
else:
transition = process_component_event(
stage=Stage(session["stage"]),
profile=session["profile"],
component_data=block["data"],
action=request.action,
payload=request.payload,
)
if getattr(transition, "polish_description", False):
self._polish_module_entry(transition)
if getattr(transition, "propose_anchor_optimization", False):
self._propose_anchor_optimization(transition)
if getattr(transition, "suggest_skills", False):
self._suggest_skills(transition)
if getattr(transition, "suggest_target_positions", False):
self._suggest_target_positions(transition)
anchor_proposal = transition.profile.get("anchor_proposal")
if transition.stage == Stage.MINIMUM_READY:
transition.profile.pop("anchor_proposal", None)
if (
isinstance(anchor_proposal, dict)
and isinstance(transition.profile.get("anchor"), dict)
and request.payload.get("use_optimized") is True
):
transition.profile["anchor"]["description"] = anchor_proposal[
"optimized_description"
]
transition.profile["anchor"]["provenance"] = anchor_proposal["source"]
elif transition.stage == Stage.ANCHOR_COLLECTING:
transition.profile.pop("anchor_proposal", None)
self.database.update_block(
connection,
block["id"],
lifecycle=transition.lifecycle,
)
draft_id = session.get("draft_id")
if transition.create_draft:
draft_id = draft_id or f"draft_{uuid4().hex}"
preview = merge_ids(None, self.rewriter.rewrite(transition.profile))
transition.turn["blocks"].insert(
-1,
{
"type": "resume_patch",
"lifecycle": "submitted",
"data": {"draft_id": draft_id, "operation": "replace", "value": preview},
},
)
resume_content = getattr(transition, "resume_content", None)
if resume_content is None and getattr(transition, "refresh_resume", False):
resume_content = self.rewriter.rewrite(transition.profile)
transition.resume_content = resume_content
resume = None
if resume_content is not None:
resume = self.database.fetch_resume(connection, session_id)
if resume is None:
raise FSMError("resume_not_created", "Create the resume before confirming content")
resume_content = (
merge_profile_refresh(resume["content"], resume_content)
if getattr(transition, "refresh_resume", False)
else merge_ids(resume["content"], resume_content)
)
if getattr(transition, "generate_profile_summary", False):
resume = resume or self.database.fetch_resume(connection, session_id)
if resume is None:
raise FSMError("resume_not_created", "Create the resume before finishing content")
base_content = resume_content if resume_content is not None else resume["content"]
summary = base_content.get("profile_summary")
should_generate_summary = not isinstance(summary, dict) or summary.get("stale") is True
if should_generate_summary:
try:
summary_text = self.profile_summary_generator.generate(base_content)
resume_content = set_generated_profile_summary(
base_content, summary_text, replace_stale=True
)
resume = self.database.fetch_resume(connection, session_id)
except Exception as exc:
log_ai_event(
"profile_summary_generation_failed",
level=logging.WARNING,
reason_code=getattr(exc, "reason_code", type(exc).__name__),
exception=type(exc).__name__,
)
expected_session_revision = session["revision"]
expected_block_version = block["version"]
expected_resume_revision = resume["revision"] if resume is not None else None
if Stage(session["stage"]) == Stage.BUILDER_CONVERSATION:
if resume is None:
raise FSMError("resume_not_created", "Create the resume before using Builder cards")
transition = builder_conversation.process_component_event(
session["profile"],
block["data"],
request.action,
request.payload,
resume["content"],
self.skill_suggester,
)
elif Stage(session["stage"]) == Stage.CONTENT_READY and block["data"].get(
"confirmation_kind"
) == "rewrite":
transition = process_rewrite_confirmation(
session["profile"], request.action, request.payload
)
else:
transition = process_component_event(
stage=Stage(session["stage"]),
profile=session["profile"],
component_data=block["data"],
action=request.action,
payload=request.payload,
)
if getattr(transition, "polish_description", False):
self._polish_module_entry(transition)
if getattr(transition, "propose_anchor_optimization", False):
self._propose_anchor_optimization(transition)
if getattr(transition, "suggest_skills", False):
self._suggest_skills(transition)
if getattr(transition, "suggest_target_positions", False):
self._suggest_target_positions(transition)
anchor_proposal = transition.profile.get("anchor_proposal")
if transition.stage == Stage.MINIMUM_READY:
transition.profile.pop("anchor_proposal", None)
if (
isinstance(anchor_proposal, dict)
and isinstance(transition.profile.get("anchor"), dict)
and request.payload.get("use_optimized") is True
):
transition.profile["anchor"]["description"] = anchor_proposal[
"optimized_description"
]
transition.profile["anchor"]["provenance"] = anchor_proposal["source"]
elif transition.stage == Stage.ANCHOR_COLLECTING:
transition.profile.pop("anchor_proposal", None)
draft_id = session.get("draft_id")
if transition.create_draft:
draft_id = draft_id or f"draft_{uuid4().hex}"
preview = merge_ids(None, self.rewriter.rewrite(transition.profile))
transition.turn["blocks"].insert(
-1,
{
"type": "resume_patch",
"lifecycle": "submitted",
"data": {"draft_id": draft_id, "operation": "replace", "value": preview},
},
)
resume_content = getattr(transition, "resume_content", None)
if resume_content is None and getattr(transition, "refresh_resume", False):
resume_content = self.rewriter.rewrite(transition.profile)
transition.resume_content = resume_content
if resume_content is not None:
if resume is None:
raise FSMError("resume_not_created", "Create the resume before confirming content")
resume_content = (
merge_profile_refresh(resume["content"], resume_content)
if getattr(transition, "refresh_resume", False)
else merge_ids(resume["content"], resume_content)
)
if getattr(transition, "generate_profile_summary", False):
if resume is None:
raise FSMError("resume_not_created", "Create the resume before finishing content")
base_content = resume_content if resume_content is not None else resume["content"]
summary = base_content.get("profile_summary")
should_generate_summary = not isinstance(summary, dict) or summary.get("stale") is True
if should_generate_summary:
try:
summary_text = self.profile_summary_generator.generate(base_content)
resume_content = set_generated_profile_summary(
base_content, summary_text, replace_stale=True
)
except Exception as exc:
log_ai_event(
"profile_summary_generation_failed",
level=logging.WARNING,
reason_code=getattr(exc, "reason_code", type(exc).__name__),
exception=type(exc).__name__,
)
with self.database.transaction(immediate=True) as connection:
current_session = self.database.fetch_session(connection, session_id)
if current_session is None:
raise FSMError("session_not_found", "Session not found", status_code=404)
if current_session["revision"] != expected_session_revision:
raise FSMError(
"revision_conflict",
"Conversation changed while processing the component; retry with the latest version",
status_code=409,
)
current_block = self.database.fetch_block(connection, session_id, request.component_id)
if current_block is None:
raise FSMError("component_not_found", "Component not found", status_code=404)
if current_block["type"] != "component" or current_block["lifecycle"] != "active":
raise FSMError("component_not_active", "Component was already handled")
if current_block["version"] != expected_block_version:
raise FSMError(
"revision_conflict",
"Component changed while processing; retry with the latest version",
status_code=409,
)
current_resume = self.database.fetch_resume(connection, session_id, for_update=True)
if expected_resume_revision is None:
if current_resume is not None:
raise FSMError(
"revision_conflict",
"Resume changed while processing the component; retry with the latest version",
status_code=409,
)
elif current_resume is None or current_resume["revision"] != expected_resume_revision:
raise FSMError(
"revision_conflict",
"Resume changed while processing the component; retry with the latest version",
status_code=409,
)
try:
self.database.update_block(
connection,
block["id"],
lifecycle=transition.lifecycle,
expected_version=expected_block_version,
)
except SessionRevisionConflict as exc:
raise FSMError(
"revision_conflict",
"Component changed while processing; retry with the latest version",
status_code=409,
) from exc
if resume_content is not None:
assert resume is not None
resume = self.database.update_resume(connection, session_id, resume_content)
if current_resume is None:
raise FSMError("resume_not_created", "Create the resume before confirming content")
try:
resume = self.database.update_resume(
connection,
session_id,
resume_content,
expected_revision=expected_resume_revision,
)
except SessionRevisionConflict as exc:
raise FSMError(
"revision_conflict",
"Resume changed while processing the component; retry with the latest version",
status_code=409,
) from exc
if transition.stage == Stage.BUILDER_CONVERSATION:
builder_conversation.reconcile_last_confirmed_entry(
transition.profile, resume["content"]
@@ -1054,13 +1113,21 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
},
},
)
updated = self.database.update_session(
connection,
session_id,
stage=transition.stage,
profile=transition.profile,
draft_id=draft_id,
)
try:
updated = self.database.update_session(
connection,
session_id,
stage=transition.stage,
profile=transition.profile,
draft_id=draft_id,
expected_revision=expected_session_revision,
)
except SessionRevisionConflict as exc:
raise FSMError(
"revision_conflict",
"Conversation changed while processing the component; retry with the latest version",
status_code=409,
) from exc
turn_id = self.database.insert_turn(
connection,
session_id=session_id,
@@ -1074,7 +1141,7 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
return response
def add_message(self, session_id: str, request: MessageRequest) -> ActionResponse:
with self.database.transaction(immediate=True) as connection:
with self.database.transaction() as connection:
session = self.database.fetch_session(connection, session_id)
if session is None:
raise FSMError("session_not_found", "Session not found", status_code=404)
@@ -1088,6 +1155,36 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
resume = self.database.fetch_resume(connection, session_id)
if resume is None:
raise FSMError("resume_not_created", "Create the resume before using Builder chat")
expected_session_revision = session["revision"]
expected_resume_revision = resume["revision"]
transition = builder_conversation.process_message(
self, session["profile"], request.content, resume["content"]
)
with self.database.transaction(immediate=True) as connection:
current_resume = self.database.fetch_resume(connection, session_id, for_update=True)
if current_resume is None:
raise FSMError("resume_not_created", "Create the resume before using Builder chat")
if current_resume["revision"] != expected_resume_revision:
raise FSMError(
"revision_conflict",
"Resume changed while the message was being processed; retry with the latest version",
status_code=409,
)
try:
updated = self.database.update_session(
connection,
session_id,
stage=transition.stage,
profile=transition.profile,
expected_revision=expected_session_revision,
)
except SessionRevisionConflict as exc:
raise FSMError(
"revision_conflict",
"Conversation changed while the message was being processed; retry with the latest version",
status_code=409,
) from exc
self.database.insert_turn(
connection,
session_id=session_id,
@@ -1096,20 +1193,14 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
composer_mode=ComposerMode.CHAT,
blocks=[{"type": "text", "lifecycle": "submitted", "data": {"text": request.content}}],
)
transition = builder_conversation.process_message(self, session["profile"], request.content, resume["content"])
self.database.supersede_active_components(connection, session_id)
updated = self.database.update_session(
connection,
session_id,
stage=transition.stage,
profile=transition.profile,
)
turn_id = self.database.insert_turn(connection, session_id=session_id, **transition.turn)
response = self._action_response(updated, self.database.get_turn(turn_id))
response.builder_stream_phases = list(
((transition.profile.get("builder") or {}).get("last_stream_phases") or [])
)
return response
def _polish_module_entry(self, transition: Any) -> None:
"""Generate a proposal without mutating the user's original description."""
draft = (transition.profile.get("enrichment") or {}).get("module_draft") or {}
@@ -1260,7 +1351,7 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
def _create_resume_transaction(
self, session_id: str, request: CreateResumeRequest
) -> CreateResumeResponse:
with self.database.transaction(immediate=True) as connection:
with self.database.transaction() as connection:
session = self.database.fetch_session(connection, session_id)
if session is None:
raise FSMError("session_not_found", "Session not found", status_code=404)
@@ -1280,24 +1371,23 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
"The first-anchor gate is not satisfied",
missing_fields=missing_fields(session["profile"]),
)
creating = self.database.update_session(
connection,
session_id,
stage=Stage.RESUME_CREATING,
profile=session["profile"],
)
self.database.supersede_active_components(connection, session_id)
creating_status = component("CreatingStatusCard", status="creating")
creating_status["lifecycle"] = "submitted"
self.database.insert_turn(
connection,
session_id=session_id,
**assistant_turn(
"Creating your resume.",
[creating_status],
),
)
content = merge_ids(None, self.rewriter.rewrite(creating["profile"]))
expected_revision = session["revision"]
source_profile = deepcopy(session["profile"])
content = merge_ids(None, self.rewriter.rewrite(source_profile))
with self.database.transaction(immediate=True) as connection:
current = self.database.fetch_session(connection, session_id)
if current is None:
raise FSMError("session_not_found", "Session not found", status_code=404)
existing = self.database.fetch_resume(connection, session_id)
if existing is not None:
turn = self._last_turn(session_id)
return self._create_response(current, existing, turn, created=False)
if current["revision"] != expected_revision:
raise FSMError(
"revision_conflict",
"Conversation changed while resume generation was running; retry with the latest version",
status_code=409,
)
resume_id = f"resume_{uuid4().hex}"
resume = self.database.insert_resume(
connection,
@@ -1307,7 +1397,7 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
content=content,
)
profile, ready_turn = builder_conversation.welcome_turn(
deepcopy(creating["profile"]), resume_id, resume_content=content
deepcopy(current["profile"]), resume_id, resume_content=content
)
updated = self.database.update_session(
connection,
@@ -1315,7 +1405,9 @@ class ResumeAgent(ResumeEditingMixin, OptimizationFlowMixin):
stage=Stage.BUILDER_CONVERSATION,
profile=profile,
resume_id=resume_id,
expected_revision=expected_revision,
)
self.database.supersede_active_components(connection, session_id)
ready_turn["blocks"].insert(
1,
{