| """ |
| LangGraph Workflow — 多 Agent 求职决策流水线。 |
| |
| 状态流转: |
| ResumeInput → CareerIntent → JobScout → JDAnalyst → ResumeEvidence |
| → MatchReasoning → CounterfactualPlanning → ResumeCoach → InterviewCoach → StrategyPlanner → FinalReport |
| """ |
|
|
| from agent_state import AgentState, CareerIntent, ResumeEvidence, MatchResult, StrategyOutput |
| from agents import ( |
| CareerIntentAgent, |
| JobScoutAgent, |
| JDAnalystAgent, |
| ResumeEvidenceAgent, |
| MatchReasoningAgent, |
| CounterfactualPlanningAgent, |
| ResumeCoachAgent, |
| InterviewCoachAgent, |
| StrategyPlannerAgent, |
| ) |
|
|
| from pathlib import Path |
| import json |
|
|
|
|
| class OfferCatcherWorkflow: |
| """主工作流 — 9 Agent 协作完成求职决策。""" |
|
|
| def __init__(self, llm_client=None): |
| self.llm = llm_client |
| self.trace: list[str] = [] |
|
|
| def run(self, resume: str, goal: str = "", |
| user_jds: list[dict] | None = None, |
| local_corpus: list[dict] | None = None) -> AgentState: |
| """ |
| 执行完整多 Agent 流水线。 |
| |
| Args: |
| resume: 简历文本 |
| goal: 用户一句话目标 |
| user_jds: 用户粘贴的 JD |
| local_corpus: 本地岗位缓存 |
| |
| Returns: |
| AgentState: 包含所有 Agent 的输出 |
| """ |
| state = AgentState(resume_text=resume, user_goal=goal) |
|
|
| |
| self._log("CareerIntentAgent 开始...") |
| state.intent = CareerIntentAgent(self.llm).run(resume, goal) |
| state.agent_trace.append(f"[CareerIntent] direction={state.intent.direction} stage={state.intent.stage}") |
| self._log(f" 方向={state.intent.direction}, 阶段={state.intent.stage}") |
|
|
| |
| self._log("JobScoutAgent 搜索岗位...") |
| scout = JobScoutAgent(self.llm) |
| state.jds = scout.scout(state.intent, user_jds, local_corpus) |
| state.agent_trace.append(f"[JobScout] 找到{len(state.jds)}个岗位") |
| self._log(f" 找到{len(state.jds)}个岗位") |
|
|
| |
| self._log("JDAnalystAgent 分析JD...") |
| analyst = JDAnalystAgent(self.llm) |
| for i, jd in enumerate(state.jds): |
| if not jd.hard_skills and jd.jd_text: |
| try: |
| analyzed = analyst.analyze(jd.jd_text, { |
| "title": jd.title, "company": jd.company, |
| "city": jd.city, "salary": jd.salary, |
| "source_url": jd.source_url, |
| }) |
| state.jds[i] = analyzed |
| except Exception as e: |
| state.agent_trace.append(f"[JDAnalyst] #{i} 失败: {e}") |
| state.agent_trace.append(f"[JDAnalyst] 完成{len(state.jds)}个JD分析") |
|
|
| |
| self._log("ResumeEvidenceAgent 提取证据...") |
| evidence_agent = ResumeEvidenceAgent(self.llm) |
| state.resume_evidence = evidence_agent.run(resume, state.intent.direction) |
| state.agent_trace.append(f"[ResumeEvidence] 技能={len(state.resume_evidence.skill_evidence)} 缺口={len(state.resume_evidence.gap_areas)}") |
|
|
| |
| self._log("MatchReasoningAgent 匹配推理...") |
| matcher = MatchReasoningAgent(self.llm) |
| state.match_results = [] |
| for jd in state.jds[:15]: |
| result = matcher.evaluate(jd, state.resume_evidence) |
| state.match_results.append(result) |
| state.match_results.sort(key=lambda x: -x.match_score) |
| state.agent_trace.append(f"[MatchReasoning] 完成{len(state.match_results)}个岗位匹配") |
|
|
| |
| self._log("CounterfactualPlanningAgent 反事实规划...") |
| cf_agent = CounterfactualPlanningAgent(self.llm) |
| state.counterfactual = cf_agent.plan(state.resume_evidence, state.match_results[:5]) |
| state.agent_trace.append(f"[Counterfactual] {len(state.counterfactual.top3_payoffs)}个补强建议") |
|
|
| |
| self._log("ResumeCoachAgent 简历优化...") |
| coach_agent = ResumeCoachAgent(self.llm) |
| target_jd = state.jds[0] if state.jds else None |
| state.coach = coach_agent.coach(resume, state.resume_evidence, target_jd) |
| state.agent_trace.append(f"[ResumeCoach] 可改写={len(state.coach.can_rewrite)} 需补={len(state.coach.need_project_first)}") |
|
|
| |
| self._log("InterviewCoachAgent 面试准备...") |
| interview_agent = InterviewCoachAgent(self.llm) |
| state.interview_prep = interview_agent.prepare(state.match_results[:3], state.resume_evidence) |
| state.agent_trace.append(f"[InterviewCoach] {len(state.interview_prep.likely_questions)}个问题 {len(state.interview_prep.prep_plan_7day)}天计划") |
|
|
| |
| self._log("StrategyPlannerAgent 投递策略...") |
| strategy_agent = StrategyPlannerAgent(self.llm) |
| state.strategy = strategy_agent.plan(state.match_results) |
| state.agent_trace.append(f"[Strategy] 稳投={len(state.strategy.safe_jobs)} 冲刺={len(state.strategy.stretch_jobs)}") |
|
|
| self._log("✓ 全部 Agent 执行完毕") |
| return state |
|
|
| def _log(self, msg: str): |
| self.trace.append(msg) |
|
|
| def get_trace(self) -> str: |
| return "\n".join(self.trace) |
|
|
| def build_report(self, state: AgentState): |
| """从 AgentState 构建 FinalDecisionReport。""" |
| from final_report import ReportBuilder |
| builder = ReportBuilder() |
| return builder.build(state) |
|
|
|
|
| def run_full_pipeline(resume: str, goal: str = "", use_online: bool = False): |
| """ |
| 一站式执行:跑完 9 Agent 工作流,返回 FinalDecisionReport。 |
| |
| Args: |
| resume: 简历文本 |
| goal: 用户目标 |
| use_online: 是否启用 LLM API(默认规则版) |
| |
| Returns: |
| FinalDecisionReport:统一的决策报告 |
| """ |
| from llm_client import LLMClient |
|
|
| llm = LLMClient() if use_online else None |
| workflow = OfferCatcherWorkflow(llm_client=llm) |
| corpus = _load_local_corpus() |
| state = workflow.run(resume, goal, local_corpus=corpus) |
| return workflow.build_report(state) |
|
|
|
|
| def _load_local_corpus() -> list[dict]: |
| """加载本地岗位缓存。""" |
| root = Path(__file__).resolve().parent.parent |
| for fname in ("jobs_corpus.json", "jobs_merged.json", "jobs.json"): |
| path = root / "data" / fname |
| if path.exists(): |
| try: |
| return json.loads(path.read_text(encoding="utf-8")) |
| except Exception: |
| continue |
| return [] |
|
|