diff --git a/README.md b/README.md index 87d19961f..c3da88ec4 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ from langchain_core.messages import HumanMessage from langchain_anthropic import ChatAnthropic from langchain_core.tools import tool from langgraph.checkpoint.memory import MemorySaver -from langgraph.graph import END, StateGraph, MessagesState +from langgraph.graph import END, START, StateGraph, MessagesState from langgraph.prebuilt import ToolNode @@ -107,7 +107,7 @@ workflow.add_node("tools", tool_node) # Set the entrypoint as `agent` # This means that this node is the first one called -workflow.set_entry_point("agent") +workflow.add_edge(START, "agent") # We now add a conditional edge workflow.add_conditional_edges( diff --git a/docs/docs/cloud/deployment/graph_rebuild.md b/docs/docs/cloud/deployment/graph_rebuild.md index c7853b30a..b1034cd0f 100644 --- a/docs/docs/cloud/deployment/graph_rebuild.md +++ b/docs/docs/cloud/deployment/graph_rebuild.md @@ -28,7 +28,7 @@ In the standard LangGraph API configuration, the server uses the compiled graph ```python from langchain_openai import ChatOpenAI -from langgraph.graph import END, MessageGraph +from langgraph.graph import END, START, MessageGraph model = ChatOpenAI(temperature=0) @@ -36,7 +36,7 @@ graph_workflow = MessageGraph() graph_workflow.add_node("agent", model) graph_workflow.add_edge("agent", END) -graph_workflow.set_entry_point("agent") +graph_workflow.add_edge(START, "agent") agent = graph_workflow.compile() ``` @@ -60,7 +60,7 @@ To make your graph rebuild on each new run with custom configuration, you need t ```python from typing import Annotated, TypedDict from langchain_openai import ChatOpenAI -from langgraph.graph import END, MessageGraph +from langgraph.graph import END, START, MessageGraph from langgraph.graph.state import StateGraph from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode @@ -83,7 +83,7 @@ def make_default_graph(): graph_workflow.add_node("agent", call_model) graph_workflow.add_edge("agent", END) - graph_workflow.set_entry_point("agent") + graph_workflow.add_edge(START, "agent") agent = graph_workflow.compile() return agent @@ -113,7 +113,7 @@ def make_alternative_graph(): graph_workflow.add_node("agent", call_model) graph_workflow.add_node("tools", tool_node) graph_workflow.add_edge("tools", "agent") - graph_workflow.set_entry_point("agent") + graph_workflow.add_edge(START, "agent") graph_workflow.add_conditional_edges("agent", should_continue) agent = graph_workflow.compile() diff --git a/docs/docs/cloud/deployment/setup.md b/docs/docs/cloud/deployment/setup.md index d2d66f86a..cd6248a8f 100644 --- a/docs/docs/cloud/deployment/setup.md +++ b/docs/docs/cloud/deployment/setup.md @@ -103,7 +103,7 @@ Example `agent.py` file, which shows how to import from other modules you define # my_agent/agent.py from typing import TypedDict, Literal -from langgraph.graph import StateGraph, END +from langgraph.graph import StateGraph, END, START from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes from my_agent.utils.state import AgentState # import state @@ -114,7 +114,7 @@ class GraphConfig(TypedDict): workflow = StateGraph(AgentState, config_schema=GraphConfig) workflow.add_node("agent", call_model) workflow.add_node("action", tool_node) -workflow.set_entry_point("agent") +workflow.add_edge(START, "agent") workflow.add_conditional_edges( "agent", should_continue, diff --git a/docs/docs/cloud/deployment/setup_pyproject.md b/docs/docs/cloud/deployment/setup_pyproject.md index aef373a9b..9c68ec198 100644 --- a/docs/docs/cloud/deployment/setup_pyproject.md +++ b/docs/docs/cloud/deployment/setup_pyproject.md @@ -111,7 +111,7 @@ Example `agent.py` file, which shows how to import from other modules you define # my_agent/agent.py from typing import TypedDict, Literal -from langgraph.graph import StateGraph, END +from langgraph.graph import StateGraph, END, START from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes from my_agent.utils.state import AgentState # import state @@ -122,7 +122,7 @@ class GraphConfig(TypedDict): workflow = StateGraph(AgentState, config_schema=GraphConfig) workflow.add_node("agent", call_model) workflow.add_node("action", tool_node) -workflow.set_entry_point("agent") +workflow.add_edge(START, "agent") workflow.add_conditional_edges( "agent", should_continue, diff --git a/examples/streaming-events-from-within-tools-without-langchain.ipynb b/examples/streaming-events-from-within-tools-without-langchain.ipynb index e0906c837..ab139c602 100644 --- a/examples/streaming-events-from-within-tools-without-langchain.ipynb +++ b/examples/streaming-events-from-within-tools-without-langchain.ipynb @@ -30,10 +30,7 @@ "id": "47f79af8-58d8-4a48-8d9a-88823d88701f", "metadata": {}, "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install -U langgraph openai" - ] + "source": ["%%capture --no-stderr\n%pip install -U langgraph openai"] }, { "cell_type": "code", @@ -49,18 +46,7 @@ ] } ], - "source": [ - "import getpass\n", - "import os\n", - "\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] + "source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"] }, { "cell_type": "markdown", @@ -84,94 +70,7 @@ "id": "d59234f9-173e-469d-a725-c13e0979663e", "metadata": {}, "outputs": [], - "source": [ - "from openai import AsyncOpenAI\n", - "from langchain_core.language_models.chat_models import ChatGenerationChunk\n", - "from langchain_core.messages import AIMessageChunk\n", - "from langchain_core.runnables.config import (\n", - " ensure_config,\n", - " get_callback_manager_for_config,\n", - ")\n", - "\n", - "openai_client = AsyncOpenAI()\n", - "# define tool schema for openai tool calling\n", - "\n", - "tool = {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"get_items\",\n", - " \"description\": \"Use this tool to look up which items are in the given place.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\"place\": {\"type\": \"string\"}},\n", - " \"required\": [\"place\"],\n", - " },\n", - " },\n", - "}\n", - "\n", - "\n", - "async def call_model(state, config=None):\n", - " config = ensure_config(config | {\"tags\": [\"agent_llm\"]})\n", - " callback_manager = get_callback_manager_for_config(config)\n", - " messages = state[\"messages\"]\n", - "\n", - " llm_run_manager = callback_manager.on_chat_model_start({}, [messages])[0]\n", - " response = await openai_client.chat.completions.create(\n", - " messages=messages, model=\"gpt-3.5-turbo\", tools=[tool], stream=True\n", - " )\n", - "\n", - " response_content = \"\"\n", - " role = None\n", - "\n", - " tool_call_id = None\n", - " tool_call_function_name = None\n", - " tool_call_function_arguments = \"\"\n", - " async for chunk in response:\n", - " delta = chunk.choices[0].delta\n", - " if delta.role is not None:\n", - " role = delta.role\n", - "\n", - " if delta.content:\n", - " response_content += delta.content\n", - " llm_run_manager.on_llm_new_token(delta.content)\n", - "\n", - " if delta.tool_calls:\n", - " # note: for simplicity we're only handling a single tool call here\n", - " if delta.tool_calls[0].function.name is not None:\n", - " tool_call_function_name = delta.tool_calls[0].function.name\n", - " tool_call_id = delta.tool_calls[0].id\n", - "\n", - " # note: we're wrapping the tools calls in ChatGenerationChunk so that the events from .astream_events in the graph can render tool calls correctly\n", - " tool_call_chunk = ChatGenerationChunk(\n", - " message=AIMessageChunk(\n", - " content=\"\",\n", - " additional_kwargs={\"tool_calls\": [delta.tool_calls[0].dict()]},\n", - " )\n", - " )\n", - " llm_run_manager.on_llm_new_token(\"\", chunk=tool_call_chunk)\n", - " tool_call_function_arguments += delta.tool_calls[0].function.arguments\n", - "\n", - " if tool_call_function_name is not None:\n", - " tool_calls = [\n", - " {\n", - " \"id\": tool_call_id,\n", - " \"function\": {\n", - " \"name\": tool_call_function_name,\n", - " \"arguments\": tool_call_function_arguments,\n", - " },\n", - " \"type\": \"function\",\n", - " }\n", - " ]\n", - " else:\n", - " tool_calls = None\n", - "\n", - " response_message = {\n", - " \"role\": role,\n", - " \"content\": response_content,\n", - " \"tool_calls\": tool_calls,\n", - " }\n", - " return {\"messages\": [response_message]}" - ] + "source": ["from openai import AsyncOpenAI\nfrom langchain_core.language_models.chat_models import ChatGenerationChunk\nfrom langchain_core.messages import AIMessageChunk\nfrom langchain_core.runnables.config import (\n ensure_config,\n get_callback_manager_for_config,\n)\n\nopenai_client = AsyncOpenAI()\n# define tool schema for openai tool calling\n\ntool = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_items\",\n \"description\": \"Use this tool to look up which items are in the given place.\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\"place\": {\"type\": \"string\"}},\n \"required\": [\"place\"],\n },\n },\n}\n\n\nasync def call_model(state, config=None):\n config = ensure_config(config | {\"tags\": [\"agent_llm\"]})\n callback_manager = get_callback_manager_for_config(config)\n messages = state[\"messages\"]\n\n llm_run_manager = callback_manager.on_chat_model_start({}, [messages])[0]\n response = await openai_client.chat.completions.create(\n messages=messages, model=\"gpt-3.5-turbo\", tools=[tool], stream=True\n )\n\n response_content = \"\"\n role = None\n\n tool_call_id = None\n tool_call_function_name = None\n tool_call_function_arguments = \"\"\n async for chunk in response:\n delta = chunk.choices[0].delta\n if delta.role is not None:\n role = delta.role\n\n if delta.content:\n response_content += delta.content\n llm_run_manager.on_llm_new_token(delta.content)\n\n if delta.tool_calls:\n # note: for simplicity we're only handling a single tool call here\n if delta.tool_calls[0].function.name is not None:\n tool_call_function_name = delta.tool_calls[0].function.name\n tool_call_id = delta.tool_calls[0].id\n\n # note: we're wrapping the tools calls in ChatGenerationChunk so that the events from .astream_events in the graph can render tool calls correctly\n tool_call_chunk = ChatGenerationChunk(\n message=AIMessageChunk(\n content=\"\",\n additional_kwargs={\"tool_calls\": [delta.tool_calls[0].dict()]},\n )\n )\n llm_run_manager.on_llm_new_token(\"\", chunk=tool_call_chunk)\n tool_call_function_arguments += delta.tool_calls[0].function.arguments\n\n if tool_call_function_name is not None:\n tool_calls = [\n {\n \"id\": tool_call_id,\n \"function\": {\n \"name\": tool_call_function_name,\n \"arguments\": tool_call_function_arguments,\n },\n \"type\": \"function\",\n }\n ]\n else:\n tool_calls = None\n\n response_message = {\n \"role\": role,\n \"content\": response_content,\n \"tool_calls\": tool_calls,\n }\n return {\"messages\": [response_message]}"] }, { "cell_type": "markdown", @@ -187,62 +86,7 @@ "id": "b90941d8-afe4-42ec-9262-9c3b87c3b1ec", "metadata": {}, "outputs": [], - "source": [ - "import json\n", - "from langchain_core.callbacks import adispatch_custom_event\n", - "\n", - "\n", - "async def get_items(place: str) -> str:\n", - " \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n", - "\n", - " # this can be replaced with any actual streaming logic that you might have\n", - " def stream(place: str):\n", - " if \"bed\" in place: # For under the bed\n", - " yield from [\"socks\", \"shoes\", \"dust bunnies\"]\n", - " elif \"shelf\" in place: # For 'shelf'\n", - " yield from [\"books\", \"penciles\", \"pictures\"]\n", - " else: # if the agent decides to ask about a different place\n", - " yield \"cat snacks\"\n", - "\n", - " tokens = []\n", - " for token in stream(place):\n", - " await adispatch_custom_event(\n", - " # this will allow you to filter events by name\n", - " \"tool_call_token_stream\",\n", - " {\n", - " \"function_name\": \"get_items\",\n", - " \"arguments\": {\"place\": place},\n", - " \"tool_output_token\": token,\n", - " },\n", - " # this will allow you to filter events by tags\n", - " config={\"tags\": [\"tool_call\"]},\n", - " )\n", - " tokens.append(token)\n", - "\n", - " return \", \".join(tokens)\n", - "\n", - "\n", - "# define mapping to look up functions when running tools\n", - "function_name_to_function = {\"get_items\": get_items}\n", - "\n", - "\n", - "async def call_tools(state):\n", - " messages = state[\"messages\"]\n", - "\n", - " tool_call = messages[-1][\"tool_calls\"][0]\n", - " function_name = tool_call[\"function\"][\"name\"]\n", - " function_arguments = tool_call[\"function\"][\"arguments\"]\n", - " arguments = json.loads(function_arguments)\n", - "\n", - " function_response = await function_name_to_function[function_name](**arguments)\n", - " tool_message = {\n", - " \"tool_call_id\": tool_call[\"id\"],\n", - " \"role\": \"tool\",\n", - " \"name\": function_name,\n", - " \"content\": function_response,\n", - " }\n", - " return {\"messages\": [tool_message]}" - ] + "source": ["import json\nfrom langchain_core.callbacks import adispatch_custom_event\n\n\nasync def get_items(place: str) -> str:\n \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n\n # this can be replaced with any actual streaming logic that you might have\n def stream(place: str):\n if \"bed\" in place: # For under the bed\n yield from [\"socks\", \"shoes\", \"dust bunnies\"]\n elif \"shelf\" in place: # For 'shelf'\n yield from [\"books\", \"penciles\", \"pictures\"]\n else: # if the agent decides to ask about a different place\n yield \"cat snacks\"\n\n tokens = []\n for token in stream(place):\n await adispatch_custom_event(\n # this will allow you to filter events by name\n \"tool_call_token_stream\",\n {\n \"function_name\": \"get_items\",\n \"arguments\": {\"place\": place},\n \"tool_output_token\": token,\n },\n # this will allow you to filter events by tags\n config={\"tags\": [\"tool_call\"]},\n )\n tokens.append(token)\n\n return \", \".join(tokens)\n\n\n# define mapping to look up functions when running tools\nfunction_name_to_function = {\"get_items\": get_items}\n\n\nasync def call_tools(state):\n messages = state[\"messages\"]\n\n tool_call = messages[-1][\"tool_calls\"][0]\n function_name = tool_call[\"function\"][\"name\"]\n function_arguments = tool_call[\"function\"][\"arguments\"]\n arguments = json.loads(function_arguments)\n\n function_response = await function_name_to_function[function_name](**arguments)\n tool_message = {\n \"tool_call_id\": tool_call[\"id\"],\n \"role\": \"tool\",\n \"name\": function_name,\n \"content\": function_response,\n }\n return {\"messages\": [tool_message]}"] }, { "cell_type": "markdown", @@ -258,33 +102,7 @@ "id": "228260be-1f9a-4195-80e0-9604f8a5dba6", "metadata": {}, "outputs": [], - "source": [ - "import operator\n", - "from typing import Annotated, TypedDict, Literal\n", - "\n", - "from langgraph.graph import StateGraph, END\n", - "\n", - "\n", - "class State(TypedDict):\n", - " messages: Annotated[list, operator.add]\n", - "\n", - "\n", - "def should_continue(state) -> Literal[\"tools\", END]:\n", - " messages = state[\"messages\"]\n", - " last_message = messages[-1]\n", - " if last_message[\"tool_calls\"]:\n", - " return \"tools\"\n", - " return END\n", - "\n", - "\n", - "workflow = StateGraph(State)\n", - "workflow.set_entry_point(\"model\")\n", - "workflow.add_node(\"model\", call_model) # i.e. our \"agent\"\n", - "workflow.add_node(\"tools\", call_tools)\n", - "workflow.add_conditional_edges(\"model\", should_continue)\n", - "workflow.add_edge(\"tools\", \"model\")\n", - "graph = workflow.compile()" - ] + "source": ["import operator\nfrom typing import Annotated, TypedDict, Literal\n\nfrom langgraph.graph import StateGraph, END, START\n\n\nclass State(TypedDict):\n messages: Annotated[list, operator.add]\n\n\ndef should_continue(state) -> Literal[\"tools\", END]:\n messages = state[\"messages\"]\n last_message = messages[-1]\n if last_message[\"tool_calls\"]:\n return \"tools\"\n return END\n\n\nworkflow = StateGraph(State)\nworkflow.add_edge(START, \"model\")\nworkflow.add_node(\"model\", call_model) # i.e. our \"agent\"\nworkflow.add_node(\"tools\", call_tools)\nworkflow.add_conditional_edges(\"model\", should_continue)\nworkflow.add_edge(\"tools\", \"model\")\ngraph = workflow.compile()"] }, { "cell_type": "markdown", @@ -318,14 +136,7 @@ ] } ], - "source": [ - "async for event in graph.astream_events(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]}, version=\"v2\"\n", - "):\n", - " tags = event.get(\"tags\", [])\n", - " if event[\"event\"] == \"on_custom_event\" and \"tool_call\" in tags:\n", - " print(\"Tool token\", event[\"data\"][\"tool_output_token\"])" - ] + "source": ["async for event in graph.astream_events(\n {\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]}, version=\"v2\"\n):\n tags = event.get(\"tags\", [])\n if event[\"event\"] == \"on_custom_event\" and \"tool_call\" in tags:\n print(\"Tool token\", event[\"data\"][\"tool_output_token\"])"] } ], "metadata": { diff --git a/examples/streaming-tokens-without-langchain.ipynb b/examples/streaming-tokens-without-langchain.ipynb index d31f287f8..40ff751e0 100644 --- a/examples/streaming-tokens-without-langchain.ipynb +++ b/examples/streaming-tokens-without-langchain.ipynb @@ -30,10 +30,7 @@ "id": "47f79af8-58d8-4a48-8d9a-88823d88701f", "metadata": {}, "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install -U langgraph openai" - ] + "source": ["%%capture --no-stderr\n%pip install -U langgraph openai"] }, { "cell_type": "code", @@ -49,18 +46,7 @@ ] } ], - "source": [ - "import getpass\n", - "import os\n", - "\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] + "source": ["import getpass\nimport os\n\n\ndef _set_env(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"{var}: \")\n\n\n_set_env(\"OPENAI_API_KEY\")"] }, { "cell_type": "markdown", @@ -84,94 +70,7 @@ "id": "d59234f9-173e-469d-a725-c13e0979663e", "metadata": {}, "outputs": [], - "source": [ - "from openai import AsyncOpenAI\n", - "from langchain_core.language_models.chat_models import ChatGenerationChunk\n", - "from langchain_core.messages import AIMessageChunk\n", - "from langchain_core.runnables.config import (\n", - " ensure_config,\n", - " get_callback_manager_for_config,\n", - ")\n", - "\n", - "openai_client = AsyncOpenAI()\n", - "# define tool schema for openai tool calling\n", - "\n", - "tool = {\n", - " \"type\": \"function\",\n", - " \"function\": {\n", - " \"name\": \"get_items\",\n", - " \"description\": \"Use this tool to look up which items are in the given place.\",\n", - " \"parameters\": {\n", - " \"type\": \"object\",\n", - " \"properties\": {\"place\": {\"type\": \"string\"}},\n", - " \"required\": [\"place\"],\n", - " },\n", - " },\n", - "}\n", - "\n", - "\n", - "async def call_model(state, config=None):\n", - " config = ensure_config(config | {\"tags\": [\"agent_llm\"]})\n", - " callback_manager = get_callback_manager_for_config(config)\n", - " messages = state[\"messages\"]\n", - "\n", - " llm_run_manager = callback_manager.on_chat_model_start({}, [messages])[0]\n", - " response = await openai_client.chat.completions.create(\n", - " messages=messages, model=\"gpt-3.5-turbo\", tools=[tool], stream=True\n", - " )\n", - "\n", - " response_content = \"\"\n", - " role = None\n", - "\n", - " tool_call_id = None\n", - " tool_call_function_name = None\n", - " tool_call_function_arguments = \"\"\n", - " async for chunk in response:\n", - " delta = chunk.choices[0].delta\n", - " if delta.role is not None:\n", - " role = delta.role\n", - "\n", - " if delta.content:\n", - " response_content += delta.content\n", - " llm_run_manager.on_llm_new_token(delta.content)\n", - "\n", - " if delta.tool_calls:\n", - " # note: for simplicity we're only handling a single tool call here\n", - " if delta.tool_calls[0].function.name is not None:\n", - " tool_call_function_name = delta.tool_calls[0].function.name\n", - " tool_call_id = delta.tool_calls[0].id\n", - "\n", - " # note: we're wrapping the tools calls in ChatGenerationChunk so that the events from .astream_events in the graph can render tool calls correctly\n", - " tool_call_chunk = ChatGenerationChunk(\n", - " message=AIMessageChunk(\n", - " content=\"\",\n", - " additional_kwargs={\"tool_calls\": [delta.tool_calls[0].dict()]},\n", - " )\n", - " )\n", - " llm_run_manager.on_llm_new_token(\"\", chunk=tool_call_chunk)\n", - " tool_call_function_arguments += delta.tool_calls[0].function.arguments\n", - "\n", - " if tool_call_function_name is not None:\n", - " tool_calls = [\n", - " {\n", - " \"id\": tool_call_id,\n", - " \"function\": {\n", - " \"name\": tool_call_function_name,\n", - " \"arguments\": tool_call_function_arguments,\n", - " },\n", - " \"type\": \"function\",\n", - " }\n", - " ]\n", - " else:\n", - " tool_calls = None\n", - "\n", - " response_message = {\n", - " \"role\": role,\n", - " \"content\": response_content,\n", - " \"tool_calls\": tool_calls,\n", - " }\n", - " return {\"messages\": [response_message]}" - ] + "source": ["from openai import AsyncOpenAI\nfrom langchain_core.language_models.chat_models import ChatGenerationChunk\nfrom langchain_core.messages import AIMessageChunk\nfrom langchain_core.runnables.config import (\n ensure_config,\n get_callback_manager_for_config,\n)\n\nopenai_client = AsyncOpenAI()\n# define tool schema for openai tool calling\n\ntool = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_items\",\n \"description\": \"Use this tool to look up which items are in the given place.\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\"place\": {\"type\": \"string\"}},\n \"required\": [\"place\"],\n },\n },\n}\n\n\nasync def call_model(state, config=None):\n config = ensure_config(config | {\"tags\": [\"agent_llm\"]})\n callback_manager = get_callback_manager_for_config(config)\n messages = state[\"messages\"]\n\n llm_run_manager = callback_manager.on_chat_model_start({}, [messages])[0]\n response = await openai_client.chat.completions.create(\n messages=messages, model=\"gpt-3.5-turbo\", tools=[tool], stream=True\n )\n\n response_content = \"\"\n role = None\n\n tool_call_id = None\n tool_call_function_name = None\n tool_call_function_arguments = \"\"\n async for chunk in response:\n delta = chunk.choices[0].delta\n if delta.role is not None:\n role = delta.role\n\n if delta.content:\n response_content += delta.content\n llm_run_manager.on_llm_new_token(delta.content)\n\n if delta.tool_calls:\n # note: for simplicity we're only handling a single tool call here\n if delta.tool_calls[0].function.name is not None:\n tool_call_function_name = delta.tool_calls[0].function.name\n tool_call_id = delta.tool_calls[0].id\n\n # note: we're wrapping the tools calls in ChatGenerationChunk so that the events from .astream_events in the graph can render tool calls correctly\n tool_call_chunk = ChatGenerationChunk(\n message=AIMessageChunk(\n content=\"\",\n additional_kwargs={\"tool_calls\": [delta.tool_calls[0].dict()]},\n )\n )\n llm_run_manager.on_llm_new_token(\"\", chunk=tool_call_chunk)\n tool_call_function_arguments += delta.tool_calls[0].function.arguments\n\n if tool_call_function_name is not None:\n tool_calls = [\n {\n \"id\": tool_call_id,\n \"function\": {\n \"name\": tool_call_function_name,\n \"arguments\": tool_call_function_arguments,\n },\n \"type\": \"function\",\n }\n ]\n else:\n tool_calls = None\n\n response_message = {\n \"role\": role,\n \"content\": response_content,\n \"tool_calls\": tool_calls,\n }\n return {\"messages\": [response_message]}"] }, { "cell_type": "markdown", @@ -187,41 +86,7 @@ "id": "b756ea32", "metadata": {}, "outputs": [], - "source": [ - "import json\n", - "\n", - "\n", - "async def get_items(place: str) -> str:\n", - " \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n", - " if \"bed\" in place: # For under the bed\n", - " return \"socks, shoes and dust bunnies\"\n", - " if \"shelf\" in place: # For 'shelf'\n", - " return \"books, penciles and pictures\"\n", - " else: # if the agent decides to ask about a different place\n", - " return \"cat snacks\"\n", - "\n", - "\n", - "# define mapping to look up functions when running tools\n", - "function_name_to_function = {\"get_items\": get_items}\n", - "\n", - "\n", - "async def call_tools(state):\n", - " messages = state[\"messages\"]\n", - "\n", - " tool_call = messages[-1][\"tool_calls\"][0]\n", - " function_name = tool_call[\"function\"][\"name\"]\n", - " function_arguments = tool_call[\"function\"][\"arguments\"]\n", - " arguments = json.loads(function_arguments)\n", - "\n", - " function_response = await function_name_to_function[function_name](**arguments)\n", - " tool_message = {\n", - " \"tool_call_id\": tool_call[\"id\"],\n", - " \"role\": \"tool\",\n", - " \"name\": function_name,\n", - " \"content\": function_response,\n", - " }\n", - " return {\"messages\": [tool_message]}" - ] + "source": ["import json\n\n\nasync def get_items(place: str) -> str:\n \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n if \"bed\" in place: # For under the bed\n return \"socks, shoes and dust bunnies\"\n if \"shelf\" in place: # For 'shelf'\n return \"books, penciles and pictures\"\n else: # if the agent decides to ask about a different place\n return \"cat snacks\"\n\n\n# define mapping to look up functions when running tools\nfunction_name_to_function = {\"get_items\": get_items}\n\n\nasync def call_tools(state):\n messages = state[\"messages\"]\n\n tool_call = messages[-1][\"tool_calls\"][0]\n function_name = tool_call[\"function\"][\"name\"]\n function_arguments = tool_call[\"function\"][\"arguments\"]\n arguments = json.loads(function_arguments)\n\n function_response = await function_name_to_function[function_name](**arguments)\n tool_message = {\n \"tool_call_id\": tool_call[\"id\"],\n \"role\": \"tool\",\n \"name\": function_name,\n \"content\": function_response,\n }\n return {\"messages\": [tool_message]}"] }, { "cell_type": "markdown", @@ -237,33 +102,7 @@ "id": "228260be-1f9a-4195-80e0-9604f8a5dba6", "metadata": {}, "outputs": [], - "source": [ - "import operator\n", - "from typing import Annotated, TypedDict, Literal\n", - "\n", - "from langgraph.graph import StateGraph, END\n", - "\n", - "\n", - "class State(TypedDict):\n", - " messages: Annotated[list, operator.add]\n", - "\n", - "\n", - "def should_continue(state) -> Literal[\"tools\", END]:\n", - " messages = state[\"messages\"]\n", - " last_message = messages[-1]\n", - " if last_message[\"tool_calls\"]:\n", - " return \"tools\"\n", - " return END\n", - "\n", - "\n", - "workflow = StateGraph(State)\n", - "workflow.set_entry_point(\"model\")\n", - "workflow.add_node(\"model\", call_model) # i.e. our \"agent\"\n", - "workflow.add_node(\"tools\", call_tools)\n", - "workflow.add_conditional_edges(\"model\", should_continue)\n", - "workflow.add_edge(\"tools\", \"model\")\n", - "graph = workflow.compile()" - ] + "source": ["import operator\nfrom typing import Annotated, TypedDict, Literal\n\nfrom langgraph.graph import StateGraph, END, START\n\n\nclass State(TypedDict):\n messages: Annotated[list, operator.add]\n\n\ndef should_continue(state) -> Literal[\"tools\", END]:\n messages = state[\"messages\"]\n last_message = messages[-1]\n if last_message[\"tool_calls\"]:\n return \"tools\"\n return END\n\n\nworkflow = StateGraph(State)\nworkflow.add_edge(START, \"model\")\nworkflow.add_node(\"model\", call_model) # i.e. our \"agent\"\nworkflow.add_node(\"tools\", call_tools)\nworkflow.add_conditional_edges(\"model\", should_continue)\nworkflow.add_edge(\"tools\", \"model\")\ngraph = workflow.compile()"] }, { "cell_type": "markdown", @@ -328,14 +167,7 @@ ] } ], - "source": [ - "async for event in graph.astream_events(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]}, version=\"v2\"\n", - "):\n", - " tags = event.get(\"tags\", [])\n", - " if event[\"event\"] == \"on_chat_model_stream\" and \"agent_llm\" in tags:\n", - " print(\"LLM token\", event[\"data\"][\"chunk\"].dict())" - ] + "source": ["async for event in graph.astream_events(\n {\"messages\": [{\"role\": \"user\", \"content\": \"what's in the bedroom\"}]}, version=\"v2\"\n):\n tags = event.get(\"tags\", [])\n if event[\"event\"] == \"on_chat_model_stream\" and \"agent_llm\" in tags:\n print(\"LLM token\", event[\"data\"][\"chunk\"].dict())"] }, { "cell_type": "code", @@ -343,7 +175,7 @@ "id": "adb0f7bc-6e51-478e-bd32-8f72df072d6c", "metadata": {}, "outputs": [], - "source": [] + "source": [""] } ], "metadata": { diff --git a/examples/tutorials/rag-agent-testing-local.ipynb b/examples/tutorials/rag-agent-testing-local.ipynb index 3105d3342..7fd810943 100644 --- a/examples/tutorials/rag-agent-testing-local.ipynb +++ b/examples/tutorials/rag-agent-testing-local.ipynb @@ -355,7 +355,7 @@ "workflow.add_node(\"web_search\", web_search) # web search\n", "\n", "# Build graph\n", - "workflow.set_entry_point(\"retrieve\")\n", + "workflow.add_edge(START, retrieve)\n", "workflow.add_edge(\"retrieve\", \"grade_documents\")\n", "workflow.add_conditional_edges(\n", " \"grade_documents\",\n", diff --git a/examples/web-navigation/web_voyager.ipynb b/examples/web-navigation/web_voyager.ipynb index 728003605..8959d2025 100644 --- a/examples/web-navigation/web_voyager.ipynb +++ b/examples/web-navigation/web_voyager.ipynb @@ -457,13 +457,13 @@ "source": [ "from langchain_core.runnables import RunnableLambda\n", "\n", - "from langgraph.graph import END, StateGraph\n", + "from langgraph.graph import END, START, StateGraph\n", "\n", "graph_builder = StateGraph(AgentState)\n", "\n", "\n", "graph_builder.add_node(\"agent\", agent)\n", - "graph_builder.set_entry_point(\"agent\")\n", + "graph_builder.add_edge(START, \"agent\")\n", "\n", "graph_builder.add_node(\"update_scratchpad\", update_scratchpad)\n", "graph_builder.add_edge(\"update_scratchpad\", \"agent\")\n", diff --git a/libs/langgraph/README.md b/libs/langgraph/README.md index 87d19961f..c3da88ec4 100644 --- a/libs/langgraph/README.md +++ b/libs/langgraph/README.md @@ -59,7 +59,7 @@ from langchain_core.messages import HumanMessage from langchain_anthropic import ChatAnthropic from langchain_core.tools import tool from langgraph.checkpoint.memory import MemorySaver -from langgraph.graph import END, StateGraph, MessagesState +from langgraph.graph import END, START, StateGraph, MessagesState from langgraph.prebuilt import ToolNode @@ -107,7 +107,7 @@ workflow.add_node("tools", tool_node) # Set the entrypoint as `agent` # This means that this node is the first one called -workflow.set_entry_point("agent") +workflow.add_edge(START, "agent") # We now add a conditional edge workflow.add_conditional_edges(