mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 22:52:29 +02:00
35 KiB
35 KiB
In [1]:
%%capture --no-stderr
%pip install --quiet -U langchain langchain_openai tavily-python[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip is available: [0m[31;49m23.3.1[0m[39;49m -> [0m[32;49m23.3.2[0m [1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
In [2]:
import os
import getpass
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")
os.environ["TAVILY_API_KEY"] = getpass.getpass("Tavily API Key:")OpenAI API Key: ········ Tavily API Key: ········
In [ ]:
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass("LangSmith API Key:")In [1]:
from langchain_community.tools.tavily_search import TavilySearchResults
tools = [TavilySearchResults(max_results=1)]In [2]:
from langgraph.prebuilt import ToolExecutor
tool_executor = ToolExecutor(tools)In [3]:
from langchain_openai import ChatOpenAI
# We will set streaming=True so that we can stream tokens
# See the streaming section for more information on this.
model = ChatOpenAI(temperature=0, streaming=True)In [4]:
model = model.bind_tools(tools)In [5]:
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
def add_messages(left: list | None, right: list | None) -> list:
if not left:
left = []
if not right:
right = []
return left + right
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]In [22]:
from typing import Literal
from langgraph.prebuilt import ToolInvocation
from langchain_core.messages import ToolMessage
# Define the function that determines whether to continue or not
def should_continue(state) -> Literal["end", "continue"]:
messages = state["messages"]
last_message = messages[-1]
# If there is no tool 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
async def call_model(state):
messages = state["messages"]
response = await model.ainvoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# Define the function to execute tools
async 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
action = ToolInvocation(
tool=last_message.tool_calls[0]["name"],
tool_input=last_message.tool_calls[0]["args"],
)
# We call the tool_executor and get back a response
response = await tool_executor.ainvoke(action)
# We use the response to create a FunctionMessage
function_message = ToolMessage(
content=str(response),
name=action.tool,
tool_call_id=last_message.tool_calls[0]["id"],
)
# We return a list, because this will get added to the existing list
return {"messages": [function_message]}In [23]:
from langgraph.graph import StateGraph, END
# Define a new graph
workflow = StateGraph(AgentState)
# 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.set_entry_point("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")
# 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()In [24]:
from IPython.display import Image, display
try:
display(Image(app.get_graph(xray=True).draw_mermaid_png()))
except:
# This requires some extra dependencies and is optional
passIn [25]:
from langchain_core.messages import HumanMessage
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
await app.ainvoke(inputs)Out [25]:
{'messages': [HumanMessage(content='what is the weather in sf'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_UCKe4ydjxDCQPaawAbIWAuwQ', 'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-f0d86d19-0bdd-46b5-9e14-fd3c9649eac3-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_UCKe4ydjxDCQPaawAbIWAuwQ'}]),
ToolMessage(content='[{\'url\': \'https://www.weatherapi.com/\', \'content\': "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1714807578, \'localtime\': \'2024-05-04 0:26\'}, \'current\': {\'last_updated_epoch\': 1714806900, \'last_updated\': \'2024-05-04 00:15\', \'temp_c\': 12.8, \'temp_f\': 55.0, \'is_day\': 0, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/night/122.png\', \'code\': 1009}, \'wind_mph\': 11.9, \'wind_kph\': 19.1, \'wind_degree\': 240, \'wind_dir\': \'WSW\', \'pressure_mb\': 1013.0, \'pressure_in\': 29.9, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 96, \'cloud\': 100, \'feelslike_c\': 11.4, \'feelslike_f\': 52.4, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 1.0, \'gust_mph\': 14.9, \'gust_kph\': 23.9}}"}]', name='tavily_search_results_json', tool_call_id='call_UCKe4ydjxDCQPaawAbIWAuwQ'),
AIMessage(content='The current weather in San Francisco is as follows:\n- Temperature: 55.0°F (12.8°C)\n- Condition: Overcast\n- Wind: 11.9 mph (19.1 kph) from WSW\n- Humidity: 96%\n- Cloud Cover: 100%\n- Feels like: 52.4°F (11.4°C)\n- Visibility: 9.0 miles (16.0 km)\n- UV Index: 1.0\n\nFor more detailed information, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-0415f86c-579a-404d-a1f8-cd9224f8b7bb-0')]}In [26]:
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
async for output in app.astream(inputs):
# stream() yields dictionaries with output keyed by node name
for key, value in output.items():
print(f"Output from node '{key}':")
print("---")
print(value)
print("\n---\n")Output from node 'agent':
---
{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_os6sSAwICFGXtN8z5Isgnp63', 'function': {'arguments': '{"query":"weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-3498553b-4ca0-4920-bd9a-0780632d4607-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_os6sSAwICFGXtN8z5Isgnp63'}])]}
---
Output from node 'action':
---
{'messages': [ToolMessage(content='[{\'url\': \'https://www.weatherapi.com/\', \'content\': "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1714807578, \'localtime\': \'2024-05-04 0:26\'}, \'current\': {\'last_updated_epoch\': 1714806900, \'last_updated\': \'2024-05-04 00:15\', \'temp_c\': 12.8, \'temp_f\': 55.0, \'is_day\': 0, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/night/122.png\', \'code\': 1009}, \'wind_mph\': 11.9, \'wind_kph\': 19.1, \'wind_degree\': 240, \'wind_dir\': \'WSW\', \'pressure_mb\': 1013.0, \'pressure_in\': 29.9, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 96, \'cloud\': 100, \'feelslike_c\': 11.4, \'feelslike_f\': 52.4, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 1.0, \'gust_mph\': 14.9, \'gust_kph\': 23.9}}"}]', name='tavily_search_results_json', tool_call_id='call_os6sSAwICFGXtN8z5Isgnp63')]}
---
Output from node 'agent':
---
{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\n- Temperature: 55.0°F (12.8°C)\n- Condition: Overcast\n- Wind: 11.9 mph (19.1 kph) from WSW\n- Humidity: 96%\n- Cloud Cover: 100%\n- Feels like: 52.4°F (11.4°C)\n- Visibility: 9.0 miles (16.0 km)\n- UV Index: 1.0\n\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-bc828ced-d6f4-45ca-babd-75282b71af82-0')]}
---
In [29]:
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
async for output in app.astream_log(inputs, include_types=["llm"]):
# astream_log() yields the requested logs (here LLMs) in JSONPatch format
for op in output.ops:
if op["path"] == "/streamed_output/-":
# this is the output from .stream()
...
elif op["path"].startswith("/logs/") and op["path"].endswith(
"/streamed_output/-"
):
# because we chose to only include LLMs, these are LLM tokens
print(op["value"].content, end="|")|||||||||||The| current| weather| in| San| Francisco| is| as| follows|: |-| Temperature|:| |55|.|0|°F| (|12|.|8|°C|) |-| Condition|:| Over|cast| |-| Wind|:| |11|.|9| mph| from| W|SW| |-| Hum|idity|:| |96|% |-| Cloud| Cover|:| |100|% |-| Visibility|:| |9|.|0| miles| |For| more| detailed| information|,| you| can| visit| [|Weather| API|](|https|://|www|.weather|api|.com|/|).||
In [ ]: