mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
50 KiB
50 KiB
In [1]:
%%capture --no-stderr
%pip install --quiet -U langgraph langchain_openaiIn [2]:
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 [3]:
os.environ["LANGCHAIN_TRACING_V2"] = "true"
_set_env("LANGCHAIN_API_KEY")In [4]:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
# `add_messages`` essentially does this
# (with more robust handling)
# def add_messages(left: list, right: list):
# return left + right
class State(TypedDict):
messages: Annotated[list, add_messages]In [5]:
from langchain_core.tools import tool
@tool
def search(query: str):
"""Call to surf the web."""
# This is a placeholder for the actual implementation
return ["The weather is cloudy with a chance of meatballs."]
tools = [search]In [6]:
from langgraph.prebuilt import ToolNode
tool_node = ToolNode(tools)In [7]:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(temperature=0)In [8]:
model = model.bind_tools(tools)In [9]:
from typing import Literal
# Define the function that determines whether to continue or not
def should_continue(state: State) -> Literal["continue", "end"]:
last_message = state["messages"][-1]
# If there is no function call, then we finish
if not last_message.tool_calls:
return "end"
# Otherwise if there is, we continue
else:
return "continue"In [10]:
from langgraph.graph import END, StateGraph, START
# Define a new graph
workflow = StateGraph(State)
# Define the two nodes we will cycle between
def call_model(state: State) -> State:
return {"messages": model.invoke(state["messages"])}
workflow.add_node("agent", call_model)
workflow.add_node("action", tool_node)
# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.add_edge(START, "agent")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
# This means these are the edges taken after the `agent` node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
# Finally we pass in a mapping.
# The keys are strings, and the values are other nodes.
# END is a special node marking that the graph should finish.
# What will happen is we will call `should_continue`, and then the output of that
# will be matched against the keys in this mapping.
# Based on which one it matches, that node will then be called.
{
# If `tools`, then we call the tool node.
"continue": "action",
# Otherwise we finish.
"end": END,
},
)
# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge("action", "agent")In [11]:
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()In [12]:
# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable
app = workflow.compile(checkpointer=memory)In [13]:
from IPython.display import Image, display
try:
display(Image(app.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
passIn [14]:
from langchain_core.messages import HumanMessage
config = {"configurable": {"thread_id": "2"}}
input_message = HumanMessage(content="hi! I'm bob")
for event in app.stream({"messages": [input_message]}, config, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= hi! I'm bob ==================================[1m Ai Message [0m================================== Hello Bob! How can I assist you today?
In [15]:
app.get_state(config).valuesOut [15]:
{'messages': [HumanMessage(content="hi! I'm bob", id='cd7df241-189c-46a6-b822-69fcfafd8ad4'),
AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 11, 'prompt_tokens': 54, 'total_tokens': 65}}, id='run-cc3e7ee7-208e-446e-80cb-0349fe75319b-0')]}In [16]:
app.get_state(config).nextOut [16]:
()
In [17]:
config = {"configurable": {"thread_id": "2"}}
input_message = HumanMessage(content="what is the weather in sf currently")
for event in app.stream({"messages": [input_message]}, config, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= what is the weather in sf currently ==================================[1m Ai Message [0m================================== Tool Calls: search (call_UVPlm7YZ0xksC2VsYsPxN5ag) Call ID: call_UVPlm7YZ0xksC2VsYsPxN5ag Args: query: weather in San Francisco =================================[1m Tool Message [0m================================= Name: search ["The weather is cloudy with a chance of meatballs."] ==================================[1m Ai Message [0m================================== The weather in San Francisco is currently cloudy with a chance of meatballs.
In [18]:
app_w_interrupt = workflow.compile(checkpointer=memory, interrupt_before=["action"])In [19]:
config = {"configurable": {"thread_id": "4"}}
input_message = HumanMessage(content="what is the weather in sf currently")
for event in app_w_interrupt.stream(
{"messages": [input_message]}, config, stream_mode="values"
):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= what is the weather in sf currently ==================================[1m Ai Message [0m================================== Tool Calls: search (call_sxtKypVZlFrjzdOFYiCh8kin) Call ID: call_sxtKypVZlFrjzdOFYiCh8kin Args: query: weather in San Francisco
In [20]:
current_values = app_w_interrupt.get_state(config)
current_values.nextOut [20]:
('action',)In [21]:
current_values.values["messages"][-1].tool_callsOut [21]:
[{'name': 'search',
'args': {'query': 'weather in San Francisco'},
'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}]In [22]:
current_values.values["messages"][-1].tool_calls[0]["args"][
"query"
] = "weather in San Francisco today"In [23]:
app_w_interrupt.update_state(config, current_values.values)Out [23]:
{'configurable': {'thread_id': '4',
'thread_ts': '2024-05-07T17:30:25.205012+00:00'}}In [24]:
app_w_interrupt.get_state(config).valuesOut [24]:
{'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco today'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}])]}In [25]:
app_w_interrupt.get_state(config).nextOut [25]:
('action',)In [26]:
for event in app_w_interrupt.stream(None, config):
for v in event.values():
print(v){'messages': [ToolMessage(content='["The weather is cloudy with a chance of meatballs."]', name='search', id='9dce802a-9811-491f-a1d5-ace400fbcba0', tool_call_id='call_sxtKypVZlFrjzdOFYiCh8kin')]}
{'messages': AIMessage(content='The weather in San Francisco is currently cloudy with a chance of meatballs.', response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 92, 'total_tokens': 108}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-269cd84d-c5ba-438b-9abf-1d1389bed733-0')}
In [27]:
for state in app_w_interrupt.get_state_history(config):
print(state)
print("--")
if len(state.values["messages"]) == 2:
to_replay = stateStateSnapshot(values={'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco today'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}]), ToolMessage(content='["The weather is cloudy with a chance of meatballs."]', name='search', id='9dce802a-9811-491f-a1d5-ace400fbcba0', tool_call_id='call_sxtKypVZlFrjzdOFYiCh8kin'), AIMessage(content='The weather in San Francisco is currently cloudy with a chance of meatballs.', response_metadata={'finish_reason': 'stop', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 92, 'total_tokens': 108}}, id='run-269cd84d-c5ba-438b-9abf-1d1389bed733-0')]}, next=(), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.872512+00:00'}}, metadata={'source': 'loop', 'step': 4}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.228389+00:00'}})
--
StateSnapshot(values={'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco today'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}]), ToolMessage(content='["The weather is cloudy with a chance of meatballs."]', name='search', id='9dce802a-9811-491f-a1d5-ace400fbcba0', tool_call_id='call_sxtKypVZlFrjzdOFYiCh8kin')]}, next=('agent',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.228389+00:00'}}, metadata={'source': 'loop', 'step': 3}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.205012+00:00'}})
--
StateSnapshot(values={'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco today'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}])]}, next=('action',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.205012+00:00'}}, metadata={'source': 'update', 'step': 2}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.186985+00:00'}})
--
StateSnapshot(values={'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'), AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}])]}, next=('action',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:25.186985+00:00'}}, metadata={'source': 'loop', 'step': 1}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:24.675950+00:00'}})
--
StateSnapshot(values={'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144')]}, next=('agent',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:24.675950+00:00'}}, metadata={'source': 'loop', 'step': 0}, parent_config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:24.672976+00:00'}})
--
StateSnapshot(values={'messages': []}, next=('__start__',), config={'configurable': {'thread_id': '4', 'thread_ts': '2024-05-07T17:30:24.672976+00:00'}}, metadata={'source': 'input', 'step': -1}, parent_config=None)
--
In [28]:
to_replay.valuesOut [28]:
{'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'search'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin', 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'logprobs': None, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'token_usage': {'completion_tokens': 16, 'prompt_tokens': 56, 'total_tokens': 72}}, id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0', tool_calls=[{'name': 'search', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_sxtKypVZlFrjzdOFYiCh8kin'}])]}In [29]:
to_replay.nextOut [29]:
('action',)In [30]:
for event in app_w_interrupt.stream(None, to_replay.config):
for v in event.values():
print(v){'messages': [ToolMessage(content='["The weather is cloudy with a chance of meatballs."]', name='search', id='71e6f2b9-46cf-4629-a0e2-fda37da9a3bb', tool_call_id='call_sxtKypVZlFrjzdOFYiCh8kin')]}
{'messages': AIMessage(content='The weather in San Francisco is currently cloudy with a chance of meatballs.', response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 91, 'total_tokens': 107}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-d2ed2496-271f-4353-8f9c-3fb3157a4f63-0')}
In [31]:
from langchain_core.messages import AIMessage
branch_config = app_w_interrupt.update_state(
to_replay.config,
{
"messages": [
AIMessage(content="All done here!", id=to_replay.values["messages"][-1].id)
]
},
)In [32]:
branch_state = app_w_interrupt.get_state(branch_config)In [33]:
branch_state.valuesOut [33]:
{'messages': [HumanMessage(content='what is the weather in sf currently', id='7e198f29-a371-49d5-86df-7e0e0b5a9144'),
AIMessage(content='All done here!', id='run-0e6d8103-a92e-461d-aa99-8a68f4c99366-0')]}In [34]:
branch_state.nextOut [34]:
()