diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py index 962ece629..90790392d 100644 --- a/docs/_scripts/copy_notebooks.py +++ b/docs/_scripts/copy_notebooks.py @@ -15,6 +15,7 @@ _MANUAL = { "how-tos": [ "async.ipynb", "streaming-tokens.ipynb", + "streaming-content.ipynb", "human-in-the-loop.ipynb", "persistence.ipynb", "time-travel.ipynb", @@ -30,8 +31,10 @@ _MANUAL = { "dynamically-returning-directly.ipynb", "configuration.ipynb", "map-reduce.ipynb", - "extraction/retries.ipynb", "create-react-agent.ipynb", + "create-react-agent-system-prompt.ipynb", + "create-react-agent-memory.ipynb", + "create-react-agent-hitl.ipynb", ], "tutorials": [ "introduction.ipynb", diff --git a/docs/docs/concepts/index.md b/docs/docs/concepts/index.md index e23686978..3364f2f4e 100644 --- a/docs/docs/concepts/index.md +++ b/docs/docs/concepts/index.md @@ -30,6 +30,7 @@ Low Level Concepts - [Conditional Entry Point](low_level#conditional-entry-point) - [Send](low_level#send) - [Checkpointer](low_level#checkpointer) +- [Threads](low_level#threads) - [Checkpointer states](low_level#checkpointer-state) - [Get state](low_level#get-state) - [Get state history](low_level#get-state-history) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index f39266d78..e8aa73f6c 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -226,8 +226,6 @@ graph.set_conditional_entry_point(routing_function, {True: "node_b", False: "nod ## `Send` -[`Send`](https://langchain-ai.github.io/langgraph/reference/graphs/#send) is a special type of edge. - By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common of example of this is with `map-reduce` design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object). To support this design pattern, LangGraph supports returning [`Send`](https://langchain-ai.github.io/langgraph/reference/graphs/#send) objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node. @@ -249,18 +247,35 @@ First, it allows for human-in-the-loop workflows, as it allows humans to inspect Second, it allows for "memory" between interactions. You can use checkpointers to create threads and save the state of a thread after a graph executes. In the case of repeated human interactions (like conversations) any follow up messages can be sent to that checkpoint, which will retain its memory of previous ones. +## Threads + +When using a checkpointer, you must specify a `thread_id` or `thread_ts` when running the graph. +Threads are used to checkpoint multiple different runs. This can be used to enable a multi-tenant chat applications. + +`thread_id` is simply the ID of a thread. This is always required + +`thread_ts` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick of a run of a graph from some point halfway through a thread. + +You must pass these when invoking the graph as part of the configurable part of the config. + +```python +config = {"configurable": {"thread_id": "a"}} +graph.invoke(inputs, config=config) +``` + ## Checkpointer state When you use a checkpointer with a graph, you can interact with the state of that graph. This usually done when enabling different human-in-the-loop interaction patterns. +When interacting with the checkpointer state, you must specify [thread identifiers](#threads) ### Get state -You can get the state of a checkpointer by calling `graph.get_state(config)`. The config commonly contains things like the `thread_id` of a particular thread to get the state for. +You can get the state of a checkpointer by calling `graph.get_state(config)`. The config should contain `thread_id`, and the state will be fetched for that thread. ### Get state history -You can also call `graph.get_state_history(config)` to get a list of the history of the graph. The config commonly contains things like the `thread_id` of a particular thread to get the state for. +You can also call `graph.get_state_history(config)` to get a list of the history of the graph. The config should contain `thread_id`, and the state history will be fetched for that thread. ### Update state @@ -273,7 +288,7 @@ You can also interact with the state directly and update it. This takes three di **config** -The config commonly contains things like `thread_id` specifying which thread to update. +The config should contain `thread_id` specifying which thread to update. **values** diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 9adde1a14..126542350 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -7,36 +7,44 @@ hide: Welcome to the LangGraph how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph. -## Basics +## Controllability -These guides show how to address common needs when building out AI workflows, with special focus placed on [ReAct](https://arxiv.org/abs/2210.03629)-style agents with [tool calling](https://python.langchain.com/docs/modules/model_io/chat/function_calling/) (agents that Reason and **Act** to accomplish tasks). - -- [How to create a ReAct agent](create-react-agent.ipynb) -- [How to add persistence ("memory") to your graph](persistence.ipynb) -- [How to view and update graph state](time-travel.ipynb) -- [How to run graph asynchronously](async.ipynb) -- [How to stream graph responses](streaming-tokens.ipynb) -- [How to visualize your graph](visualization.ipynb) -- [How to add runtime configuration to your graph](configuration.ipynb) - -### Design patterns - -Recipes showing how to apply common design patterns in your workflows: +LangGraph is known for being a highly controllable agent framework. +These how-to guides show how to achieve that controllability. - [How to create subgraphs](subgraph.ipynb) - [How to create branches for parallel execution](branching.ipynb) - [How to create map-reduce branches for parallel execution](map-reduce.ipynb) + +## Human in the Loop + +One of LangGraph's main benefits is that it makes human-in-the-loop workflows easy. +These guides cover common examples of that. + +- [How to add persistence ("memory") to your graph](persistence.ipynb) +- [How to view and update graph state](time-travel.ipynb) - [How to add human-in-the-loop](human-in-the-loop.ipynb) -The following examples are useful especially if you are used to LangChain's `AgentExecutor` configurations. +## Streaming -- [How to force an agent to call a tool](force-calling-a-tool-first.ipynb) -- [How to pass runtime values to tools](pass-run-time-values-to-tools.ipynb) -- [How to let agent return tool results directly](dynamically-returning-directly.ipynb) -- [How to have agent respond in structured format](respond-in-format.ipynb) -- [How to manage agent steps](managing-agent-steps.ipynb) +LangGraph is built to be streaming first. +These guides show how to use different streaming modes. -### Advanced +- [How to stream LLM tokens](streaming-tokens.ipynb) +- [How to stream arbitrarily nested content](streaming-content.ipynb) +## Other +- [How to run graph asynchronously](async.ipynb) +- [How to visualize your graph](visualization.ipynb) +- [How to add runtime configuration to your graph](configuration.ipynb) - [How to use a Pydantic model as your state](state-model.ipynb) -- [How to extract structured output with re-prompting](./extraction/retries.ipynb) \ No newline at end of file + +## Prebuilt ReAct Agent + +These guides show how to use the prebuilt ReAct agent. +Please note that here will we use a **prebuilt agent**. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph. + +- [How to create a ReAct agent](create-react-agent.ipynb) +- [How to add memory to a ReAct agent](create-react-agent-memory.ipynb) +- [How to add a custom system prompt to a ReAct agent](create-react-agent-system-prompt.ipynb) +- [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb) diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index ae82b7e99..a5aa052dc 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -63,4 +63,6 @@ Learn from example implementations of graphs designed for specific scenarios and - [Web Research (STORM)](storm/storm.ipynb): Generate Wikipedia-like articles via research and multi-perspective QA - [TNT-LLM](tnt-llm/tnt-llm.ipynb): Build rich, interpretable taxonomies of user intentand using the classification system developed by Microsoft for their Bing Copilot application. - [Web Navigation](web-navigation/web_voyager.ipynb): Build an agent that can navigate and interact with websites -- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao. \ No newline at end of file +- [Competitive Programming](usaco/usaco.ipynb): Build an agent with few-shot "episodic memory" and human-in-the-loop collaboration to solve problems from the USA Computing Olympiad; adapted from the ["Can Language Models Solve Olympiad Programming?"](https://arxiv.org/abs/2404.10952v1) paper by Shi, Tang, Narasimhan, and Yao. +- [Complex data extraction](extraction/retries.ipynb): Build an agent that can use function calling to do complex extraction tasks +- \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c880ba9c4..4e4f1078d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -130,32 +130,36 @@ nav: - TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb - Web Navigation: tutorials/web-navigation/web_voyager.ipynb - Competitive Programming: tutorials/usaco/usaco.ipynb + - Extract structured output: tutorials/extraction/retries.ipynb - "How-to Guides": - 'how-tos/index.md' - - Basics: - - Create a ReAct agent: how-tos/create-react-agent.ipynb - - Add persistence ("memory"): how-tos/persistence.ipynb - - View and update graph state: how-tos/time-travel.ipynb - - Run graph asynchronously: how-tos/async.ipynb - - Stream graph responses: how-tos/streaming-tokens.ipynb - - Visualize your graph: how-tos/visualization.ipynb - - Add runtime configuration: how-tos/configuration.ipynb - - Design Patterns: + - Controllability: - Create subgraphs: how-tos/subgraph.ipynb - Create branches for parallel execution: how-tos/branching.ipynb - Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb + - Human-in-the-loop: + - Add persistence ("memory"): how-tos/persistence.ipynb + - View and update graph state: how-tos/time-travel.ipynb - Add human-in-the-loop: how-tos/human-in-the-loop.ipynb - - Force an agent to call a tool: how-tos/force-calling-a-tool-first.ipynb - - Pass runtime values to tools: how-tos/pass-run-time-values-to-tools.ipynb - - Let agent return tool results directly: how-tos/dynamically-returning-directly.ipynb - - Have agent respond in structured format: how-tos/respond-in-format.ipynb - - Manage agent steps: how-tos/managing-agent-steps.ipynb - - Advanced: + - Streaming: + - Stream LLM tokens: how-tos/streaming-tokens.ipynb + - Stream Arbitrarily Nested Content: how-tos/streaming-content.ipynb + - Other: + - Run graph asynchronously: how-tos/async.ipynb + - Visualize your graph: how-tos/visualization.ipynb + - Add runtime configuration: how-tos/configuration.ipynb - Use Pydantic model as state: how-tos/state-model.ipynb - - Extract structured output with re-prompting: how-tos/extraction/retries.ipynb + - Prebuilt ReAct Agent: + - Create a ReAct agent: how-tos/create-react-agent.ipynb + - Add Memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb + - Add a System Prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb + - Add Human-in-the-Loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb - 'Conceptual Guides': - 'concepts/index.md' + - LangGraph for Agentic Applications: concepts/high_level.md + - Low Level LangGraph Concepts: concepts/high_level.md + - Common Agentic Patterns: concepts/high_level.md - Reference: - Graphs: reference/graphs.md - Checkpointing: reference/checkpoints.md diff --git a/examples/create-react-agent-hitl.ipynb b/examples/create-react-agent-hitl.ipynb new file mode 100644 index 000000000..21ac07472 --- /dev/null +++ b/examples/create-react-agent-hitl.ipynb @@ -0,0 +1,245 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4", + "metadata": {}, + "source": [ + "# How to add human-in-the-loop processes to the prebuilt ReAct agent\n", + "\n", + "This tutorial will show how to add human-in-the-loop processes to the prebuilt ReAct agent. Please see [this tutorial](create-react-agent) for how to get started with the prebuilt ReAct agent\n", + "\n", + "You can add a a breakpoint before tools are called by passing `interrupt_before=[\"tools\"]` to `create_react_agent`. Note that you need to be using a checkpointer for this to work." + ] + }, + { + "cell_type": "markdown", + "id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a213e11a-5c62-4ddb-a707-490d91add383", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "23a1885c-04ab-4750-aefa-105891fddf3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENAI_API_KEY: ········\n" + ] + } + ], + "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\")\n", + "\n", + "# Recommended\n", + "_set_env(\"LANGCHAIN_API_KEY\")\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\"" + ] + }, + { + "cell_type": "markdown", + "id": "03c0f089-070c-4cd4-87e0-6c51f2477b82", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7a154152-973e-4b5d-aa13-48c617744a4c", + "metadata": {}, + "outputs": [], + "source": [ + "# First we initialize the model we want to use.\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", + "\n", + "\n", + "# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n", + "\n", + "from typing import Literal\n", + "\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def get_weather(city: Literal[\"nyc\", \"sf\"]):\n", + " \"\"\"Use this to get weather information.\"\"\"\n", + " if city == \"nyc\":\n", + " return \"It might be cloudy in nyc\"\n", + " elif city == \"sf\":\n", + " return \"It's always sunny in sf\"\n", + " else:\n", + " raise AssertionError(\"Unknown city\")\n", + "\n", + "\n", + "tools = [get_weather]\n", + "\n", + "# We need a checkpointer to enable human-in-the-loop patterns\n", + "from langgraph.checkpoint import MemorySaver\n", + "\n", + "memory = MemorySaver()\n", + "\n", + "# Define the graph\n", + "\n", + "from langgraph.prebuilt import create_react_agent\n", + "\n", + "graph = create_react_agent(model, tools=tools, interrupt_before=[\"tools\"], checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "00407425-506d-4ffd-9c86-987921d8c844", + "metadata": {}, + "source": [ + "## Usage\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28", + "metadata": {}, + "outputs": [], + "source": [ + "def print_stream(stream):\n", + " for s in stream:\n", + " message = s[\"messages\"][-1]\n", + " if isinstance(message, tuple):\n", + " print(message)\n", + " else:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What's the weather in SF?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " get_weather (call_0OMmuTLec9t8kxMVkllZCSxo)\n", + " Call ID: call_0OMmuTLec9t8kxMVkllZCSxo\n", + " Args:\n", + " city: sf\n" + ] + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"42\"}}\n", + "inputs = {\"messages\": [(\"user\", \"What's the weather in SF?\")]}\n", + "\n", + "print_stream(graph.stream(inputs, config, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3decf001-7228-4ed5-8779-2b9ed98a74ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Next step: ('tools',)\n" + ] + } + ], + "source": [ + "snapshot = graph.get_state(config)\n", + "print(\"Next step: \", snapshot.next)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "83148e08-63e8-49e5-a08b-02dc907bed1d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: get_weather\n", + "\n", + "It's always sunny in sf\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The weather in San Francisco is currently sunny.\n" + ] + } + ], + "source": [ + "print_stream(graph.stream(None, config, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f6f8965-b016-4e25-be63-31c00fc0a6de", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/create-react-agent-memory.ipynb b/examples/create-react-agent-memory.ipynb new file mode 100644 index 000000000..1588084c3 --- /dev/null +++ b/examples/create-react-agent-memory.ipynb @@ -0,0 +1,255 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4", + "metadata": {}, + "source": [ + "# How to add memory to the prebuilt ReAct agent\n", + "\n", + "This tutorial will show how to add memory to the prebuilt ReAct agent. Please see [this tutorial](create-react-agent) for how to get started with the prebuilt ReAct agent\n", + "\n", + "All we need to do to enable memory is pass in a checkpointer to `create_react_agents`" + ] + }, + { + "cell_type": "markdown", + "id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a213e11a-5c62-4ddb-a707-490d91add383", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "23a1885c-04ab-4750-aefa-105891fddf3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENAI_API_KEY: ········\n" + ] + } + ], + "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\")\n", + "\n", + "# Recommended\n", + "_set_env(\"LANGCHAIN_API_KEY\")\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\"" + ] + }, + { + "cell_type": "markdown", + "id": "03c0f089-070c-4cd4-87e0-6c51f2477b82", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7a154152-973e-4b5d-aa13-48c617744a4c", + "metadata": {}, + "outputs": [], + "source": [ + "# First we initialize the model we want to use.\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", + "\n", + "\n", + "# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n", + "\n", + "from typing import Literal\n", + "\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def get_weather(city: Literal[\"nyc\", \"sf\"]):\n", + " \"\"\"Use this to get weather information.\"\"\"\n", + " if city == \"nyc\":\n", + " return \"It might be cloudy in nyc\"\n", + " elif city == \"sf\":\n", + " return \"It's always sunny in sf\"\n", + " else:\n", + " raise AssertionError(\"Unknown city\")\n", + "\n", + "\n", + "tools = [get_weather]\n", + "\n", + "# We can add \"chat memory\" to the graph with LangGraph's checkpointer\n", + "# to retain the chat context between interactions\n", + "from langgraph.checkpoint import MemorySaver\n", + "\n", + "memory = MemorySaver()\n", + "\n", + "# Define the graph\n", + "\n", + "from langgraph.prebuilt import create_react_agent\n", + "\n", + "graph = create_react_agent(model, tools=tools, checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "00407425-506d-4ffd-9c86-987921d8c844", + "metadata": {}, + "source": [ + "## Usage\n", + "\n", + "Let's interact with it multiple times to show that it can remember" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28", + "metadata": {}, + "outputs": [], + "source": [ + "def print_stream(stream):\n", + " for s in stream:\n", + " message = s[\"messages\"][-1]\n", + " if isinstance(message, tuple):\n", + " print(message)\n", + " else:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What's the weather in NYC?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " get_weather (call_mdovy4yXSSYrmSlnlVSUacVn)\n", + " Call ID: call_mdovy4yXSSYrmSlnlVSUacVn\n", + " Args:\n", + " city: nyc\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: get_weather\n", + "\n", + "It might be cloudy in nyc\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The weather in NYC might be cloudy.\n" + ] + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n", + "\n", + "print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "markdown", + "id": "838a043f-90ad-4e69-9d1d-6e22db2c346c", + "metadata": {}, + "source": [ + "Notice that when we pass the same the same thread ID, the chat history is preserved" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "187479f9-32fa-4611-9487-cf816ba2e147", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What's it known for?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "New York City (NYC) is known for many things, including:\n", + "\n", + "1. **Landmarks and Attractions**: The Statue of Liberty, Times Square, Central Park, Empire State Building, and Brooklyn Bridge.\n", + "2. **Cultural Institutions**: Broadway theaters, Metropolitan Museum of Art, Museum of Modern Art (MoMA), and the American Museum of Natural History.\n", + "3. **Diverse Neighborhoods**: Areas like Chinatown, Little Italy, Harlem, and Greenwich Village.\n", + "4. **Financial Hub**: Wall Street and the New York Stock Exchange.\n", + "5. **Cuisine**: A melting pot of global cuisines, famous for its pizza, bagels, and street food.\n", + "6. **Media and Entertainment**: Home to major media companies, TV networks, and film studios.\n", + "7. **Fashion**: A global fashion capital, hosting New York Fashion Week.\n", + "8. **Sports**: Teams like the New York Yankees, New York Mets, New York Knicks, and New York Rangers.\n", + "9. **Public Transportation**: An extensive subway system and iconic yellow taxis.\n", + "10. **Events**: New Year's Eve celebration in Times Square, Macy's Thanksgiving Day Parade, and various cultural festivals.\n" + ] + } + ], + "source": [ + "inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n", + "print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3decf001-7228-4ed5-8779-2b9ed98a74ea", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/create-react-agent-system-prompt.ipynb b/examples/create-react-agent-system-prompt.ipynb new file mode 100644 index 000000000..4b664412d --- /dev/null +++ b/examples/create-react-agent-system-prompt.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4", + "metadata": {}, + "source": [ + "# How to add a custom system prompt to the prebuilt ReAct agent\n", + "\n", + "This tutorial will show how to add a custom system prompt to the prebuilt ReAct agent. Please see [this tutorial](create-react-agent) for how to get started with the prebuilt ReAct agent\n", + "\n", + "You can add a custom system prompt by passing a string to the `messages_modifier` param." + ] + }, + { + "cell_type": "markdown", + "id": "7be3889f-3c17-4fa1-bd2b-84114a2c7247", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a213e11a-5c62-4ddb-a707-490d91add383", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "23a1885c-04ab-4750-aefa-105891fddf3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OPENAI_API_KEY: ········\n" + ] + } + ], + "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\")\n", + "\n", + "# Recommended\n", + "_set_env(\"LANGCHAIN_API_KEY\")\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Create ReAct Agent Tutorial\"" + ] + }, + { + "cell_type": "markdown", + "id": "03c0f089-070c-4cd4-87e0-6c51f2477b82", + "metadata": {}, + "source": [ + "## Code" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7a154152-973e-4b5d-aa13-48c617744a4c", + "metadata": {}, + "outputs": [], + "source": [ + "# First we initialize the model we want to use.\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", + "\n", + "\n", + "# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n", + "\n", + "from typing import Literal\n", + "\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", + "@tool\n", + "def get_weather(city: Literal[\"nyc\", \"sf\"]):\n", + " \"\"\"Use this to get weather information.\"\"\"\n", + " if city == \"nyc\":\n", + " return \"It might be cloudy in nyc\"\n", + " elif city == \"sf\":\n", + " return \"It's always sunny in sf\"\n", + " else:\n", + " raise AssertionError(\"Unknown city\")\n", + "\n", + "\n", + "tools = [get_weather]\n", + "\n", + "# We can add our system prompt here\n", + "\n", + "prompt = \"Respond in Italian\"\n", + "\n", + "# Define the graph\n", + "\n", + "from langgraph.prebuilt import create_react_agent\n", + "\n", + "graph = create_react_agent(model, tools=tools, messages_modifier=prompt)" + ] + }, + { + "cell_type": "markdown", + "id": "00407425-506d-4ffd-9c86-987921d8c844", + "metadata": {}, + "source": [ + "## Usage\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28", + "metadata": {}, + "outputs": [], + "source": [ + "def print_stream(stream):\n", + " for s in stream:\n", + " message = s[\"messages\"][-1]\n", + " if isinstance(message, tuple):\n", + " print(message)\n", + " else:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What's the weather in NYC?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " get_weather (call_b02uzBRrIm2uciJa8zDXCDxT)\n", + " Call ID: call_b02uzBRrIm2uciJa8zDXCDxT\n", + " Args:\n", + " city: nyc\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: get_weather\n", + "\n", + "It might be cloudy in nyc\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "A New York potrebbe essere nuvoloso.\n" + ] + } + ], + "source": [ + "inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n", + "\n", + "print_stream(graph.stream(inputs, stream_mode=\"values\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3decf001-7228-4ed5-8779-2b9ed98a74ea", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/create-react-agent.ipynb b/examples/create-react-agent.ipynb index 07498ff90..eb3b44a06 100644 --- a/examples/create-react-agent.ipynb +++ b/examples/create-react-agent.ipynb @@ -5,7 +5,7 @@ "id": "992c4695-ec4f-428d-bd05-fb3b5fbd70f4", "metadata": {}, "source": [ - "# How to create a ReAct agent" + "# How to use the prebuilt ReAct agent" ] }, { @@ -16,7 +16,14 @@ "In this how-to we'll create a simple [ReAct](https://arxiv.org/abs/2210.03629) agent app that can check the weather. The app consists of an agent (LLM) and tools. As we interact with the app, we will first call the agent (LLM) to decide if we should use tools. Then we will run a loop: \n", "\n", "1. If the agent said to take an action (i.e. call tool), we'll run the tools and pass the results back to the agent\n", - "2. If the agent did not ask to run tools, we will finish (respond to the user)" + "2. If the agent did not ask to run tools, we will finish (respond to the user)\n", + "\n", + "
\n", + "

Prebuilt Agent

\n", + "

\n", + "Please note that here will we use a prebuilt agent. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph.\n", + "

\n", + "
" ] }, { @@ -75,40 +82,29 @@ "id": "03c0f089-070c-4cd4-87e0-6c51f2477b82", "metadata": {}, "source": [ - "## How to create a simple ReAct agent with `create_react_agent`" - ] - }, - { - "cell_type": "markdown", - "id": "91efaf20-711b-4b94-837e-dcef10d8abdd", - "metadata": {}, - "source": [ - "In our example we'll use `ChatOpenAI` as our agent and a custom tool that returns pre-defined values for weather in two cities (NYC & SF)" - ] - }, - { - "cell_type": "markdown", - "id": "6238f7d4-7da1-4e48-bc5e-a592280271e0", - "metadata": {}, - "source": [ - "### Define model and tools" + "## Code" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "029c838a-c6e7-4679-9a8b-953703fe3041", + "execution_count": 1, + "id": "7a154152-973e-4b5d-aa13-48c617744a4c", "metadata": {}, "outputs": [], "source": [ - "from typing import Literal\n", - "\n", - "from langchain_core.tools import tool\n", + "# First we initialize the model we want to use.\n", "from langchain_openai import ChatOpenAI\n", "\n", "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", "\n", "\n", + "# For this tutorial we will use custom tool that returns pre-defined values for weather in two cities (NYC & SF)\n", + "\n", + "from typing import Literal\n", + "\n", + "from langchain_core.tools import tool\n", + "\n", + "\n", "@tool\n", "def get_weather(city: Literal[\"nyc\", \"sf\"]):\n", " \"\"\"Use this to get weather information.\"\"\"\n", @@ -120,40 +116,29 @@ " raise AssertionError(\"Unknown city\")\n", "\n", "\n", - "tools = [get_weather]" - ] - }, - { - "cell_type": "markdown", - "id": "47570e6c-655b-45ae-ad4a-b278e58f1b94", - "metadata": {}, - "source": [ - "### Define the graph" - ] - }, - { - "cell_type": "markdown", - "id": "2a2da4e4", - "metadata": {}, - "source": [ - "We're going to use a prebuilt implementation of ReAct agent included with `langgraph` library - [`create_react_agent`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "3d2df9e7-4c2c-494a-9249-5a2ee32eca3e", - "metadata": {}, - "outputs": [], - "source": [ + "tools = [get_weather]\n", + "\n", + "\n", + "# Define the graph\n", + "\n", "from langgraph.prebuilt import create_react_agent\n", "\n", "graph = create_react_agent(model, tools=tools)" ] }, + { + "cell_type": "markdown", + "id": "00407425-506d-4ffd-9c86-987921d8c844", + "metadata": {}, + "source": [ + "## Usage\n", + "\n", + "First, let's visualize the graph we just created" + ] + }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "fa16de4c-aac0-4ff4-ab69-60d399f75423", "metadata": {}, "outputs": [ @@ -176,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "16636975-5f2d-4dc7-ab8e-d0bea0830a28", "metadata": {}, "outputs": [], @@ -200,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "9ffff6c3-a4f5-47c9-b51d-97caaee85cd6", "metadata": {}, "outputs": [ @@ -213,8 +198,8 @@ "what is the weather in sf\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " get_weather (call_g6w9lHn3fxYo2ABE3Ihhprbr)\n", - " Call ID: call_g6w9lHn3fxYo2ABE3Ihhprbr\n", + " get_weather (call_jgO5OOUnugRkhRi3wAOHl8Et)\n", + " Call ID: call_jgO5OOUnugRkhRi3wAOHl8Et\n", " Args:\n", " city: sf\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", @@ -242,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "187479f9-32fa-4611-9487-cf816ba2e147", "metadata": {}, "outputs": [ @@ -263,383 +248,13 @@ "inputs = {\"messages\": [(\"user\", \"who built you?\")]}\n", "print_stream(graph.stream(inputs, stream_mode=\"values\"))" ] - }, - { - "cell_type": "markdown", - "id": "82b06aa3-6414-48e6-ba11-07bf065a316a", - "metadata": {}, - "source": [ - "## How to add system prompt to `create_react_agent`" - ] - }, - { - "cell_type": "markdown", - "id": "a3823699-1050-40fc-a8cc-ecbdc03c1cbe", - "metadata": {}, - "source": [ - "There are several ways to customize prompt, all of which are controlled by `messages_modifier` param. You can pass:\n", - "- system message string / `SystemMessage` that will be prepended to the list of messages\n", - "- a function that takes a list of messages and transforms them into an output that can be passed to the language model" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "23ea0a31-3e6b-433b-a832-036655579ebe", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's your name? And what's the weather in SF?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " get_weather (call_PGwTiytTVAAvNKWp4nznPi4s)\n", - " Call ID: call_PGwTiytTVAAvNKWp4nznPi4s\n", - " Args:\n", - " city: sf\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: get_weather\n", - "\n", - "It's always sunny in sf\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "My name is Fred. The weather in San Francisco is currently sunny.\n" - ] - } - ], - "source": [ - "system_prompt = \"You are a helpful bot named Fred.\"\n", - "graph = create_react_agent(model, tools, messages_modifier=system_prompt)\n", - "\n", - "inputs = {\"messages\": [(\"user\", \"What's your name? And what's the weather in SF?\")]}\n", - "print_stream(graph.stream(inputs, stream_mode=\"values\"))" - ] - }, - { - "cell_type": "markdown", - "id": "d8e797ce-74bf-4a30-a388-08104138fd52", - "metadata": {}, - "source": [ - "We can also add a more complex prompt for the LLM:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "23c40ab3-c574-47be-a80f-654e9035cd6d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's my name? And what's the weather in SF?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " get_weather (call_scpzIjdK3l411zcEn2T00Xm0)\n", - " Call ID: call_scpzIjdK3l411zcEn2T00Xm0\n", - " Args:\n", - " city: sf\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: get_weather\n", - "\n", - "It's always sunny in sf\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Your name is Joe. The weather in San Francisco is always sunny.\n" - ] - } - ], - "source": [ - "from langchain_core.prompts import ChatPromptTemplate\n", - "\n", - "prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", \"You are a helpful bot named Fred.\"),\n", - " (\"user\", \"My name is Joe\"),\n", - " (\"placeholder\", \"{messages}\"),\n", - " ]\n", - ")\n", - "\n", - "\n", - "def modify_messages(messages: list):\n", - " # You can do more complex modifications here\n", - " return prompt.invoke({\"messages\": messages})\n", - "\n", - "\n", - "graph = create_react_agent(model, tools, messages_modifier=modify_messages)\n", - "\n", - "inputs = {\"messages\": [(\"user\", \"What's my name? And what's the weather in SF?\")]}\n", - "print_stream(graph.stream(inputs, stream_mode=\"values\"))" - ] - }, - { - "cell_type": "markdown", - "id": "0af418b8-bdd3-4e10-a552-9d8d2a409a57", - "metadata": {}, - "source": [ - "## How to add memory to `create_react_agent`" - ] - }, - { - "cell_type": "markdown", - "id": "4eef6365-530b-4d33-9874-41273ebee510", - "metadata": {}, - "source": [ - "We can add \"chat memory\" to the graph with LangGraph's checkpointer, to retain the chat context between interactions" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "c43d071a-a91e-4646-a035-b3fed9cd9f0c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's the weather in NYC?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " get_weather (call_C5yEOD1GhlVPX9Gc5nnqgUXF)\n", - " Call ID: call_C5yEOD1GhlVPX9Gc5nnqgUXF\n", - " Args:\n", - " city: nyc\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: get_weather\n", - "\n", - "It might be cloudy in nyc\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "The weather in NYC might be cloudy.\n" - ] - } - ], - "source": [ - "from langgraph.checkpoint import MemorySaver\n", - "\n", - "graph = create_react_agent(model, tools, checkpointer=MemorySaver())\n", - "\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "inputs = {\"messages\": [(\"user\", \"What's the weather in NYC?\")]}\n", - "\n", - "print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))" - ] - }, - { - "cell_type": "markdown", - "id": "4541ea58-ee6a-4685-a988-c915a42284e6", - "metadata": {}, - "source": [ - "Notice that when we pass the same the same thread ID, the chat history is preserved" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "b509417b-70f3-4736-b838-5d1729ed759e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's it known for?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "New York City (NYC) is known for many things, including:\n", - "\n", - "1. **Landmarks and Attractions**: \n", - " - **Statue of Liberty**: A symbol of freedom and democracy.\n", - " - **Times Square**: Known for its bright lights, Broadway theaters, and bustling atmosphere.\n", - " - **Central Park**: A large urban park offering various recreational activities.\n", - " - **Empire State Building**: An iconic skyscraper with an observation deck offering panoramic views of the city.\n", - " - **Brooklyn Bridge**: A historic bridge connecting Manhattan and Brooklyn.\n", - "\n", - "2. **Cultural Diversity**: NYC is a melting pot of cultures, languages, and cuisines, making it one of the most diverse cities in the world.\n", - "\n", - "3. **Arts and Entertainment**: \n", - " - **Broadway**: Renowned for its world-class theater productions.\n", - " - **Museums**: Such as the Metropolitan Museum of Art, the Museum of Modern Art (MoMA), and the American Museum of Natural History.\n", - " - **Music and Nightlife**: A vibrant scene with numerous music venues, bars, and clubs.\n", - "\n", - "4. **Financial Hub**: \n", - " - **Wall Street**: The financial district is home to the New York Stock Exchange and numerous financial institutions.\n", - "\n", - "5. **Fashion and Shopping**: \n", - " - **Fifth Avenue**: Known for its high-end shopping.\n", - " - **Fashion Week**: One of the major fashion events held twice a year.\n", - "\n", - "6. **Cuisine**: \n", - " - **Diverse Food Scene**: From street food like hot dogs and pretzels to fine dining and international cuisines.\n", - " - **Famous Foods**: New York-style pizza, bagels, and cheesecake.\n", - "\n", - "7. **Media and Publishing**: \n", - " - Home to major media companies, newspapers like The New York Times, and numerous publishing houses.\n", - "\n", - "8. **Sports**: \n", - " - Home to several major sports teams, including the New York Yankees (baseball), New York Mets (baseball), New York Knicks (basketball), Brooklyn Nets (basketball), New York Giants (football), and New York Jets (football).\n", - "\n", - "9. **Education and Research**: \n", - " - Prestigious institutions like Columbia University, New York University (NYU), and Rockefeller University.\n", - "\n", - "10. **Public Transportation**: \n", - " - An extensive subway system, buses, and taxis that make getting around the city convenient.\n", - "\n", - "NYC is a city that never sleeps, offering endless opportunities for exploration and experiences.\n" - ] - } - ], - "source": [ - "inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n", - "print_stream(graph.stream(inputs, config=config, stream_mode=\"values\"))" - ] - }, - { - "cell_type": "markdown", - "id": "ef765ab6-3ea0-4de0-8737-de8f9d9add40", - "metadata": {}, - "source": [ - "And if we pass a different thread ID, the chat history is reset" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "1bb09dbe-bbba-4e8f-b44c-b18bd2ed96ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's it known for?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Could you please specify what \"it\" refers to? Are you asking about a specific city, person, object, or something else?\n" - ] - } - ], - "source": [ - "inputs = {\"messages\": [(\"user\", \"What's it known for?\")]}\n", - "print_stream(\n", - " graph.stream(\n", - " inputs, config={\"configurable\": {\"thread_id\": 2}}, stream_mode=\"values\"\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "42e730aa-8fac-48a5-9c67-b51393665469", - "metadata": {}, - "source": [ - "## How to add human-in-the-loop to `create_react_agent`" - ] - }, - { - "cell_type": "markdown", - "id": "6314e49c-9143-45a8-871b-8bd3f489425c", - "metadata": {}, - "source": [ - "Let's add an interrupt to let the user confirm before LLM takes an action:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "8cd2cbcc-8a1f-443e-9cc9-dc18cd0ceac6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "What's the weather in SF?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " get_weather (call_I7B9YW4ENth7QXYzDpiIoLCE)\n", - " Call ID: call_I7B9YW4ENth7QXYzDpiIoLCE\n", - " Args:\n", - " city: sf\n" - ] - } - ], - "source": [ - "graph = create_react_agent(\n", - " model, tools, interrupt_before=[\"tools\"], checkpointer=MemorySaver()\n", - ")\n", - "\n", - "config = {\"configurable\": {\"thread_id\": \"42\"}}\n", - "inputs = {\"messages\": [(\"user\", \"What's the weather in SF?\")]}\n", - "\n", - "print_stream(graph.stream(inputs, config, stream_mode=\"values\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "493d4963-b27b-46ef-8474-1527b08c1c03", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Next step: ('tools',)\n" - ] - } - ], - "source": [ - "snapshot = graph.get_state(config)\n", - "print(\"Next step: \", snapshot.next)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "7ad01f3a-e838-4230-ab07-2f850bb7e7cb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: get_weather\n", - "\n", - "It's always sunny in sf\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "The weather in San Francisco is currently sunny.\n" - ] - } - ], - "source": [ - "print_stream(graph.stream(None, config, stream_mode=\"values\"))" - ] } ], "metadata": { "kernelspec": { - "display_name": "langgraph-example-dev", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "langgraph-example-dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -651,7 +266,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.1" } }, "nbformat": 4, diff --git a/examples/extraction/retries.ipynb b/examples/extraction/retries.ipynb index b45a5a536..ca9bcf59f 100644 --- a/examples/extraction/retries.ipynb +++ b/examples/extraction/retries.ipynb @@ -5,7 +5,7 @@ "id": "e327e9bd-effc-4bee-a875-1c383c17f43d", "metadata": {}, "source": [ - "# How to extract structured output with re-prompting\n", + "# Complex data extraction with function calling\n", "\n", "Function calling is a core primitive for integrating LLMs within your software stack. We use it throughout the LangGraph docs, since developing with function calling (aka tool usage) tends to be much more stress-free than the traditional way of writing custom string parsers.\n", "\n", @@ -1036,7 +1036,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.11.1" } }, "nbformat": 4, diff --git a/examples/streaming-content.ipynb b/examples/streaming-content.ipynb new file mode 100644 index 000000000..aabd6fc97 --- /dev/null +++ b/examples/streaming-content.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "15c4bd28", + "metadata": {}, + "source": [ + "# How to stream arbitrary nested content\n", + "\n", + "The most common use case for streaming from inside a node is to stream LLM tokens, but you may have other long-running streaming functions you wish to render for the user. While individual nodes in LangGraph cannot return generators (since they are executed to completion for each [superstep](https://langchain-ai.github.io/langgraph/concepts/#core-design)), we can still stream arbitrary custom functions from within a node using a similar tact and calling `astream_events` on the graph.\n", + "\n", + "We do so using a [RunnableGenerator](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableGenerator.html#langchain-core-runnables-base-runnablegenerator) (which your function will automatically behave as if wrapped as a [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)).\n", + "\n", + "Below is a simple toy example." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "486a01a0", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage\n", + "from langchain_core.runnables import RunnableGenerator\n", + "from langchain_core.runnables import RunnableConfig\n", + "\n", + "from langgraph.graph import START, StateGraph, MessagesState, END\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(MessagesState)\n", + "\n", + "\n", + "async def my_generator(state: MessagesState):\n", + " messages = [\n", + " \"Four\",\n", + " \"score\",\n", + " \"and\",\n", + " \"seven\",\n", + " \"years\",\n", + " \"ago\",\n", + " \"our\",\n", + " \"fathers\",\n", + " \"...\",\n", + " ]\n", + " for message in messages:\n", + " yield message\n", + "\n", + "\n", + "async def my_node(state: MessagesState, config: RunnableConfig):\n", + " messages = []\n", + " # Tagging a node makes it easy to filter out which events to include in your stream\n", + " # It's completely optional, but useful if you have many functions with similar names\n", + " gen = RunnableGenerator(my_generator).with_config(tags=[\"should_stream\"])\n", + " async for message in gen.astream(state):\n", + " messages.append(message)\n", + " return {\"messages\": [AIMessage(content=\" \".join(messages))]}\n", + "\n", + "\n", + "workflow.add_node(\"model\", my_node)\n", + "workflow.add_edge(START, \"model\")\n", + "workflow.add_edge(\"model\", END)\n", + "app = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ce773a40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'chunk': 'Four'}|{'chunk': 'score'}|{'chunk': 'and'}|{'chunk': 'seven'}|{'chunk': 'years'}|{'chunk': 'ago'}|{'chunk': 'our'}|{'chunk': 'fathers'}|{'chunk': '...'}|" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/.pyenv/versions/3.11.1/envs/permchain/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py:87: LangChainBetaWarning: This API is in beta and may change in the future.\n", + " warn_beta(\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "\n", + "inputs = [HumanMessage(content=\"What are you thinking about?\")]\n", + "async for event in app.astream_events({\"messages\": inputs}, version=\"v1\"):\n", + " kind = event[\"event\"]\n", + " tags = event.get(\"tags\", [])\n", + " if kind == \"on_chain_stream\" and \"should_stream\" in tags:\n", + " data = event[\"data\"]\n", + " if data:\n", + " # Empty content in the context of OpenAI or Anthropic usually means\n", + " # that the model is asking for a tool to be invoked.\n", + " # So we only print non-empty content\n", + " print(data, end=\"|\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c7b7902-2d80-4bf9-91c1-737b749e58a3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/streaming-tokens.ipynb b/examples/streaming-tokens.ipynb index 62632c132..2977cb875 100644 --- a/examples/streaming-tokens.ipynb +++ b/examples/streaming-tokens.ipynb @@ -439,98 +439,6 @@ " print(f\"Tool output was: {event['data'].get('output')}\")\n", " print(\"--\")" ] - }, - { - "cell_type": "markdown", - "id": "15c4bd28", - "metadata": {}, - "source": [ - "## Streaming arbitrary nested content\n", - "\n", - "The above example streams tokens from a chat model, but you may have other long-running streaming functions you wish to render for the user. While individual nodes in LangGraph cannot return generators (since they are executed to completion for each [superstep](https://langchain-ai.github.io/langgraph/concepts/#core-design)), we can still stream arbitrary custom functions from within a node using a similar tact and calling `astream_events` on the graph.\n", - "\n", - "We do so using a [RunnableGenerator](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableGenerator.html#langchain-core-runnables-base-runnablegenerator) (which your function will automatically behave as if wrapped as a [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)).\n", - "\n", - "Below is a simple toy example." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "486a01a0", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import AIMessage\n", - "from langchain_core.runnables import RunnableGenerator\n", - "\n", - "from langgraph.graph import START, StateGraph\n", - "\n", - "# Define a new graph\n", - "workflow = StateGraph(State)\n", - "\n", - "\n", - "async def my_generator(state: State):\n", - " messages = [\n", - " \"Four\",\n", - " \"score\",\n", - " \"and\",\n", - " \"seven\",\n", - " \"years\",\n", - " \"ago\",\n", - " \"our\",\n", - " \"fathers\",\n", - " \"...\",\n", - " ]\n", - " for message in messages:\n", - " yield message\n", - "\n", - "\n", - "async def my_node(state: State, config: RunnableConfig):\n", - " messages = []\n", - " # Tagging a node makes it easy to filter out which events to include in your stream\n", - " # It's completely optional, but useful if you have many functions with similar names\n", - " gen = RunnableGenerator(my_generator).with_config(tags=[\"should_stream\"])\n", - " async for message in gen.astream(state):\n", - " messages.append(message)\n", - " return {\"messages\": [AIMessage(content=\" \".join(messages))]}\n", - "\n", - "\n", - "workflow.add_node(\"model\", my_node)\n", - "workflow.add_edge(START, \"model\")\n", - "workflow.add_edge(\"model\", END)\n", - "app = workflow.compile()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "ce773a40", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'chunk': 'Four'}|{'chunk': 'score'}|{'chunk': 'and'}|{'chunk': 'seven'}|{'chunk': 'years'}|{'chunk': 'ago'}|{'chunk': 'our'}|{'chunk': 'fathers'}|{'chunk': '...'}|" - ] - } - ], - "source": [ - "from langchain_core.messages import HumanMessage\n", - "\n", - "inputs = [HumanMessage(content=\"What are you thinking about?\")]\n", - "async for event in app.astream_events({\"messages\": inputs}, version=\"v1\"):\n", - " kind = event[\"event\"]\n", - " tags = event.get(\"tags\", [])\n", - " if kind == \"on_chain_stream\" and \"should_stream\" in tags:\n", - " data = event[\"data\"]\n", - " if data:\n", - " # Empty content in the context of OpenAI or Anthropic usually means\n", - " # that the model is asking for a tool to be invoked.\n", - " # So we only print non-empty content\n", - " print(data, end=\"|\")" - ] } ], "metadata": { @@ -549,7 +457,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.11.1" } }, "nbformat": 4,