mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 14:42:28 +02:00
36 KiB
36 KiB
In [1]:
!pip install --quiet -U langchain langchain_openai tavily-pythonIn [ ]:
import os
import getpass
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")
os.environ["TAVILY_API_KEY"] = getpass.getpass("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]:
from langchain.tools.render import format_tool_to_openai_function
functions = [format_tool_to_openai_function(t) for t in tools]
model = model.bind_functions(functions)In [5]:
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]In [6]:
from langgraph.prebuilt import ToolInvocation
import json
from langchain_core.messages import FunctionMessage
# 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 "function_call" not in last_message.additional_kwargs:
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]}In [7]:
# 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
action = ToolInvocation(
tool=last_message.additional_kwargs["function_call"]["name"],
tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
)
response = input(prompt=f"[y/n] continue with: {action}?")
if response == "n":
raise ValueError
# We call the tool_executor and get back a response
response = tool_executor.invoke(action)
# We use the response to create a FunctionMessage
function_message = FunctionMessage(content=str(response), name=action.tool)
# We return a list, because this will get added to the existing list
return {"messages": [function_message]}In [8]:
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 [10]:
from langchain_core.messages import HumanMessage
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
for output in app.stream(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={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}})]}
---
[y/n] continue with: tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'}? n
[0;31m---------------------------------------------------------------------------[0m [0;31mValueError[0m Traceback (most recent call last) Cell [0;32mIn[10], line 4[0m [1;32m 1[0m [38;5;28;01mfrom[39;00m [38;5;21;01mlangchain_core[39;00m[38;5;21;01m.[39;00m[38;5;21;01mmessages[39;00m [38;5;28;01mimport[39;00m HumanMessage [1;32m 3[0m inputs [38;5;241m=[39m {[38;5;124m"[39m[38;5;124mmessages[39m[38;5;124m"[39m: [HumanMessage(content[38;5;241m=[39m[38;5;124m"[39m[38;5;124mwhat is the weather in sf[39m[38;5;124m"[39m)]} [0;32m----> 4[0m [38;5;28;43;01mfor[39;49;00m[43m [49m[43moutput[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[43mapp[49m[38;5;241;43m.[39;49m[43mstream[49m[43m([49m[43minputs[49m[43m)[49m[43m:[49m [1;32m 5[0m [43m [49m[38;5;66;43;03m# stream() yields dictionaries with output keyed by node name[39;49;00m [1;32m 6[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mkey[49m[43m,[49m[43m [49m[43mvalue[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[43moutput[49m[38;5;241;43m.[39;49m[43mitems[49m[43m([49m[43m)[49m[43m:[49m [1;32m 7[0m [43m [49m[38;5;28;43mprint[39;49m[43m([49m[38;5;124;43mf[39;49m[38;5;124;43m"[39;49m[38;5;124;43mOutput from node [39;49m[38;5;124;43m'[39;49m[38;5;132;43;01m{[39;49;00m[43mkey[49m[38;5;132;43;01m}[39;49;00m[38;5;124;43m'[39;49m[38;5;124;43m:[39;49m[38;5;124;43m"[39;49m[43m)[49m File [0;32m~/workplace/permchain/langgraph/pregel/__init__.py:528[0m, in [0;36mPregel.transform[0;34m(self, input, config, output_keys, input_keys, **kwargs)[0m [1;32m 519[0m [38;5;28;01mdef[39;00m [38;5;21mtransform[39m( [1;32m 520[0m [38;5;28mself[39m, [1;32m 521[0m [38;5;28minput[39m: Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]], [0;32m (...)[0m [1;32m 526[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Any, [1;32m 527[0m ) [38;5;241m-[39m[38;5;241m>[39m Iterator[Union[[38;5;28mdict[39m[[38;5;28mstr[39m, Any], Any]]: [0;32m--> 528[0m [43m [49m[38;5;28;43;01mfor[39;49;00m[43m [49m[43mchunk[49m[43m [49m[38;5;129;43;01min[39;49;00m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform_stream_with_config[49m[43m([49m [1;32m 529[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 530[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_transform[49m[43m,[49m [1;32m 531[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 532[0m [43m [49m[43moutput_keys[49m[38;5;241;43m=[39;49m[43moutput_keys[49m[43m,[49m [1;32m 533[0m [43m [49m[43minput_keys[49m[38;5;241;43m=[39;49m[43minput_keys[49m[43m,[49m [1;32m 534[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 535[0m [43m [49m[43m)[49m[43m:[49m [1;32m 536[0m [43m [49m[38;5;28;43;01myield[39;49;00m[43m [49m[43mchunk[49m File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:1226[0m, in [0;36mRunnable._transform_stream_with_config[0;34m(self, input, transformer, config, run_type, **kwargs)[0m [1;32m 1224[0m [38;5;28;01mtry[39;00m: [1;32m 1225[0m [38;5;28;01mwhile[39;00m [38;5;28;01mTrue[39;00m: [0;32m-> 1226[0m chunk: Output [38;5;241m=[39m context[38;5;241m.[39mrun([38;5;28mnext[39m, iterator) [38;5;66;03m# type: ignore[39;00m [1;32m 1227[0m [38;5;28;01myield[39;00m chunk [1;32m 1228[0m [38;5;28;01mif[39;00m final_output_supported: File [0;32m~/workplace/permchain/langgraph/pregel/__init__.py:313[0m, in [0;36mPregel._transform[0;34m(self, input, run_manager, config, input_keys, output_keys)[0m [1;32m 303[0m done, inflight [38;5;241m=[39m concurrent[38;5;241m.[39mfutures[38;5;241m.[39mwait( [1;32m 304[0m [ [1;32m 305[0m executor[38;5;241m.[39msubmit(proc[38;5;241m.[39minvoke, [38;5;28minput[39m, config) [0;32m (...)[0m [1;32m 309[0m timeout[38;5;241m=[39m[38;5;28mself[39m[38;5;241m.[39mstep_timeout, [1;32m 310[0m ) [1;32m 312[0m [38;5;66;03m# interrupt on failure or timeout[39;00m [0;32m--> 313[0m [43m_interrupt_or_proceed[49m[43m([49m[43mdone[49m[43m,[49m[43m [49m[43minflight[49m[43m,[49m[43m [49m[43mstep[49m[43m)[49m [1;32m 315[0m [38;5;66;03m# apply writes to channels[39;00m [1;32m 316[0m _apply_writes(checkpoint, channels, pending_writes, config, step [38;5;241m+[39m [38;5;241m1[39m) File [0;32m~/workplace/permchain/langgraph/pregel/__init__.py:611[0m, in [0;36m_interrupt_or_proceed[0;34m(done, inflight, step)[0m [1;32m 609[0m inflight[38;5;241m.[39mpop()[38;5;241m.[39mcancel() [1;32m 610[0m [38;5;66;03m# raise the exception[39;00m [0;32m--> 611[0m [38;5;28;01mraise[39;00m exc [1;32m 612[0m [38;5;66;03m# TODO this is where retry of an entire step would happen[39;00m [1;32m 614[0m [38;5;28;01mif[39;00m inflight: [1;32m 615[0m [38;5;66;03m# if we got here means we timed out[39;00m File [0;32m~/.pyenv/versions/3.11.1/lib/python3.11/concurrent/futures/thread.py:58[0m, in [0;36m_WorkItem.run[0;34m(self)[0m [1;32m 55[0m [38;5;28;01mreturn[39;00m [1;32m 57[0m [38;5;28;01mtry[39;00m: [0;32m---> 58[0m result [38;5;241m=[39m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfn[49m[43m([49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43margs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m)[49m [1;32m 59[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m exc: [1;32m 60[0m [38;5;28mself[39m[38;5;241m.[39mfuture[38;5;241m.[39mset_exception(exc) File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:3596[0m, in [0;36mRunnableBindingBase.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 3590[0m [38;5;28;01mdef[39;00m [38;5;21minvoke[39m( [1;32m 3591[0m [38;5;28mself[39m, [1;32m 3592[0m [38;5;28minput[39m: Input, [1;32m 3593[0m config: Optional[RunnableConfig] [38;5;241m=[39m [38;5;28;01mNone[39;00m, [1;32m 3594[0m [38;5;241m*[39m[38;5;241m*[39mkwargs: Optional[Any], [1;32m 3595[0m ) [38;5;241m-[39m[38;5;241m>[39m Output: [0;32m-> 3596[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mbound[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 3597[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 3598[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_merge_configs[49m[43m([49m[43mconfig[49m[43m)[49m[43m,[49m [1;32m 3599[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43m{[49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mkwargs[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m}[49m[43m,[49m [1;32m 3600[0m [43m [49m[43m)[49m File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:1774[0m, in [0;36mRunnableSequence.invoke[0;34m(self, input, config)[0m [1;32m 1772[0m [38;5;28;01mtry[39;00m: [1;32m 1773[0m [38;5;28;01mfor[39;00m i, step [38;5;129;01min[39;00m [38;5;28menumerate[39m([38;5;28mself[39m[38;5;241m.[39msteps): [0;32m-> 1774[0m [38;5;28minput[39m [38;5;241m=[39m [43mstep[49m[38;5;241;43m.[39;49m[43minvoke[49m[43m([49m [1;32m 1775[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 1776[0m [43m [49m[38;5;66;43;03m# mark each step as a child run[39;49;00m [1;32m 1777[0m [43m [49m[43mpatch_config[49m[43m([49m [1;32m 1778[0m [43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mcallbacks[49m[38;5;241;43m=[39;49m[43mrun_manager[49m[38;5;241;43m.[39;49m[43mget_child[49m[43m([49m[38;5;124;43mf[39;49m[38;5;124;43m"[39;49m[38;5;124;43mseq:step:[39;49m[38;5;132;43;01m{[39;49;00m[43mi[49m[38;5;241;43m+[39;49m[38;5;241;43m1[39;49m[38;5;132;43;01m}[39;49;00m[38;5;124;43m"[39;49m[43m)[49m [1;32m 1779[0m [43m [49m[43m)[49m[43m,[49m [1;32m 1780[0m [43m [49m[43m)[49m [1;32m 1781[0m [38;5;66;03m# finish the root run[39;00m [1;32m 1782[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:3074[0m, in [0;36mRunnableLambda.invoke[0;34m(self, input, config, **kwargs)[0m [1;32m 3072[0m [38;5;250m[39m[38;5;124;03m"""Invoke this runnable synchronously."""[39;00m [1;32m 3073[0m [38;5;28;01mif[39;00m [38;5;28mhasattr[39m([38;5;28mself[39m, [38;5;124m"[39m[38;5;124mfunc[39m[38;5;124m"[39m): [0;32m-> 3074[0m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_call_with_config[49m[43m([49m [1;32m 3075[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_invoke[49m[43m,[49m [1;32m 3076[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m [1;32m 3077[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43m_config[49m[43m([49m[43mconfig[49m[43m,[49m[43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfunc[49m[43m)[49m[43m,[49m [1;32m 3078[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 3079[0m [43m [49m[43m)[49m [1;32m 3080[0m [38;5;28;01melse[39;00m: [1;32m 3081[0m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m( [1;32m 3082[0m [38;5;124m"[39m[38;5;124mCannot invoke a coroutine function synchronously.[39m[38;5;124m"[39m [1;32m 3083[0m [38;5;124m"[39m[38;5;124mUse `ainvoke` instead.[39m[38;5;124m"[39m [1;32m 3084[0m ) File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:975[0m, in [0;36mRunnable._call_with_config[0;34m(self, func, input, config, run_type, **kwargs)[0m [1;32m 971[0m context [38;5;241m=[39m copy_context() [1;32m 972[0m context[38;5;241m.[39mrun(var_child_runnable_config[38;5;241m.[39mset, child_config) [1;32m 973[0m output [38;5;241m=[39m cast( [1;32m 974[0m Output, [0;32m--> 975[0m [43mcontext[49m[38;5;241;43m.[39;49m[43mrun[49m[43m([49m [1;32m 976[0m [43m [49m[43mcall_func_with_variable_args[49m[43m,[49m [1;32m 977[0m [43m [49m[43mfunc[49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 978[0m [43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;66;43;03m# type: ignore[arg-type][39;49;00m [1;32m 979[0m [43m [49m[43mconfig[49m[43m,[49m [1;32m 980[0m [43m [49m[43mrun_manager[49m[43m,[49m [1;32m 981[0m [43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m,[49m [1;32m 982[0m [43m [49m[43m)[49m, [1;32m 983[0m ) [1;32m 984[0m [38;5;28;01mexcept[39;00m [38;5;167;01mBaseException[39;00m [38;5;28;01mas[39;00m e: [1;32m 985[0m run_manager[38;5;241m.[39mon_chain_error(e) File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/config.py:326[0m, in [0;36mcall_func_with_variable_args[0;34m(func, input, config, run_manager, **kwargs)[0m [1;32m 324[0m [38;5;28;01mif[39;00m run_manager [38;5;129;01mis[39;00m [38;5;129;01mnot[39;00m [38;5;28;01mNone[39;00m [38;5;129;01mand[39;00m accepts_run_manager(func): [1;32m 325[0m kwargs[[38;5;124m"[39m[38;5;124mrun_manager[39m[38;5;124m"[39m] [38;5;241m=[39m run_manager [0;32m--> 326[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/base.py:2950[0m, in [0;36mRunnableLambda._invoke[0;34m(self, input, run_manager, config, **kwargs)[0m [1;32m 2948[0m output [38;5;241m=[39m chunk [1;32m 2949[0m [38;5;28;01melse[39;00m: [0;32m-> 2950[0m output [38;5;241m=[39m [43mcall_func_with_variable_args[49m[43m([49m [1;32m 2951[0m [43m [49m[38;5;28;43mself[39;49m[38;5;241;43m.[39;49m[43mfunc[49m[43m,[49m[43m [49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[43mconfig[49m[43m,[49m[43m [49m[43mrun_manager[49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m [1;32m 2952[0m [43m [49m[43m)[49m [1;32m 2953[0m [38;5;66;03m# If the output is a runnable, invoke it[39;00m [1;32m 2954[0m [38;5;28;01mif[39;00m [38;5;28misinstance[39m(output, Runnable): File [0;32m~/workplace/langchain/libs/core/langchain_core/runnables/config.py:326[0m, in [0;36mcall_func_with_variable_args[0;34m(func, input, config, run_manager, **kwargs)[0m [1;32m 324[0m [38;5;28;01mif[39;00m run_manager [38;5;129;01mis[39;00m [38;5;129;01mnot[39;00m [38;5;28;01mNone[39;00m [38;5;129;01mand[39;00m accepts_run_manager(func): [1;32m 325[0m kwargs[[38;5;124m"[39m[38;5;124mrun_manager[39m[38;5;124m"[39m] [38;5;241m=[39m run_manager [0;32m--> 326[0m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[38;5;28;43minput[39;49m[43m,[49m[43m [49m[38;5;241;43m*[39;49m[38;5;241;43m*[39;49m[43mkwargs[49m[43m)[49m Cell [0;32mIn[7], line 14[0m, in [0;36mcall_tool[0;34m(state)[0m [1;32m 12[0m response [38;5;241m=[39m [38;5;28minput[39m(prompt[38;5;241m=[39m[38;5;124mf[39m[38;5;124m"[39m[38;5;124m[y/n] continue with: [39m[38;5;132;01m{[39;00maction[38;5;132;01m}[39;00m[38;5;124m?[39m[38;5;124m"[39m) [1;32m 13[0m [38;5;28;01mif[39;00m response [38;5;241m==[39m [38;5;124m"[39m[38;5;124mn[39m[38;5;124m"[39m: [0;32m---> 14[0m [38;5;28;01mraise[39;00m [38;5;167;01mValueError[39;00m [1;32m 15[0m [38;5;66;03m# We call the tool_executor and get back a response[39;00m [1;32m 16[0m response [38;5;241m=[39m tool_executor[38;5;241m.[39minvoke(action) [0;31mValueError[0m:
In [ ]: