from langgraph.graph import StateGraph, END
from typing import TypedDict, List
from langchain_core.documents import Document
class CRAGState(TypedDict):
question: str
documents: List[Document]
filtered_documents: List[Document]
web_results: List[Document]
grade: str
generation: str
def retrieve(state: CRAGState) -> dict:
docs = retriever.invoke(state["question"])
return {"documents": docs}
def evaluate(state: CRAGState) -> dict:
grade = grade_retrieval(state["question"], state["documents"])
return {"grade": grade}
def route_by_grade(state: CRAGState) -> str:
if state["grade"] == "correct":
return "generate"
elif state["grade"] == "incorrect":
return "web_search"
else: # ambiguous
return "filter_and_supplement"
def filter_documents(state: CRAGState) -> dict:
filtered = [doc for doc in state["documents"]
if evaluate_document_relevance(state["question"], doc.page_content)
in ("relevant", "partially_relevant")]
return {"filtered_documents": filtered}
def web_search(state: CRAGState) -> dict:
# Optional: search the web for additional context
# Only if external search is configured
web_docs = web_search_tool.invoke(state["question"])
return {"web_results": web_docs}
def generate(state: CRAGState) -> dict:
# Use filtered docs if available, otherwise original docs
docs = state.get("filtered_documents") or state["documents"]
web = state.get("web_results", [])
all_docs = docs + web
context = "\n\n".join(d.page_content for d in all_docs)
answer = llm.invoke(
f"Context: {context}\n\nQuestion: {state['question']}\n\nAnswer:"
).content
return {"generation": answer}
# Build CRAG workflow
workflow = StateGraph(CRAGState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("evaluate", evaluate)
workflow.add_node("filter_and_supplement", filter_documents)
workflow.add_node("web_search", web_search)
workflow.add_node("generate", generate)
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "evaluate")
workflow.add_conditional_edges("evaluate", route_by_grade, {
"generate": "generate",
"web_search": "web_search",
"filter_and_supplement": "filter_and_supplement",
})
workflow.add_edge("filter_and_supplement", "generate")
workflow.add_edge("web_search", "generate")
workflow.add_edge("generate", END)
crag_app = workflow.compile()