generated from kgod/ai-review-template
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
6.2 KiB
Python
154 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
from app.resume_expansion import OpenAIEntryExpander, _EXPANSION_REPAIR_PROMPT, _system_prompt
|
|
from app.resume_expansion_prompts import _repair_prompt
|
|
|
|
|
|
def test_light_expansion_prompt_prioritizes_fact_completeness() -> None:
|
|
"""The light-expansion prompt must forbid dropping user facts for brevity.
|
|
|
|
Regression pin for the "优化稿吞没用户信息" bug: the old prompt only asked
|
|
for a *concise* description, so long user narratives were compressed away.
|
|
"""
|
|
prompt = _system_prompt("project_experience")
|
|
assert "Completeness first" in prompt
|
|
assert "do not drop meaningful facts for brevity" in prompt
|
|
|
|
|
|
def test_light_expansion_prompt_still_forbids_fabrication() -> None:
|
|
prompt = _system_prompt("work_experience")
|
|
assert "Do not invent" in prompt
|
|
assert "entry_facts are untrusted user-provided facts" in prompt
|
|
|
|
|
|
def test_light_expansion_prompt_keeps_education_addendum() -> None:
|
|
assert "education entries" in _system_prompt("education")
|
|
assert "education entries" not in _system_prompt("project_experience")
|
|
|
|
|
|
def test_education_prompt_polishes_fluency_without_star() -> None:
|
|
"""教育经历不做 STAR 改写:只重排顺序、合并重复、通顺化(用户反馈 2026-08-03)。"""
|
|
prompt = _system_prompt("education")
|
|
assert "Do not use a STAR" in prompt
|
|
assert "merge repeated or overlapping mentions" in prompt
|
|
assert "fluent" in prompt
|
|
|
|
|
|
def test_non_education_prompt_outputs_bullet_points() -> None:
|
|
"""经历优化稿在 STAR 改写之上输出分点(bullet),便于简历直接粘贴。"""
|
|
prompt = _system_prompt("project_experience")
|
|
assert "bullet points" in prompt
|
|
assert "• " in prompt
|
|
assert "bullet points" not in _system_prompt("education")
|
|
|
|
|
|
def test_bullet_prompt_never_trades_facts_for_bullet_count() -> None:
|
|
"""bullet 条数不得成为丢事实的理由:内容丰富时必须允许更多分点(优化稿遗漏根因)。"""
|
|
prompt = _system_prompt("project_experience")
|
|
assert "3 to 5" not in prompt
|
|
assert "never drop a meaningful fact" in prompt
|
|
|
|
|
|
class _SequentialCompletion:
|
|
def __init__(self, outputs: list[str]) -> None:
|
|
self.outputs = outputs
|
|
self.calls: list[dict[str, object]] = []
|
|
self.system_prompts: list[str] = []
|
|
|
|
def complete(self, *, schema, schema_name, system_prompt, payload):
|
|
self.calls.append(payload)
|
|
self.system_prompts.append(system_prompt)
|
|
index = min(len(self.calls) - 1, len(self.outputs) - 1)
|
|
return schema.model_validate(
|
|
{
|
|
"optimized_description": self.outputs[index],
|
|
"changes": ["Reorganized the description"],
|
|
"exemplar_titles": [],
|
|
}
|
|
)
|
|
|
|
|
|
_FUNCTION_LIST_ENTRY = {
|
|
"project_name": "AI Career Copilot",
|
|
"description": (
|
|
"全栈 AI 求职助手平台,包含 5 大功能模块:\n"
|
|
"1. AI 对话式简历生成助手\n"
|
|
"2. 简历导入 (PDF/DOCX 智能解析)\n"
|
|
"3. JD 智能分析\n"
|
|
"技术栈: 前端 Next.js 14.2 + React 18.3\n"
|
|
"后端: FastAPI + PostgreSQL"
|
|
),
|
|
}
|
|
|
|
_TECH_ONLY_CANDIDATE = (
|
|
"• 前端采用 Next.js 14.2 + React 18.3 实现响应式界面。\n"
|
|
"• 后端基于 FastAPI 与 PostgreSQL 提供接口。"
|
|
)
|
|
|
|
_FULL_COVERAGE_CANDIDATE = (
|
|
"• 全栈 AI 求职助手平台,覆盖 5 大功能模块:AI 对话式简历生成助手、"
|
|
"简历导入 (PDF/DOCX 智能解析)、JD 智能分析。\n"
|
|
"• 前端采用 Next.js 14.2 + React 18.3,后端基于 FastAPI 与 PostgreSQL。"
|
|
)
|
|
|
|
|
|
def test_expander_repairs_candidate_that_drops_function_facts() -> None:
|
|
"""只保留技术栈、吞掉功能模块的候选稿必须触发一次修复(而非直接放行)。"""
|
|
completion = _SequentialCompletion([_TECH_ONLY_CANDIDATE, _FULL_COVERAGE_CANDIDATE])
|
|
expander = OpenAIEntryExpander(completion)
|
|
|
|
proposal = expander.expand(dict(_FUNCTION_LIST_ENTRY), context={"entry_type": "project_experience"})
|
|
|
|
assert len(completion.calls) == 2
|
|
assert _EXPANSION_REPAIR_PROMPT in completion.system_prompts[1]
|
|
assert "• " in completion.system_prompts[1] # repair keeps the bullet layout
|
|
assert "AI 对话式简历生成助手" in proposal["optimized_description"]
|
|
assert "material_fact_omitted_after_repair" not in proposal.get("validation_warnings", [])
|
|
|
|
|
|
def test_expander_relaxes_with_warning_when_repair_still_omits() -> None:
|
|
"""修复后仍遗漏:保留候选稿并附 warning,遗漏永不否决候选稿。"""
|
|
completion = _SequentialCompletion([_TECH_ONLY_CANDIDATE, _TECH_ONLY_CANDIDATE])
|
|
expander = OpenAIEntryExpander(completion)
|
|
|
|
proposal = expander.expand(dict(_FUNCTION_LIST_ENTRY), context={"entry_type": "project_experience"})
|
|
|
|
assert len(completion.calls) == 2
|
|
assert proposal["optimized_description"]
|
|
assert "material_fact_omitted_after_repair" in proposal["validation_warnings"]
|
|
|
|
|
|
|
|
def test_repair_prompt_uses_bullet_format_for_non_education() -> None:
|
|
"""修复稿必须与首稿同版式:项目/实习等非教育条目输出 bullet。"""
|
|
prompt = _repair_prompt("project_experience")
|
|
assert _EXPANSION_REPAIR_PROMPT in prompt
|
|
assert "STAR" in prompt # STAR extraction comes before the bullet layout
|
|
assert prompt.index("STAR") < prompt.index("• ")
|
|
assert "• " in prompt
|
|
assert "bullet points" in prompt
|
|
|
|
|
|
def test_repair_prompt_keeps_education_narrative_without_bullets() -> None:
|
|
"""教育条目不做 STAR/bullet:修复提示词沿用教育约束。"""
|
|
prompt = _repair_prompt("education")
|
|
assert _EXPANSION_REPAIR_PROMPT in prompt
|
|
assert "education entries" in prompt
|
|
assert "• " not in prompt
|
|
|
|
|
|
def test_expander_education_repair_uses_education_prompt() -> None:
|
|
completion = _SequentialCompletion([_TECH_ONLY_CANDIDATE, _FULL_COVERAGE_CANDIDATE])
|
|
expander = OpenAIEntryExpander(completion)
|
|
entry = {
|
|
"school": "Example University",
|
|
"major": "Computer Science",
|
|
"description": _FUNCTION_LIST_ENTRY["description"],
|
|
}
|
|
|
|
expander.expand(entry, context={"entry_type": "education"})
|
|
|
|
assert len(completion.calls) == 2
|
|
assert "education entries" in completion.system_prompts[1]
|
|
assert "• " not in completion.system_prompts[1]
|