mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
46 KiB
46 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("OPENAI_API_KEY")In [3]:
os.environ["LANGCHAIN_TRACING_V2"] = "true"
_set_env("LANGCHAIN_API_KEY")In [2]:
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 [3]:
from langchain_core.tools import tool
@tool
def search(query: str):
"""Call to surf the web."""
# This is a placeholder for the actual implementation
# Don't let the LLM know this though 😊
return [
"It's sunny in San Francisco, but you better look out if you're a Gemini 😈."
]
tools = [search]In [4]:
from langgraph.prebuilt import ToolExecutor
tool_executor = ToolExecutor(tools)In [5]:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(temperature=0)In [6]:
model = model.bind_tools(tools)In [7]:
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import ToolInvocation
# Define the function that determines whether to continue or not
def should_continue(state):
messages = state["messages"]
last_message = 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"
# Define the function that calls the model
def call_model(state):
messages = state["messages"]
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# Define the function to execute tools
def call_tool(state):
messages = state["messages"]
# Based on the continue condition
# we know the last message involves a function call
last_message = messages[-1]
# We construct an ToolInvocation from the function_call
tool_call = last_message.tool_calls[0]
action = ToolInvocation(
tool=tool_call["name"],
tool_input=tool_call["args"],
)
# We call the tool_executor and get back a response
response = tool_executor.invoke(action)
# We use the response to create a ToolMessage
tool_message = ToolMessage(
content=str(response), name=action.tool, tool_call_id=tool_call["id"]
)
# We return a list, because this will get added to the existing list
return {"messages": [tool_message]}In [8]:
from langgraph.graph import END, StateGraph, START
# Define a new graph
workflow = StateGraph(State)
# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)
# 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 [9]:
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()In [10]:
# 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, interrupt_before=["action"])In [11]:
from IPython.display import Image, display
display(Image(app.get_graph().draw_mermaid_png()))In [12]:
from langchain_core.messages import HumanMessage
thread = {"configurable": {"thread_id": "2"}}
inputs = [HumanMessage(content="hi! I'm bob")]
for event in app.stream({"messages": inputs}, thread, 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 [13]:
inputs = [HumanMessage(content="What did I tell you my name was?")]
for event in app.stream({"messages": inputs}, thread, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= What did I tell you my name was? ==================================[1m Ai Message [0m================================== You mentioned that your name is Bob. How can I help you, Bob?
In [14]:
inputs = [HumanMessage(content="what's the weather in sf now?")]
for event in app.stream({"messages": inputs}, thread, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= what's the weather in sf now? ==================================[1m Ai Message [0m================================== Tool Calls: search (call_bxEBI37XzVUvfLKZB2JMewRk) Call ID: call_bxEBI37XzVUvfLKZB2JMewRk Args: query: weather in San Francisco
In [15]:
for event in app.stream(None, thread, stream_mode="values"):
event["messages"][-1].pretty_print()=================================[1m Tool Message [0m================================= Name: search ["It's sunny in San Francisco, but you better look out if you're a Gemini 😈."] ==================================[1m Ai Message [0m================================== The current weather in San Francisco is sunny. Enjoy the sunshine!
In [16]:
import json
from typing import Optional
from langchain_core.messages import AIMessage
# Helper function to construct message asking for verification
def generate_verification_message(message: AIMessage) -> None:
"""Generate "verification message" from message with tool calls."""
serialized_tool_calls = json.dumps(
message.tool_calls,
indent=2,
)
return AIMessage(
content=(
"I plan to invoke the following tools, do you approve?\n\n"
"Type 'y' if you do, anything else to stop.\n\n"
f"{serialized_tool_calls}"
),
id=message.id,
)
# Helper function to stream output from the graph
def stream_app_catch_tool_calls(inputs, thread) -> Optional[AIMessage]:
"""Stream app, catching tool calls."""
tool_call_message = None
for event in app.stream(inputs, thread, stream_mode="values"):
message = event["messages"][-1]
if isinstance(message, AIMessage) and message.tool_calls:
tool_call_message = message
else:
message.pretty_print()
return tool_call_messageIn [17]:
import uuid
thread = {"configurable": {"thread_id": "3"}}
tool_call_message = stream_app_catch_tool_calls(
{"messages": [HumanMessage("what's the weather in sf now?")]},
thread,
)
while tool_call_message:
verification_message = generate_verification_message(tool_call_message)
verification_message.pretty_print()
input_message = HumanMessage(input())
if input_message.content == "exit":
break
input_message.pretty_print()
# First we update the state with the verification message and the input message.
# note that `generate_verification_message` sets the message ID to be the same
# as the ID from the original tool call message. Updating the state with this
# message will overwrite the previous tool call.
snapshot = app.get_state(thread)
snapshot.values["messages"] += [verification_message, input_message]
if input_message.content == "y":
tool_call_message.id = str(uuid.uuid4())
# If verified, we append the tool call message to the state
# and resume execution.
snapshot.values["messages"] += [tool_call_message]
app.update_state(thread, snapshot.values, as_node="agent")
else:
# Otherwise, resume execution from the input message.
app.update_state(thread, snapshot.values, as_node="__start__")
tool_call_message = stream_app_catch_tool_calls(None, thread)================================[1m Human Message [0m================================= what's the weather in sf now? ==================================[1m Ai Message [0m================================== I plan to invoke the following tools, do you approve? Type 'y' if you do, anything else to stop. [ { "name": "search", "args": { "query": "weather in San Francisco" }, "id": "call_fwf8h8Km90CxA7rfaRJypFAB" } ]
can you specify sf in CA?
================================[1m Human Message [0m================================= can you specify sf in CA? ==================================[1m Ai Message [0m================================== I plan to invoke the following tools, do you approve? Type 'y' if you do, anything else to stop. [ { "name": "search", "args": { "query": "weather in San Francisco, California" }, "id": "call_AKIFrAtiunH0AZmLxJE0WSRR" } ]
y
================================[1m Human Message [0m================================= y =================================[1m Tool Message [0m================================= Name: search ["It's sunny in San Francisco, but you better look out if you're a Gemini 😈."] ==================================[1m Ai Message [0m================================== The current weather in San Francisco, California is sunny. Enjoy the sunshine!
In [18]:
class State(TypedDict):
messages: Annotated[list, add_messages]
tool_call_message: Optional[AIMessage]
def call_model(state):
messages = state["messages"]
if messages[-1].content == "y":
return {
"messages": [state["tool_call_message"]],
"tool_call_message": None,
}
else:
response = model.invoke(messages)
if response.tool_calls:
verification_message = generate_verification_message(response)
response.id = str(uuid.uuid4())
return {
"messages": [verification_message],
"tool_call_message": response,
}
else:
return {
"messages": [response],
"tool_call_message": None,
}In [19]:
workflow = StateGraph(State)
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges(
"agent",
should_continue,
{
"continue": "action",
"end": END,
},
)
workflow.add_edge("action", "agent")
app = workflow.compile(checkpointer=memory)In [20]:
thread = {"configurable": {"thread_id": "4"}}
inputs = [HumanMessage(content="what's the weather in sf?")]
for event in app.stream({"messages": inputs}, thread, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= what's the weather in sf? ==================================[1m Ai Message [0m================================== I plan to invoke the following tools, do you approve? Type 'y' if you do, anything else to stop. [ { "name": "search", "args": { "query": "weather in San Francisco" }, "id": "call_Nanzshz5kQZc0FWJcD2hkYXn" } ]
In [21]:
inputs = [HumanMessage(content="can you specify sf in CA?")]
for event in app.stream({"messages": inputs}, thread, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= can you specify sf in CA? ==================================[1m Ai Message [0m================================== I plan to invoke the following tools, do you approve? Type 'y' if you do, anything else to stop. [ { "name": "search", "args": { "query": "weather in San Francisco, California" }, "id": "call_qOnskgB8E72ReGOroSBPdu3v" } ]
In [22]:
inputs = [HumanMessage(content="y")]
for event in app.stream({"messages": inputs}, thread, stream_mode="values"):
event["messages"][-1].pretty_print()================================[1m Human Message [0m================================= y ==================================[1m Ai Message [0m================================== Tool Calls: search (call_qOnskgB8E72ReGOroSBPdu3v) Call ID: call_qOnskgB8E72ReGOroSBPdu3v Args: query: weather in San Francisco, California =================================[1m Tool Message [0m================================= Name: search ["It's sunny in San Francisco, but you better look out if you're a Gemini 😈."] ==================================[1m Ai Message [0m================================== The weather in San Francisco, California is sunny. Enjoy the sunshine!