The context-aware graph
A graph where every agent starts from scratch is only a little better than a monolith. The real win comes from agents that build on each other. The strengths analyzer reads the structured job analysis. The suggester reads strengths and weaknesses. Each agent narrows the problem for the next one.
Context flowing through the graph
Earlier agents produce narrower context. Later agents reason against it.
async def analyze_strengths(self, state: CVAnalysisState) -> CVAnalysisState:
cv_content = state["cv_content"]
extracted = state["analysis_results"].get("extracted_content", {})
job_analysis = state.get("job_analysis", {})
prompt = f"""
Identify TOP 5 strengths in this CV.
CV: {cv_content}
Structured data: {extracted}
Job requirements (structured): {job_analysis}
If the job analysis includes mandatory requirements, prioritize strengths
that match them. Be concise. One sentence each. JSON list only.
"""
raw = await self.llm.generate_text(prompt)
state["strengths"] = json.loads(clean_json_response(raw))
return stateThe strengths agent never re-reads the job description from scratch. It reads the already-structured output of the JD analyzer, so its prompt stays focused and fast.
async def score_cv(self, state: CVAnalysisState) -> CVAnalysisState:
"""The scorer reads everything the earlier agents produced."""
cv_content = state["cv_content"]
strengths = state.get("strengths", [])
weaknesses = state.get("weaknesses", [])
job_analysis = state.get("job_analysis", {})
extracted = state["analysis_results"].get("extracted_content", {})
scoring_prompt = f"""Score this CV 1-100 per category.
CV: {cv_content[:2000]}
Extracted: {str(extracted)[:1000]}
Strengths: {strengths}
Weaknesses: {weaknesses}
Job requirements: {job_analysis}
Return JSON with a "scores" object holding overall_score, keyword_match_score,
experience_relevance, skills_alignment, format_score, and a "rationale"
object with a brief sentence for each."""
raw = await self.llm.generate_text(scoring_prompt)
result = json.loads(clean_json_response(raw))
state["score"] = result["scores"]["overall_score"]
state["score_rationale"] = result.get("rationale", {})
return stateBy the time the scorer runs, four earlier agents have narrowed the problem. The scoring prompt is short and the output is grounded in every previous agent.
Prompt chaining is exactly the right mental model. The graph is the container that makes chaining reliable. Shared state, typed fields, conditional routing, and retry policies are what turn a clever chain into a production workflow.
Ordering exercise: Order these agents by when they should run
Loading practice…
Quiz: Quiz
Loading practice…