The resilient graph
LLM calls fail. The provider rate-limits, the JSON parsing blows up, or the model returns a field that Pydantic rejects. A production graph treats each node as fallible and keeps moving when it can. That is what turns a demo into a workflow you can ship.
Errors contained at the node level
A failing agent writes an error flag into state. The graph keeps running and the downstream consumer handles the missing data.
async def analyze_jd(self, state: CVAnalysisState) -> CVAnalysisState:
jd = state.get("job_description", "") or ""
if len(jd.strip()) < 10:
state["job_analysis"] = JobAnalysis().model_dump()
return state
try:
raw = await self.llm.generate_text(build_prompt(jd))
cleaned = clean_json_response(raw)
parsed = JobAnalysis.model_validate_json(cleaned)
state["job_analysis"] = parsed.model_dump()
except ValidationError as e:
state["job_analysis"] = JobAnalysis().model_dump()
state["error"] = f"JD analysis failed: {e}"
return stateA node that fails never raises out of the graph. It writes a fallback into state and records the failure. Downstream agents read the safe default and keep working.
Yes, for transient errors like rate limits or timeouts. Wrap the LLM call in a small retry helper with exponential backoff. Reserve fallbacks for the cases where retries still fail or where the input itself is bad. The graph treats both cases uniformly by writing a safe default into state.
Quiz: Quiz
Loading practice…