generated from kgod/ai-review-template
feat: builder 简历生成 + 轻度优化 + 简历导入交付副本
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""Injectable, grounded skill suggestions for the enrichment skills card."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .enrichment_modules import skill_suggestions
|
||||
from .llm_services import OpenAICompatibleStructuredClient, StrictSchema
|
||||
from .settings import Settings
|
||||
|
||||
|
||||
class SkillSuggester(Protocol):
|
||||
"""Suggest skills from the user's target role and already-entered resume facts."""
|
||||
|
||||
def suggest(self, profile: dict[str, Any]) -> list[str]: ...
|
||||
|
||||
|
||||
class RuleBasedSkillSuggester:
|
||||
def suggest(self, profile: dict[str, Any]) -> list[str]:
|
||||
return skill_suggestions(profile.get("target_position"), profile)
|
||||
|
||||
|
||||
class SkillSuggestionOutput(StrictSchema):
|
||||
skills: list[str] = Field(max_length=8)
|
||||
|
||||
|
||||
class OpenAISkillSuggester:
|
||||
def __init__(
|
||||
self, completion: OpenAICompatibleStructuredClient, fallback: SkillSuggester
|
||||
) -> None:
|
||||
self.completion = completion
|
||||
self.fallback = fallback
|
||||
|
||||
def suggest(self, profile: dict[str, Any]) -> list[str]:
|
||||
fallback = self.fallback.suggest(profile)
|
||||
try:
|
||||
output = self.completion.complete(
|
||||
schema=SkillSuggestionOutput,
|
||||
schema_name="skill_suggestions",
|
||||
system_prompt=(
|
||||
"你是中文求职简历助手。根据目标岗位和用户已经填写的经历事实推荐技能标签。"
|
||||
"只输出适合技能卡的简短技能名称,不要写句子、等级、熟练度或虚构项目成果。"
|
||||
"可以补充目标岗位常见但用户尚未填写的技能,作为待学习/待确认建议;"
|
||||
"不要把公司、学校、课程或奖项名称当作技能。"
|
||||
),
|
||||
payload={
|
||||
"target_position": profile.get("target_position"),
|
||||
"facts": _skill_facts(profile),
|
||||
"existing_skills": (profile.get("tags") or {}).get("skills") or [],
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
return fallback
|
||||
return _merge_suggestions(output.skills, fallback, profile)
|
||||
|
||||
|
||||
def build_skill_suggester(
|
||||
settings: Settings, client: Any | None = None
|
||||
) -> SkillSuggester:
|
||||
rules = RuleBasedSkillSuggester()
|
||||
if not settings.use_openai:
|
||||
return rules
|
||||
return OpenAISkillSuggester(OpenAICompatibleStructuredClient(settings, client), rules)
|
||||
|
||||
|
||||
def _skill_facts(profile: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
facts: list[dict[str, Any]] = []
|
||||
entries: list[Any] = [profile.get("anchor")]
|
||||
entries.extend(profile.get("experiences") or [])
|
||||
for records in (profile.get("records") or {}).values():
|
||||
entries.extend(records or [])
|
||||
for entry in entries:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
description = str(entry.get("description") or "").strip()
|
||||
highlights = [str(item).strip() for item in entry.get("highlights") or [] if str(item).strip()]
|
||||
if description or highlights:
|
||||
facts.append(
|
||||
{
|
||||
"record_type": entry.get("record_type"),
|
||||
"description": description or None,
|
||||
"highlights": highlights,
|
||||
}
|
||||
)
|
||||
return facts
|
||||
|
||||
|
||||
def _merge_suggestions(
|
||||
proposed: list[str], fallback: list[str], profile: dict[str, Any]
|
||||
) -> list[str]:
|
||||
existing = {
|
||||
str(skill).strip().casefold()
|
||||
for skill in ((profile.get("tags") or {}).get("skills") or [])
|
||||
if str(skill).strip()
|
||||
}
|
||||
result: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for skill in [*proposed, *fallback]:
|
||||
normalized = str(skill).strip()
|
||||
key = normalized.casefold()
|
||||
if normalized and len(normalized) <= 32 and key not in existing and key not in seen:
|
||||
result.append(normalized)
|
||||
seen.add(key)
|
||||
return result[:8]
|
||||
Reference in New Issue
Block a user