import re SKILL_CATALOG = [ # LLM / NLP "LLM", "RAG", "Agent", "Embedding", "Faiss", "LangChain", "Prompt", "BERT", "GPT", "T5", "Llama", "Qwen", "ChatGLM", "DeepSeek", "文本分类", "命名实体识别", "NER", "Seq2Seq", "Attention", "Beam Search", "自然语言处理", "NLP", "文本生成", "摘要生成", "信息抽取", # 推荐 / 搜索 "推荐系统", "召回", "排序", "重排", "NDCG", "A/B Test", "CTR", "搜索", "Query 理解", "意图识别", "向量检索", "多兴趣", "Semantic ID", "Wide&Deep", "DeepFM", "DIN", "DIEN", "MIND", # CV "TensorFlow", "PyTorch", "OpenCV", "YOLO", "图像分类", "目标检测", "计算机视觉", "CV", "分割", "Transformer", "ViT", # 后端 / 基础 "Python", "Java", "Go", "Golang", "C++", "Rust", "Docker", "Kubernetes", "K8s", "微服务", "gRPC", "Thrift", "RPC", "MySQL", "Redis", "Kafka", "消息队列", "Consul", "etcd", "FastAPI", "Flask", "Spring", "Django", "Gin", # 数据 / 分析 "SQL", "Hadoop", "Spark", "Flink", "Hive", "Pandas", "NumPy", "Matplotlib", "数据分析", "数据可视化", "指标体系", "漏斗分析", "AUC", "ROC", # ML / DL 基础 "机器学习", "深度学习", "强化学习", "scikit-learn", "XGBoost", "LightGBM", "模型训练", "模型评估", "特征工程", # LLM 工程 "多轮对话", "Function Calling", "Tool Use", "思维链", "CoT", "混合检索", "Hybrid Search", "Reranker", "重排序", "Prompt Engineering", "Few-shot", "RLHF", "SFT", # 其他 "产品设计", "可视化", "多模态", "CLIP", "Stable Diffusion", ] PROJECT_SIGNAL_CATALOG = [ "Semantic ID", "rerank", "MIND", "简历", "JD", "岗位", "检索", "评估", "Demo", "用户兴趣", "多兴趣", "生成式推荐", ] def contains(text: str, term: str) -> bool: return term.lower() in text.lower() def parse_resume(resume_text: str) -> dict: """Rule-based parser used as the stable fallback for Resume Parser Agent.""" resume_text = resume_text or "" skills = [skill for skill in SKILL_CATALOG if contains(resume_text, skill)] project_signals = [ signal for signal in PROJECT_SIGNAL_CATALOG if contains(resume_text, signal) ] project_signals = list(dict.fromkeys(project_signals + skills)) has_metrics = bool( re.search(r"ndcg|hitrate|auc|准确率|召回率|提升|%|topk", resume_text, re.I) ) has_llm_project = bool( re.search(r"llm|rag|agent|prompt|deepseek|openai|通义|混元", resume_text, re.I) ) has_rec_project = bool( re.search(r"推荐|召回|排序|mind|semantic id|rerank|用户兴趣", resume_text, re.I) ) return { "skills": skills, "project_signals": project_signals, "has_metrics": has_metrics, "has_llm_project": has_llm_project, "has_rec_project": has_rec_project, "raw_text": resume_text, } # --------------------------------------------------------------------------- # LLM 增强解析(可选,失败自动 fallback 到规则版 parse_resume) # --------------------------------------------------------------------------- RESUME_LLM_SCHEMA = """{ "skills": ["技能列表"], "project_signals": ["项目信号词"], "has_metrics": true/false, "has_llm_project": true/false, "has_rec_project": true/false }""" RESUME_LLM_PROMPT = """你是一个简历解析器。请从以下简历文本中提取结构化信息,严格按 JSON Schema 输出。 Schema: {schema} 要求: 1. 只输出 JSON,不要输出任何其他文字 2. skills 提取所有明确提到的技术名称 3. has_metrics 判断是否有量化指标(NDCG、准确率、提升 x%、TopK 等) 4. has_llm_project 判断是否有 LLM/RAG/Agent/Prompt 相关项目 5. has_rec_project 判断是否有推荐/召回/排序相关项目 6. project_signals 提取项目相关关键词 简历文本: {resume_text}""" def parse_resume_with_llm(resume_text: str, llm_client=None) -> dict: """LLM 增强简历解析,失败自动 fallback 到 parse_resume。""" if llm_client is None: return parse_resume(resume_text) try: from src.llm_client import LLMClient if not isinstance(llm_client, LLMClient) or not llm_client.available: return parse_resume(resume_text) except ImportError: return parse_resume(resume_text) prompt = RESUME_LLM_PROMPT.format(schema=RESUME_LLM_SCHEMA, resume_text=resume_text[:3000]) result = llm_client.chat_json("你是精确的简历解析器。", prompt) if result is None: return parse_resume(resume_text) # 校验 + 补全 for key in ["skills", "project_signals"]: if key not in result or not isinstance(result.get(key), list): result[key] = [] for key in ["has_metrics", "has_llm_project", "has_rec_project"]: if key not in result: result[key] = False result["raw_text"] = resume_text return result