mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 14:42:28 +02:00
224 KiB
224 KiB
In [ ]:
%%capture --no-stderr
%pip install -U langgraph langsmith
# Used for this tutorial; not a requirement for LangGraph
%pip install -U langchain langchain_anthropicIn [1]:
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY")In [2]:
_set_env("LANGSMITH_API_KEY")
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "LangGraph Tutorial"In [3]:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
# Messages have the type "list". The `add_messages` function
# in the annotation defines how this state key should be updated
# (in this case, it appends messages to the list, rather than overwriting them)
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)In [4]:
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-haiku-20240307")
def chatbot(state: State):
return {"messages": [llm.invoke(state["messages"])]}
# The first argument is the unique node name
# The second argument is the function or object that will be called whenever
# the node is used.
graph_builder.add_node("chatbot", chatbot)In [5]:
graph_builder.set_entry_point("chatbot")In [6]:
graph_builder.set_finish_point("chatbot")In [7]:
graph = graph_builder.compile()In [8]:
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except:
# This requires some extra dependencies and is optional
passIn [9]:
while True:
user_input = input("User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
for event in graph.stream({"messages": ("user", user_input)}):
for value in event.values():
print("Assistant:", value["messages"][-1].content)User: Hi there, I'm Will!
Assistant: It's nice to meet you, Will! I'm Claude, an AI assistant created by Anthropic. I'm here to help with any questions or tasks you might have. Please let me know if there's anything I can assist you with.
User: What's my name?
Assistant: I'm afraid I don't actually know your name. As an AI assistant, I don't have specific information about you or other individual users. I can only respond based on the context provided to me during our conversation.
User: q
Goodbye!
In [10]:
from typing import Annotated
from langchain_anthropic import ChatAnthropic
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
llm = ChatAnthropic(model="claude-3-haiku-20240307")
def chatbot(state: State):
return {"messages": [llm.invoke(state["messages"])]}
# The first argument is the unique node name
# The second argument is the function or object that will be called whenever
# the node is used.
graph_builder.add_node("chatbot", chatbot)
graph_builder.set_entry_point("chatbot")
graph_builder.set_finish_point("chatbot")
graph = graph_builder.compile()In [ ]:
%%capture --no-stderr
%pip install -U tavily-pythonIn [11]:
_set_env("TAVILY_API_KEY")In [12]:
from langchain_community.tools.tavily_search import TavilySearchResults
tool = TavilySearchResults(max_results=2)
tools = [tool]
tool.invoke("What's a 'node' in LangGraph?")Out [12]:
[{'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',
'content': 'Nodes: Nodes are the building blocks of your LangGraph. Each node represents a function or a computation step. You define nodes to perform specific tasks, such as processing input, making ...'},
{'url': 'https://www.analyticsvidhya.com/blog/2024/03/build-an-ai-coding-agent-with-langgraph-by-langchain/',
'content': 'LangGraph is an extension of LangChain, which allows us to build cyclic, stateful, multi-actor agent systems. It implements a graph structure with nodes and edges. The nodes are functions or tools, and the edges are the connections between nodes. Edges are of two types: conditional and normal.'}]In [13]:
from typing import Annotated
from langchain_anthropic import ChatAnthropic
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
llm = ChatAnthropic(model="claude-3-haiku-20240307")
# Modification: tell the LLM which tools it can call
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)/Users/wfh/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: The function `bind_tools` is in beta. It is actively being worked on, so the API may change. warn_beta(
In [14]:
from langgraph.prebuilt import ToolNode
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)In [15]:
from langgraph.prebuilt import tools_condition
# The `tools_condition` function returns "action" if the chatbot asks to use a tool, and "__end__" if
# it is fine directly responding. This conditional routing defines the main agent loop.
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
# The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node
# It defaults to the identity function, but if you
# want to use a node named something else apart from "action",
# You can update the value of the dictionary to something else
# e.g., "action": "my_tools"
{"action": "action", "__end__": "__end__"},
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()In [16]:
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except:
# This requires some extra dependencies and is optional
passIn [18]:
from langchain_core.messages import BaseMessage
while True:
user_input = input("User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
for event in graph.stream({"messages": [("user", user_input)]}):
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)User: What sets langgraph apart?
Assistant: [{'id': 'toolu_01H54JZhgQMGzbKq54PoNQL6', 'input': {'query': 'langgraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Assistant: [{"url": "https://github.com/langchain-ai/langgraph/blob/main/README.md", "content": "Define the nodes\nWe now need to define a few different nodes in our graph.\\nIn langgraph, a node can be either a function or a runnable.\\nThere are two main nodes we need for this:\nWe will also need to define some edges.\\nSome of these edges may be conditional.\\nThe reason they are conditional is that based on the output of a node, one of several paths may be taken.\\nThe path that is taken is not known until that node is run (the LLM decides).\n LangChain.\\nIt extends the LangChain Expression Language with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner.\\nIt is inspired by Pregel and Apache Beam.\\nThe current interface exposed is one inspired by NetworkX.\nThe main use is for adding cycles to your LLM application.\\nCrucially, this is NOT a DAG framework.\\nIf you want to build a DAG, you should use just use LangChain Expression Language.\n This is a special node representing the end of the graph.\\nThis means that anything passed to this node will be the final output of the graph.\\nIt can be used in two places:\nWhen to Use\nWhen should you use this versus LangChain Expression Language?\n This method adds a node to the graph.\\nIt takes two arguments:\n.add_edge\nCreates an edge from one node to the next.\\nThis means that output of the first node will be passed to the next node.\\nIt takes two arguments.\n Assuming you have done the above Quick Start, you can build off it like:\nHere, we manually define the first tool call that we will make.\\nNotice that it does that same thing as agent would have done (adds the agent_outcome key).\\nThis is so that we can easily plug it in.\n"}, {"url": "https://blog.langchain.dev/langgraph-multi-agent-workflows/", "content": "As a part of the launch, we highlighted two simple runtimes: one that is the equivalent of the AgentExecutor in langchain, and a second that was a version of that aimed at message passing and chat models.\n It's important to note that these three examples are only a few of the possible examples we could highlight - there are almost assuredly other examples out there and we look forward to seeing what the community comes up with!\n LangGraph: Multi-Agent Workflows\nLinks\nLast week we highlighted LangGraph - a new package (available in both Python and JS) to better enable creation of LLM workflows containing cycles, which are a critical component of most agent runtimes. \"\nAnother key difference between Autogen and LangGraph is that LangGraph is fully integrated into the LangChain ecosystem, meaning you take fully advantage of all the LangChain integrations and LangSmith observability.\n As part of this launch, we're also excited to highlight a few applications built on top of LangGraph that utilize the concept of multiple agents.\n"}]
Assistant: Based on the search results, here are the key things that set LangGraph apart:
1. LangGraph extends the LangChain Expression Language by enabling the coordination of multiple "chains" or "actors" across multiple steps of computation in a cyclic manner. This allows for the creation of more complex workflows that involve cycles, rather than just directed acyclic graphs (DAGs).
2. LangGraph is inspired by frameworks like Pregel and Apache Beam, which support cyclic computations. In contrast, LangChain Expression Language is more focused on building DAG-based workflows.
3. The main use case for LangGraph is to add cycles to LLM applications, where the path through the workflow is not known until runtime, as the LLM decides which path to take.
4. LangGraph provides a custom interface inspired by NetworkX for defining nodes (functions or runnables) and edges (including conditional edges) in the workflow graph.
5. LangGraph is fully integrated with the LangChain ecosystem, allowing it to leverage all the integrations and observability features provided by LangChain.
In summary, LangGraph extends the capabilities of LangChain by enabling the creation of more complex, cyclic workflows involving multiple agents or components, rather than being limited to directed acyclic graphs.
User: q
Goodbye!
In [19]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile()In [20]:
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")In [21]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")In [22]:
graph = graph_builder.compile(checkpointer=memory)In [23]:
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except:
# This requires some extra dependencies and is optional
passIn [24]:
config = {"configurable": {"thread_id": "1"}}In [25]:
user_input = "Hi there! My name is Will."
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream({"messages": [("user", user_input)]}, config)
for event in events:
for value in event.values():
if isinstance(value, BaseMessage):
print("Assistant:", value.content)In [26]:
user_input = "Remember my name?"
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream({"messages": [("user", user_input)]}, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: Yes, I remember your name is Will. It's nice to meet you, Will!
In [27]:
# The only difference is we change the `thread_id` here to "2" instead of "1"
events = graph.stream(
{"messages": [("user", user_input)]}, {"configurable": {"thread_id": "2"}}
)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: I'm afraid I don't actually have any information about your name stored. As an AI assistant created by Anthropic, I don't have a persistent memory of previous conversations or personal details about users. I'm happy to try to assist you, but I don't have the capability to "remember" your name from previous interactions. Could you please let me know your name again so I can refer to you appropriately?
In [28]:
snapshot = graph.get_state(config)
snapshotOut [28]:
StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Will.', id='4de66d5f-2bcf-451e-8913-8d0939bdf3aa'), AIMessage(content="It's nice to meet you, Will! I'm an AI assistant created by Anthropic to be helpful, harmless, and honest. I'm happy to chat with you about anything you'd like - feel free to ask me questions or let me know if there's anything I can assist with.", response_metadata={'id': 'msg_01NK3eHWMzScBWg4aPhxHgBe', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 375, 'output_tokens': 64}}, id='run-f6ad0a33-e5dd-4f09-8947-23e48805e483-0'), HumanMessage(content='Remember my name?', id='eb778872-1114-4974-a0c8-37c8d8d8eaa3'), AIMessage(content="Yes, I remember your name is Will. It's nice to meet you, Will!", response_metadata={'id': 'msg_01Q9FjFhHs63kgEytVmZeova', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 446, 'output_tokens': 21}}, id='run-7e4976a1-5a21-4b7d-ba4d-55da1c33fa72-0')]}, next=(), config={'configurable': {'thread_id': '1', 'thread_ts': '2024-04-18T07:28:57.722029+00:00'}}, parent_config=None)In [29]:
snapshot.next # (since the graph ended this turn, `next` is empty. If you fetch a state from within a graph invocation, next tells which node will execute next)Out [29]:
()
In [30]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import MessageGraph, StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")
graph = graph_builder.compile(checkpointer=memory)In [31]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import MessageGraph, StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
memory = SqliteSaver.from_conn_string(":memory:")
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")In [32]:
graph = graph_builder.compile(
checkpointer=memory,
# This is new!
interrupt_before=["action"],
# Note: can also interrupt __after__ actions, if desired.
# interrupt_after=["action"]
)In [33]:
user_input = "I'm learning LangGraph. Could you do some research on it for me?"
config = {"configurable": {"thread_id": "1"}}
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream({"messages": [("user", user_input)]}, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: [{'text': "Okay, let's look into LangGraph for you. Here is a summary of the key information I was able to find:", 'type': 'text'}, {'id': 'toolu_01XayS5zo1ZHaQMYpWX5x8fQ', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
In [34]:
snapshot = graph.get_state(config)
snapshot.nextOut [34]:
('action',)In [35]:
existing_message = snapshot.values["messages"][-1]
existing_message.tool_callsOut [35]:
[{'name': 'tavily_search_results_json',
'args': {'query': 'LangGraph'},
'id': 'toolu_01XayS5zo1ZHaQMYpWX5x8fQ'}]In [36]:
# `None` will append nothing new to the current state, letting it resume as if it had never been interrupted
events = graph.stream(None, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: [{"url": "https://blog.langchain.dev/langgraph/", "content": "Some of the things we are looking to implement in the near future:\nIf any of these resonate with you, please feel free to add an example notebook in the LangGraph repo, or reach out to us at hello@langchain.dev for more involved collaboration!\n See this notebook for how to get started\nModifications\nOne of the big benefits of LangGraph is that it exposes the logic of AgentExecutor in a far more natural and modifiable way. An example of this could be that after a model is called we either exit the graph and return to the user, or we call a tool - depending on what a user decides! This function/LCEL should accept a dictionary in the same form as the State object as input, and output a dictionary with keys of the State object to update.\n In this case, it's often ideal if the LLM can reason that the results returned from the retriever are poor, and maybe issue a second (more refined) query to the retriever, and use those results instead."}, {"url": "https://blog.langchain.dev/langgraph-multi-agent-workflows/", "content": "As a part of the launch, we highlighted two simple runtimes: one that is the equivalent of the AgentExecutor in langchain, and a second that was a version of that aimed at message passing and chat models.\n It's important to note that these three examples are only a few of the possible examples we could highlight - there are almost assuredly other examples out there and we look forward to seeing what the community comes up with!\n LangGraph: Multi-Agent Workflows\nLinks\nLast week we highlighted LangGraph - a new package (available in both Python and JS) to better enable creation of LLM workflows containing cycles, which are a critical component of most agent runtimes. \"\nAnother key difference between Autogen and LangGraph is that LangGraph is fully integrated into the LangChain ecosystem, meaning you take fully advantage of all the LangChain integrations and LangSmith observability.\n As part of this launch, we're also excited to highlight a few applications built on top of LangGraph that utilize the concept of multiple agents.\n"}]
Assistant: Based on the search results, LangGraph seems to be a new package developed as part of the LangChain ecosystem. It is designed to enable the creation of more advanced LLM workflows that can contain cycles, which is an important feature for agent-based models.
Some key points about LangGraph:
- It exposes the logic of the AgentExecutor in LangChain in a more modifiable way, allowing for customization of behavior after a model is called.
- It supports multi-agent workflows, enabling the creation of applications with multiple interacting agents.
- It is integrated with the broader LangChain ecosystem, allowing it to take advantage of existing LangChain integrations and observability tools.
The search results indicate that LangGraph is a relatively new development, and the blog post mentions there are plans to implement additional features in the near future. Overall, it seems like an interesting tool for building more sophisticated LLM-powered applications, especially those involving agent-based models or multi-agent interactions.
Let me know if you have any other questions! I'm happy to provide more details on LangGraph or do additional research if needed.
In [37]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import MessageGraph, StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")
memory = SqliteSaver.from_conn_string(":memory:")
graph = graph_builder.compile(
checkpointer=memory,
# This is new!
interrupt_before=["action"],
# Note: can also interrupt __after__ actions, if desired.
# interrupt_after=["action"]
)In [38]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import MessageGraph, StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
graph_builder.add_node("action", tool_node)
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
{"action": "action", "__end__": "__end__"},
)
graph_builder.add_edge("action", "chatbot")
graph_builder.set_entry_point("chatbot")
memory = SqliteSaver.from_conn_string(":memory:")
graph = graph_builder.compile(
checkpointer=memory,
# This is new!
interrupt_before=["action"],
# Note: can also interrupt __after__ actions, if desired.
# interrupt_after=["action"]
)
user_input = "I'm learning LangGraph. Could you do some research on it for me?"
config = {"configurable": {"thread_id": "1"}}
# The config is the **second positional argument** to stream() or invoke()!
events = graph.stream({"messages": [("user", user_input)]}, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: [{'text': "Okay, let's look up some information on LangGraph:", 'type': 'text'}, {'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
In [40]:
snapshot = graph.get_state(config)
existing_message = snapshot.values["messages"][-1]
existing_message.pretty_print()==================================[1m Ai Message [0m================================== [{'text': "Okay, let's look up some information on LangGraph:", 'type': 'text'}, {'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
In [41]:
from langchain_core.messages import AIMessage, ToolMessage
new_message = AIMessage(
content="LangGraph is a library for building stateful, multi-actor applications with LLMs."
)
new_message.pretty_print()
graph.update_state(
# Which state to update
config,
# The updated values to provide. The messages in our `State` are "append-only", meaning this will be appended
# to the existing state. We will review how to update existing messages in the next section!
{"messages": [new_message]},
)
print("\n\nLast 2 messages;")
print(graph.get_state(config).values["messages"][-2:])==================================[1m Ai Message [0m================================== LangGraph is a library for building stateful, multi-actor applications with LLMs. Last 2 messages; [AIMessage(content=[{'text': "Okay, let's look up some information on LangGraph:", 'type': 'text'}, {'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], response_metadata={'id': 'msg_01WgZjuqwL2gR1JnwJ2Ugxg8', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 384, 'output_tokens': 75}}, id='run-ba7aa4d5-6269-4682-888e-5f505fc7ec23-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph'}, 'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx'}]), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='e58607fd-43c4-416e-99dc-4c4575eddcdf')]
In [42]:
graph.update_state(
config,
{"messages": [AIMessage(content="I'm an AI expert!")]},
# Which node for this function to act as. It will automatically continue
# processing as if this node just ran.
as_node="chatbot",
)Out [42]:
{'configurable': {'thread_id': '1',
'thread_ts': '2024-04-18T07:45:58.218035+00:00'}}In [43]:
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except:
# This requires some extra dependencies and is optional
passIn [44]:
snapshot = graph.get_state(config)
print(snapshot.values["messages"][-3:])
print(snapshot.next)[AIMessage(content=[{'text': "Okay, let's look up some information on LangGraph:", 'type': 'text'}, {'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], response_metadata={'id': 'msg_01WgZjuqwL2gR1JnwJ2Ugxg8', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 384, 'output_tokens': 75}}, id='run-ba7aa4d5-6269-4682-888e-5f505fc7ec23-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph'}, 'id': 'toolu_016HGEqpk5m2wiAD98c3Gwcx'}]), AIMessage(content='LangGraph is a library for building stateful, multi-actor applications with LLMs.', id='e58607fd-43c4-416e-99dc-4c4575eddcdf'), AIMessage(content="I'm an AI expert!", id='82bbd2aa-b3c7-48cf-abeb-33f7938e98ae')]
()
In [45]:
user_input = "I'm learning LangGraph. Could you do some research on it for me?"
config = {"configurable": {"thread_id": "2"}} # we'll use thread_id = 2 here
events = graph.stream({"messages": [("user", user_input)]}, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: [{'id': 'toolu_01YQjNFesoHt4yFzg53xSLgb', 'input': {'query': 'LangGraph'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
In [46]:
from langchain_core.messages import AIMessage
snapshot = graph.get_state(config)
existing_message = snapshot.values["messages"][-1]
print("Original")
print(existing_message.tool_calls[0])
new_tool_call = existing_message.tool_calls[0].copy()
new_tool_call["args"]["query"] = "LangGraph human-in-the-loop workflow"
new_message = AIMessage(
content=existing_message.content,
tool_calls=[new_tool_call],
# Important! The ID is how LangGraph knows to REPLACE the message in the state rather than APPEND this messages
id=existing_message.id,
)
print("Updated")
print(new_message.tool_calls[0])
print("Message ID", new_message.id)
graph.update_state(config, {"messages": [new_message]})
print("\n\nTool calls")
graph.get_state(config).values["messages"][-1].tool_callsOut [46]:
Original
{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph'}, 'id': 'toolu_01YQjNFesoHt4yFzg53xSLgb'}
Updated
{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph human-in-the-loop workflow'}, 'id': 'toolu_01YQjNFesoHt4yFzg53xSLgb'}
Message ID run-4507ef72-6c8e-4fc3-9521-6e9e856c62a8-0
Tool calls
[{'name': 'tavily_search_results_json',
'args': {'query': 'LangGraph human-in-the-loop workflow'},
'id': 'toolu_01YQjNFesoHt4yFzg53xSLgb'}]In [47]:
events = graph.stream(None, config)
for event in events:
for value in event.values():
if isinstance(value["messages"][-1], BaseMessage):
print("Assistant:", value["messages"][-1].content)Assistant: [{"url": "https://langchain-ai.github.io/langgraph/how-tos/human-in-the-loop/", "content": "Human-in-the-loop\u00b6 When creating LangGraph agents, it is often nice to add a human in the loop component. This can be helpful when giving them access to tools. ... from langgraph.graph import MessageGraph, END # Define a new graph workflow = MessageGraph # Define the two nodes we will cycle between workflow. add_node (\"agent\", call_model) ..."}, {"url": "https://langchain-ai.github.io/langgraph/how-tos/agent_executor/human-in-the-loop/", "content": "Human in the Loop\u00b6 In this notebook we will go over how to add a human-in-the-loop workflow to the base agent executor. We will use the human to approve ... In langgraph, a node can be either a function or a runnable. There are two main nodes we need for this: The agent: responsible for deciding what (if any) actions to take. ..."}]
Assistant: Based on the search results, LangGraph appears to be a framework for building AI agents that can interact with humans in a loop. Some key points:
- LangGraph allows you to define "nodes" that represent different components of an AI agent, such as a language model, a tool executor, or a human-in-the-loop component.
- The human-in-the-loop feature lets you incorporate human feedback and approval into the agent's decision-making process. This can be helpful for sensitive applications where you want a human to review the agent's proposed actions.
- LangGraph provides examples of how to set up a workflow with an agent node and a human-in-the-loop node, allowing the agent to cycle between making decisions and getting human approval.
Overall, LangGraph seems to be a flexible framework for building AI agents that can interact with humans in a controlled and transparent way. It could be useful for applications where you want to leverage large language models while maintaining human oversight and control.
Let me know if you have any other questions! I'm happy to dig deeper into the LangGraph framework.
In [48]:
events = graph.stream(
{"messages": ("user", "Guess what I'm learning about these days?")}, config
)
for event in events:
for value in event.values():
if isinstance(value, BaseMessage):
print("Assistant:", value.content)In [49]:
from typing import Annotated, Union
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import MessageGraph, StateGraph
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
# This flag is new
ask_human: boolIn [50]:
from langchain_core.pydantic_v1 import BaseModel
class RequestAssistance(BaseModel):
"""Escalate the conversation to an expert. Use this if you are unable to assist directly or if the user requires support beyond your permissions.
To use this function, relay the user's 'request' so the expert can provide the right guidance.
"""
request: strIn [51]:
tool = TavilySearchResults(max_results=2)
tools = [tool]
llm = ChatAnthropic(model="claude-3-haiku-20240307")
# We can bind the llm to a tool definition, a pydantic model, or a json schema
llm_with_tools = llm.bind_tools(tools + [RequestAssistance])
def chatbot(state: State):
response = llm_with_tools.invoke(state["messages"])
ask_human = False
if (
response.tool_calls
and response.tool_calls[0]["name"] == RequestAssistance.__name__
):
ask_human = True
return {"messages": [response], "ask_human": ask_human}Warning:
Output truncated. This notebook contains too many cells to display efficiently.