mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
427 KiB
427 KiB
In [3]:
%%capture --no-stderr
%pip install -U langgraphIn [1]:
from operator import add
from typing import List, TypedDict, Optional, Annotated, Dict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
# The structure of the logs
class Logs(TypedDict):
id: str
question: str
docs: Optional[List]
answer: str
grade: Optional[int]
grader: Optional[str]
feedback: Optional[str]
# Failure Analysis Sub-graph
class FailureAnalysisState(TypedDict):
docs: List[Logs]
failures: List[Logs]
fa_summary: str
def get_failures(state):
docs = state["docs"]
failures = [doc for doc in docs if "grade" in doc]
return {"failures": failures}
def generate_summary(state):
failures = state["failures"]
# Add fxn: fa_summary = summarize(failures)
fa_summary = "Poor quality retrieval of Chroma documentation."
return {"fa_summary": fa_summary}
fa_builder = StateGraph(FailureAnalysisState)
fa_builder.add_node("get_failures", get_failures)
fa_builder.add_node("generate_summary", generate_summary)
fa_builder.add_edge(START, "get_failures")
fa_builder.add_edge("get_failures", "generate_summary")
fa_builder.add_edge("generate_summary", END)
# Summarization subgraph
class QuestionSummarizationState(TypedDict):
docs: List[Logs]
qs_summary: str
report: str
def generate_summary(state):
docs = state["docs"]
# Add fxn: summary = summarize(docs)
summary = "Questions focused on usage of ChatOllama and Chroma vector store."
return {"qs_summary": summary}
def send_to_slack(state):
qs_summary = state["qs_summary"]
# Add fxn: report = report_generation(qs_summary)
report = "foo bar baz"
return {"report": report}
def format_report_for_slack(state):
report = state["report"]
# Add fxn: formatted_report = report_format(report)
formatted_report = "foo bar"
return {"report": formatted_report}
qs_builder = StateGraph(QuestionSummarizationState)
qs_builder.add_node("generate_summary", generate_summary)
qs_builder.add_node("send_to_slack", send_to_slack)
qs_builder.add_node("format_report_for_slack", format_report_for_slack)
qs_builder.add_edge(START, "generate_summary")
qs_builder.add_edge("generate_summary", "send_to_slack")
qs_builder.add_edge("send_to_slack", "format_report_for_slack")
qs_builder.add_edge("format_report_for_slack", END)In [2]:
# Dummy logs
question_answer = Logs(
id="1",
question="How can I import ChatOllama?",
answer="To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'",
)
question_answer_feedback = Logs(
id="2",
question="How can I use Chroma vector store?",
answer="To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).",
grade=0,
grader="Document Relevance Recall",
feedback="The retrieved documents discuss vector stores in general, but not Chroma specifically",
)
# Entry Graph
class EntryGraphState(TypedDict):
raw_logs: Annotated[List[Dict], add]
docs: Annotated[List[Logs], add] # This will be used in sub-graphs
fa_summary: str # This will be generated in the FA sub-graph
report: str # This will be generated in the QS sub-graph
def convert_logs_to_docs(state):
# Get logs
raw_logs = state["raw_logs"]
docs = [question_answer, question_answer_feedback]
return {"docs": docs}
entry_builder = StateGraph(EntryGraphState)
entry_builder.add_node("convert_logs_to_docs", convert_logs_to_docs)
entry_builder.add_node("question_summarization", qs_builder.compile())
entry_builder.add_node("failure_analysis", fa_builder.compile())
entry_builder.add_edge(START, "convert_logs_to_docs")
entry_builder.add_edge("convert_logs_to_docs", "failure_analysis")
entry_builder.add_edge("convert_logs_to_docs", "question_summarization")
entry_builder.add_edge("failure_analysis", END)
entry_builder.add_edge("question_summarization", END)
graph = entry_builder.compile()
from IPython.display import Image, display
# Setting xray to 1 will show the internal structure of the nested graph
display(Image(graph.get_graph(xray=1).draw_mermaid_png()))In [3]:
raw_logs = [{"foo": "bar"}, {"foo": "baz"}]
graph.invoke({"raw_logs": raw_logs}, debug=False)Out [3]:
{'raw_logs': [{'foo': 'bar'}, {'foo': 'baz'}],
'docs': [{'id': '1',
'question': 'How can I import ChatOllama?',
'answer': "To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'"},
{'id': '2',
'question': 'How can I use Chroma vector store?',
'answer': 'To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',
'grade': 0,
'grader': 'Document Relevance Recall',
'feedback': 'The retrieved documents discuss vector stores in general, but not Chroma specifically'},
{'id': '1',
'question': 'How can I import ChatOllama?',
'answer': "To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'"},
{'id': '2',
'question': 'How can I use Chroma vector store?',
'answer': 'To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',
'grade': 0,
'grader': 'Document Relevance Recall',
'feedback': 'The retrieved documents discuss vector stores in general, but not Chroma specifically'},
{'id': '1',
'question': 'How can I import ChatOllama?',
'answer': "To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'"},
{'id': '2',
'question': 'How can I use Chroma vector store?',
'answer': 'To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',
'grade': 0,
'grader': 'Document Relevance Recall',
'feedback': 'The retrieved documents discuss vector stores in general, but not Chroma specifically'}],
'fa_summary': 'Poor quality retrieval of Chroma documentation.',
'report': 'foo bar'}In [15]:
from typing import Annotated
from typing_extensions import TypedDict
def reduce_list(left: list | None, right: list | None) -> list:
if not left:
left = []
if not right:
right = []
return left + right
class ChildState(TypedDict):
name: str
path: Annotated[list[str], reduce_list]
class ParentState(TypedDict):
name: str
path: Annotated[list[str], reduce_list]
child_builder = StateGraph(ChildState)
child_builder.add_node("child_start", lambda state: {"path": ["child_start"]})
child_builder.add_edge(START, "child_start")
child_builder.add_node("child_middle", lambda state: {"path": ["child_middle"]})
child_builder.add_node("child_end", lambda state: {"path": ["child_end"]})
child_builder.add_edge("child_start", "child_middle")
child_builder.add_edge("child_middle", "child_end")
child_builder.add_edge("child_end", END)
builder = StateGraph(ParentState)
builder.add_node("grandparent", lambda state: {"path": ["grandparent"]})
builder.add_edge(START, "grandparent")
builder.add_node("parent", lambda state: {"path": ["parent"]})
builder.add_node("child", child_builder.compile())
builder.add_node("sibling", lambda state: {"path": ["sibling"]})
builder.add_node("fin", lambda state: {"path": ["fin"]})
# Add connections
builder.add_edge("grandparent", "parent")
builder.add_edge("parent", "child")
builder.add_edge("parent", "sibling")
builder.add_edge("child", "fin")
builder.add_edge("sibling", "fin")
builder.add_edge("fin", END)
graph = builder.compile()In [3]:
from IPython.display import Image, display
# Setting xray to 1 will show the internal structure of the nested graph
display(Image(graph.get_graph(xray=1).draw_mermaid_png()))In [17]:
graph.invoke({"name": "test"}, debug=True)Out [17]:
[36;1m[1;3m[0:tasks][0m [1mStarting step 0 with 1 task: [0m- [32;1m[1;3m__start__[0m -> {'name': 'test'} [36;1m[1;3m[0:writes][0m [1mFinished step 0 with writes to 1 channel: [0m- [33;1m[1;3mname[0m -> 'test' [36;1m[1;3m[0:checkpoint][0m [1mState at the end of step 0: [0m{'name': 'test', 'path': []} [36;1m[1;3m[1:tasks][0m [1mStarting step 1 with 1 task: [0m- [32;1m[1;3mgrandparent[0m -> {'name': 'test', 'path': []} [36;1m[1;3m[1:writes][0m [1mFinished step 1 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['grandparent'] [36;1m[1;3m[1:checkpoint][0m [1mState at the end of step 1: [0m{'name': 'test', 'path': ['grandparent']} [36;1m[1;3m[2:tasks][0m [1mStarting step 2 with 1 task: [0m- [32;1m[1;3mparent[0m -> {'name': 'test', 'path': ['grandparent']} [36;1m[1;3m[2:writes][0m [1mFinished step 2 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['parent'] [36;1m[1;3m[2:checkpoint][0m [1mState at the end of step 2: [0m{'name': 'test', 'path': ['grandparent', 'parent']} [36;1m[1;3m[3:tasks][0m [1mStarting step 3 with 2 tasks: [0m- [32;1m[1;3mchild[0m -> {'name': 'test', 'path': ['grandparent', 'parent']} - [32;1m[1;3msibling[0m -> {'name': 'test', 'path': ['grandparent', 'parent']} [36;1m[1;3m[3:writes][0m [1mFinished step 3 with writes to 2 channels: [0m- [33;1m[1;3mname[0m -> 'test' - [33;1m[1;3mpath[0m -> ['grandparent', 'parent', 'child_start', 'child_middle', 'child_end'], ['sibling'] [36;1m[1;3m[3:checkpoint][0m [1mState at the end of step 3: [0m{'name': 'test', 'path': ['grandparent', 'parent', 'grandparent', 'parent', 'child_start', 'child_middle', 'child_end', 'sibling']} [36;1m[1;3m[4:tasks][0m [1mStarting step 4 with 1 task: [0m- [32;1m[1;3mfin[0m -> {'name': 'test', 'path': ['grandparent', 'parent', 'grandparent', 'parent', 'child_start', 'child_middle', 'child_end', 'sibling']} [36;1m[1;3m[4:writes][0m [1mFinished step 4 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['fin'] [36;1m[1;3m[4:checkpoint][0m [1mState at the end of step 4: [0m{'name': 'test', 'path': ['grandparent', 'parent', 'grandparent', 'parent', 'child_start', 'child_middle', 'child_end', 'sibling', 'fin']}
{'name': 'test',
'path': ['grandparent',
'parent',
'grandparent',
'parent',
'child_start',
'child_middle',
'child_end',
'sibling',
'fin']}In [23]:
import uuid
def reduce_list(left: list | None, right: list | None) -> list:
"""Append the right-hand list, replacing any elements with the same id in the left-hand list."""
if not left:
left = []
if not right:
right = []
left_, right_ = [], []
for orig, new in [(left, left_), (right, right_)]:
for val in orig:
if not isinstance(val, dict):
val = {"val": val}
if "id" not in val:
val["id"] = str(uuid.uuid4())
new.append(val)
# Merge the two lists
left_idx_by_id = {val["id"]: i for i, val in enumerate(left_)}
merged = left_.copy()
for val in right_:
if (existing_idx := left_idx_by_id.get(val["id"])) is not None:
merged[existing_idx] = val
else:
merged.append(val)
return merged
class ChildState(TypedDict):
name: str
path: Annotated[list[str], reduce_list]
class ParentState(TypedDict):
name: str
path: Annotated[list[str], reduce_list]In [24]:
child_builder = StateGraph(ChildState)
child_builder.add_node("child_start", lambda state: {"path": ["child_start"]})
child_builder.add_edge(START, "child_start")
child_builder.add_node("child_middle", lambda state: {"path": ["child_middle"]})
child_builder.add_node("child_end", lambda state: {"path": ["child_end"]})
child_builder.add_edge("child_start", "child_middle")
child_builder.add_edge("child_middle", "child_end")
child_builder.add_edge("child_end", END)
builder = StateGraph(ParentState)
builder.add_node("grandparent", lambda state: {"path": ["grandparent"]})
builder.add_edge(START, "grandparent")
builder.add_node("parent", lambda state: {"path": ["parent"]})
builder.add_node("child", child_builder.compile())
builder.add_node("sibling", lambda state: {"path": ["sibling"]})
builder.add_node("fin", lambda state: {"path": ["fin"]})
# Add connections
builder.add_edge("grandparent", "parent")
builder.add_edge("parent", "child")
builder.add_edge("parent", "sibling")
builder.add_edge("child", "fin")
builder.add_edge("sibling", "fin")
builder.add_edge("fin", END)
graph = builder.compile()In [25]:
from IPython.display import Image, display
# Setting xray to 1 will show the internal structure of the nested graph
display(Image(graph.get_graph(xray=1).draw_mermaid_png()))In [26]:
graph.invoke({"name": "test"}, debug=True)Out [26]:
[36;1m[1;3m[0:tasks][0m [1mStarting step 0 with 1 task: [0m- [32;1m[1;3m__start__[0m -> {'name': 'test'} [36;1m[1;3m[0:writes][0m [1mFinished step 0 with writes to 1 channel: [0m- [33;1m[1;3mname[0m -> 'test' [36;1m[1;3m[0:checkpoint][0m [1mState at the end of step 0: [0m{'name': 'test', 'path': []} [36;1m[1;3m[1:tasks][0m [1mStarting step 1 with 1 task: [0m- [32;1m[1;3mgrandparent[0m -> {'name': 'test', 'path': []} [36;1m[1;3m[1:writes][0m [1mFinished step 1 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['grandparent'] [36;1m[1;3m[1:checkpoint][0m [1mState at the end of step 1: [0m{'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}]} [36;1m[1;3m[2:tasks][0m [1mStarting step 2 with 1 task: [0m- [32;1m[1;3mparent[0m -> {'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}]} [36;1m[1;3m[2:writes][0m [1mFinished step 2 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['parent'] [36;1m[1;3m[2:checkpoint][0m [1mState at the end of step 2: [0m{'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}]} [36;1m[1;3m[3:tasks][0m [1mStarting step 3 with 2 tasks: [0m- [32;1m[1;3mchild[0m -> {'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}]} - [32;1m[1;3msibling[0m -> {'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}]} [36;1m[1;3m[3:writes][0m [1mFinished step 3 with writes to 2 channels: [0m- [33;1m[1;3mname[0m -> 'test' - [33;1m[1;3mpath[0m -> [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}, {'id': 'd1c1bab0-6e19-4846-a470-e9cc2eb85088', 'val': 'child_start'}, {'id': 'e0fcb647-1e9e-4ae4-b560-0046515d5783', 'val': 'child_middle'}, {'id': '669dd810-360f-4694-a9f3-49597f23376a', 'val': 'child_end'}], ['sibling'] [36;1m[1;3m[3:checkpoint][0m [1mState at the end of step 3: [0m{'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}, {'id': 'd1c1bab0-6e19-4846-a470-e9cc2eb85088', 'val': 'child_start'}, {'id': 'e0fcb647-1e9e-4ae4-b560-0046515d5783', 'val': 'child_middle'}, {'id': '669dd810-360f-4694-a9f3-49597f23376a', 'val': 'child_end'}, {'id': '137dbc2f-b33c-4ea4-8b04-a62215ba9718', 'val': 'sibling'}]} [36;1m[1;3m[4:tasks][0m [1mStarting step 4 with 1 task: [0m- [32;1m[1;3mfin[0m -> {'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}, {'id': 'd1c1bab0-6e19-4846-a470-e9cc2eb85088', 'val': 'child_start'}, {'id': 'e0fcb647-1e9e-4ae4-b560-0046515d5783', 'val': 'child_middle'}, {'id': '669dd810-360f-4694-a9f3-49597f23376a', 'val': 'child_end'}, {'id': '137dbc2f-b33c-4ea4-8b04-a62215ba9718', 'val': 'sibling'}]} [36;1m[1;3m[4:writes][0m [1mFinished step 4 with writes to 1 channel: [0m- [33;1m[1;3mpath[0m -> ['fin'] [36;1m[1;3m[4:checkpoint][0m [1mState at the end of step 4: [0m{'name': 'test', 'path': [{'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49', 'val': 'grandparent'}, {'id': '2a6f0263-3949-4e47-a210-57f817e6097d', 'val': 'parent'}, {'id': 'd1c1bab0-6e19-4846-a470-e9cc2eb85088', 'val': 'child_start'}, {'id': 'e0fcb647-1e9e-4ae4-b560-0046515d5783', 'val': 'child_middle'}, {'id': '669dd810-360f-4694-a9f3-49597f23376a', 'val': 'child_end'}, {'id': '137dbc2f-b33c-4ea4-8b04-a62215ba9718', 'val': 'sibling'}, {'id': 'a4328c5f-845a-43de-b3d7-53a39208e316', 'val': 'fin'}]}
{'name': 'test',
'path': [{'val': 'grandparent', 'id': '79a81f03-d16d-4d12-94a6-4ba29fc9ce49'},
{'val': 'parent', 'id': '2a6f0263-3949-4e47-a210-57f817e6097d'},
{'val': 'child_start', 'id': 'd1c1bab0-6e19-4846-a470-e9cc2eb85088'},
{'val': 'child_middle', 'id': 'e0fcb647-1e9e-4ae4-b560-0046515d5783'},
{'val': 'child_end', 'id': '669dd810-360f-4694-a9f3-49597f23376a'},
{'val': 'sibling', 'id': '137dbc2f-b33c-4ea4-8b04-a62215ba9718'},
{'val': 'fin', 'id': 'a4328c5f-845a-43de-b3d7-53a39208e316'}]}
