offer-catcher-agent-v2 / src /langgraph_workflow.py
hungryb's picture
v2: agent report + filtered corpus + evidence contract
54b5b64 verified
Raw
History Blame Contribute Delete
6.72 kB
"""
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)
# Step 1: Career Intent
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}")
# Step 2: Job Scout
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)}个岗位")
# Step 3: JD Analyst
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分析")
# Step 4: Resume Evidence
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)}")
# Step 5: Match Reasoning
self._log("MatchReasoningAgent 匹配推理...")
matcher = MatchReasoningAgent(self.llm)
state.match_results = []
for jd in state.jds[:15]: # 最多匹配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)}个岗位匹配")
# Step 6: Counterfactual Planning
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)}个补强建议")
# Step 7: Resume Coach
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)}")
# Step 8: Interview Coach
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)}天计划")
# Step 9: Strategy Planner
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 []