Files
resume-agent/backend/tests/test_builder_detail_gate.py
T
hypandClaude ae2d9b128d feat: builder 简历生成 + 轻度优化 + 简历导入交付副本
自内部仓库剥离深度优化与 RAG 知识库后的交付版本:
- Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强)
- 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护
- 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏
- PostgreSQL 运行时 + Alembic 迁移链

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 11:08:30 +08:00

109 lines
4.5 KiB
Python

"""Detail-path guards: skip intents never pollute drafts; LLM gate routes before fact-merge."""
from __future__ import annotations
from types import SimpleNamespace
from typing import Any
from app.builder_conversation.candidate import _candidate_rewrite
from app.builder_conversation.predicates import _is_no_information_reply
from app.builder_conversation.rescue import llm_detail_route
from app.chat_intents import ChatTurnClassification
from builder_flow_helpers import create_builder_session, send_message, start_education
from test_api import active_component
class _StubClassifier:
def __init__(self, result: ChatTurnClassification) -> None:
self.result = result
def classify(self, message: str, *, state_summary: dict[str, Any]) -> ChatTurnClassification:
return self.result
def _agent(classifier: Any) -> Any:
return SimpleNamespace(
expander=SimpleNamespace(expand=lambda entry, *, context: {}),
_chat_intent_classifier=classifier,
)
def _profile_with_draft() -> dict[str, Any]:
return {
"job_type": "campus",
"builder": {
"active_section": "education",
"identity_draft": {"school": "X 大学", "description": "完成数据库课程项目。"},
"gap_state": {"asked": ["academic_result"], "skipped": [], "rounds": 1},
},
}
def test_skip_words_count_as_no_information() -> None:
for word in ("跳过", "先跳过", "跳过吧", "不用了", "不需要", "以后再说", "没有了"):
assert _is_no_information_reply(word), word
def test_skip_reply_does_not_pollute_description(client: Any) -> None:
session_id, body = create_builder_session(client)
start_education(client, session_id, body)
send_message(client, session_id, "完成数据库课程项目。") # triggers the gap prompt
reply = send_message(client, session_id, "跳过")
card = active_component(reply)["data"]
assert card["component"] == "experience_confirm_card"
assert "跳过" not in str(card["value"].get("description") or "")
def test_detail_gate_no_info_skips_gap_without_merging() -> None:
agent = _agent(_StubClassifier(ChatTurnClassification(intent="no_info", confidence=0.9)))
transition = llm_detail_route(agent, _profile_with_draft(), "先跳过这个")
assert transition is not None
state = transition.profile["builder"]
assert "先跳过这个" not in state["identity_draft"]["description"]
assert "academic_result" in state["gap_state"]["skipped"]
def test_detail_gate_revise_regenerates_candidate() -> None:
agent = _agent(_StubClassifier(ChatTurnClassification(
intent="revise_proposal", confidence=0.9, revision_instruction="再简洁一点",
)))
transition = llm_detail_route(agent, _profile_with_draft(), "帮我再精简下")
assert transition is not None
assert transition.profile["builder"]["pending_entry"]["_proposal"] is not None
assert "帮我再精简下" not in transition.profile["builder"]["identity_draft"]["description"]
def test_detail_gate_chitchat_does_not_merge() -> None:
agent = _agent(_StubClassifier(ChatTurnClassification(intent="chitchat", confidence=0.9)))
transition = llm_detail_route(agent, _profile_with_draft(), "好的谢谢")
assert transition is not None
assert transition.profile["builder"]["identity_draft"]["description"] == "完成数据库课程项目。"
def test_detail_gate_passes_facts_and_low_confidence_through() -> None:
facts = _agent(_StubClassifier(ChatTurnClassification(intent="provide_facts", confidence=0.9)))
assert llm_detail_route(facts, _profile_with_draft(), "GPA 4.3") is None
shaky = _agent(_StubClassifier(ChatTurnClassification(intent="no_info", confidence=0.4)))
assert llm_detail_route(shaky, _profile_with_draft(), "跳过") is None
def test_candidate_rewrite_ensure_facts_appends_missing() -> None:
class _Expander:
def expand(self, entry: dict[str, Any], *, context: dict[str, Any]) -> dict[str, Any]:
return {"optimized_description": "主修课程:数据结构、计算机视觉。", "source": "test"}
agent = SimpleNamespace(expander=_Expander())
proposal = _candidate_rewrite(
agent,
{"job_type": "campus"},
{"description": "学习数据结构、计算机视觉课程。GPA: 4.3/5.0,排名前百分之10。"},
"education",
ensure_facts=True,
)
assert "GPA: 4.3/5.0" in proposal["optimized_description"]
assert "排名前百分之10" in proposal["optimized_description"]
assert proposal["uncovered_facts"] == []