async def output_node(state: DocMindState) -> DocMindState: if state["judge_verdict"] and state["judge_verdict"].get("should_return", False): state["final_output"] = state["generated_response"] else: state["final_output"] = "Unable to provide a confident response. Please rephrase your query." state["node_history"] = state.get("node_history", []) + ["output"] return state
def should_retry(state: DocMindState) -> str: verdict = state.get("judge_verdict", {}) retry_count = state.get("retry_count", 0) # Retry if hallucinated and haven't exceeded max retries (2 attempts max) if verdict.get("is_hallucinated", False) and retry_count < 2: log_retry_attempt(retry_count + 1, 2) return "retry" return "output"
Returns:
"retry" → Route back to retrieve node
"output" → Route to output node
Maximum 2 retry attempts (total 3 retrieval passes). From README.md:47-48: “Maximum 2 retries to avoid infinite loops. If retrieval fails twice, the information probably doesn’t exist in the documents.”
workflow.add_edge("decompose", "retrieve") # Always goes to retrieveworkflow.add_edge("retrieve", "generate") # Always goes to generateworkflow.add_edge("generate", "judge") # Always goes to judgeworkflow.add_edge("output", END) # Always ends
Conditional edge (dynamic routing):
workflow.add_conditional_edges( "judge", # Source node should_retry, # Decision function { "retry": "retrieve", # If function returns "retry" "output": "output" # If function returns "output" })
query = "What are the late payment penalties?"# Initial statestate = { "query": query, "retry_count": 0, "node_history": []}# Execute workflowresult = await workflow.ainvoke(state)print(result["node_history"])# Output: ["decompose", "retrieve", "generate", "judge", "output"]print(result["final_output"])# Output: "If payment is not received within thirty (30) days, Client shall be assessed a late fee of 1.5% per month... (See Late Payment Penalties, page 8)"
query = "What is the warranty period?" # Not in contract# First attempt: Retrieves irrelevant sections → Judge rejects# Second attempt: Retrieves different sections → Judge rejects# Third attempt: Skip (max retries reached)print(result["node_history"])# Output: ["decompose", "retrieve", "generate", "judge", "retrieve", "generate", "judge", "output"]print(result["retry_count"])# Output: 2print(result["final_output"])# Output: "Unable to provide a confident response. Please rephrase your query."
Modularity: “Why separate modules: allows isolated unit tests (test judge without retriever), component substitution (swap mock for real), clear responsibilities.”
“If the judge detects hallucination, the system retries retrieval. Maximum 2 retries to avoid infinite loops. If retrieval fails twice, the information probably doesn’t exist in the documents.”