diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py
index d81ddb815..0af25ab33 100644
--- a/docs/_scripts/copy_notebooks.py
+++ b/docs/_scripts/copy_notebooks.py
@@ -19,6 +19,7 @@ _MANUAL = {
"stream-multiple.ipynb",
"streaming-tokens.ipynb",
"streaming-content.ipynb",
+ "streaming-events-from-within-tools.ipynb",
"persistence.ipynb",
"visualization.ipynb",
"state-model.ipynb",
diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md
index df083bbef..3381ac3f9 100644
--- a/docs/docs/how-tos/index.md
+++ b/docs/docs/how-tos/index.md
@@ -37,6 +37,7 @@ These guides show how to use different streaming modes.
- [How to stream LLM tokens](streaming-tokens.ipynb)
- [How to stream arbitrarily nested content](streaming-content.ipynb)
- [How to configure multiple streaming modes at the same time](stream-multiple.ipynb)
+- [How to stream events from within a tool](streaming-events-from-within-tools.ipynb)
## Other
- [How to run graph asynchronously](async.ipynb)
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 0280eee6e..15d8f5a47 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -150,6 +150,7 @@ nav:
- Stream LLM tokens: how-tos/streaming-tokens.ipynb
- Stream arbitrarily nested content: how-tos/streaming-content.ipynb
- Configure multiple streaming modes: how-tos/stream-multiple.ipynb
+ - Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb
- Other:
- Run graph asynchronously: how-tos/async.ipynb
- Visualize your graph: how-tos/visualization.ipynb
diff --git a/examples/streaming-events-from-within-tools.ipynb b/examples/streaming-events-from-within-tools.ipynb
new file mode 100644
index 000000000..059c1186e
--- /dev/null
+++ b/examples/streaming-events-from-within-tools.ipynb
@@ -0,0 +1,291 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "b23ced4e-dc29-43be-9f94-0c36bb181b8a",
+ "metadata": {},
+ "source": [
+ "# How to stream events from within a tool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7044eeb8-4074-4f9c-8a62-962488744557",
+ "metadata": {},
+ "source": [
+ "If your LangGraph graph needs to use tools that call LLMs (or any other LangChain `Runnable` objects -- other graphs, LCEL chains, retrievers, etc.), you might want to stream events from the underlying `Runnable`. This guide shows how you can do that."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a37f60af-43ea-4aa6-847a-df8cc47065f5",
+ "metadata": {},
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "47f79af8-58d8-4a48-8d9a-88823d88701f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%capture --no-stderr\n",
+ "%pip install -U langgraph langchain-openai"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "0cf6b41d-7fcb-40b6-9a72-229cdd00a094",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdin",
+ "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\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e3d02ebb-c2e1-4ef7-b187-810d55139317",
+ "metadata": {},
+ "source": [
+ "## Define graph and tools"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d74a1760-a063-4d05-8c6f-9d16bc31fa82",
+ "metadata": {},
+ "source": [
+ "We'll use a prebuilt ReAct agent for this guide"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "083757a9-26d7-481e-8f3d-3e34bcba154b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from langchain_core.callbacks import Callbacks\n",
+ "from langchain_core.prompts import ChatPromptTemplate\n",
+ "from langchain_core.tools import tool\n",
+ "\n",
+ "from langgraph.prebuilt import create_react_agent\n",
+ "from langchain_openai import ChatOpenAI"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9378fd4a-69e4-49e2-b34c-a98a0505ea35",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
ASYNC IN PYTHON<=3.10
\n",
+ "
\n",
+ "Any Langchain RunnableLambda, a RunnableGenerator, or Tool that invokes other runnables and is running async in python<=3.10, will have to propagate callbacks to child objects manually. This is because LangChain cannot automatically propagate callbacks to child objects in this case.\n",
+ " \n",
+ "This is a common reason why you may fail to see events being emitted from custom runnables or tools.\n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "2cb38dd9-74d8-456d-9e39-4655f2bf3f37",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@tool\n",
+ "async def get_items(place: str, callbacks: Callbacks) -> str: # <--- Accept callbacks (Python <= 3.10)\n",
+ " \"\"\"Use this tool to look up which items are in the given place.\"\"\"\n",
+ " template = ChatPromptTemplate.from_messages(\n",
+ " [\n",
+ " (\n",
+ " \"human\",\n",
+ " \"Can you tell me what kind of items i might find in the following place: '{place}'. \"\n",
+ " \"List at least 3 such items separating them by a comma. And include a brief description of each item..\",\n",
+ " )\n",
+ " ]\n",
+ " )\n",
+ " chain = template | llm.with_config(\n",
+ " {\n",
+ " \"run_name\": \"Get Items LLM\",\n",
+ " \"tags\": [\"tool_llm\"],\n",
+ " \"callbacks\": callbacks, # <-- Propagate callbacks (Python <= 3.10)\n",
+ " }\n",
+ " )\n",
+ " chunks = [chunk async for chunk in chain.astream({\"place\": place})]\n",
+ " return \"\".join(chunk.content for chunk in chunks)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "17279b8a-049d-483d-af63-8a875098e71f",
+ "metadata": {},
+ "source": [
+ "We're adding a custom tag (`tool_llm`) to our LLM runnable within the tool. This will allow us to filter events that we'll stream from the compiled graph (`agent`) Runnable below"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "7254310e-7016-45f7-9795-6d52a1160086",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\")\n",
+ "tools = [get_items]\n",
+ "agent = create_react_agent(llm, tools=tools)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b7d88960-a66b-4699-adee-c12d40b4318a",
+ "metadata": {},
+ "source": [
+ "## Stream events from the graph"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "31fe94ab-80de-4729-843e-5a0fe1bb52c0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/Users/vadymbarda/.virtualenvs/langgraph/lib/python3.12/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"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1. Books - A collection of written or printed works bound together with covers. They can be fiction or non-fiction and come in various genres.\n",
+ "\n",
+ "2. Picture frames - A decorative border for a photograph or artwork, typically made of wood, metal, or plastic. Picture frames are used to display and protect a picture or painting.\n",
+ "\n",
+ "3. Candles - A cylinder of wax with a central wick that is lit to produce light or fragrance. Candles are often used for decoration, ambiance, or religious ceremonies."
+ ]
+ }
+ ],
+ "source": [
+ "async for event in agent.astream_events({\"messages\": [(\"human\", \"what items are on the shelf?\")]}, version=\"v2\"):\n",
+ " tags = event.get(\"tags\", [])\n",
+ " if event[\"event\"] == \"on_chat_model_stream\" and \"tool_llm\" in tags:\n",
+ " print(event[\"data\"][\"chunk\"].content, end=\"\", flush=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ebd8902e-935b-4724-8b5d-551b7674fd34",
+ "metadata": {},
+ "source": [
+ "Let's inspect the last event to get the final list of messages from the agent"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "ca382c1f-b1c7-4c8a-bd9b-7a873b891b3e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "final_messages = event[\"data\"][\"output\"][\"messages\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "3fa7d768-5a84-475a-950e-fd351a44841b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "================================\u001b[1m Human Message \u001b[0m=================================\n",
+ "\n",
+ "what items are on the shelf?\n",
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
+ "Tool Calls:\n",
+ " get_items (call_5CAMZ3asoLsZm9ocMbCOWxYQ)\n",
+ " Call ID: call_5CAMZ3asoLsZm9ocMbCOWxYQ\n",
+ " Args:\n",
+ " place: shelf\n",
+ "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
+ "Name: get_items\n",
+ "\n",
+ "1. Books - A collection of written or printed works bound together with covers. They can be fiction or non-fiction and come in various genres.\n",
+ "\n",
+ "2. Picture frames - A decorative border for a photograph or artwork, typically made of wood, metal, or plastic. Picture frames are used to display and protect a picture or painting.\n",
+ "\n",
+ "3. Candles - A cylinder of wax with a central wick that is lit to produce light or fragrance. Candles are often used for decoration, ambiance, or religious ceremonies.\n",
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
+ "\n",
+ "The items on the shelf are:\n",
+ "1. Books\n",
+ "2. Picture frames\n",
+ "3. Candles\n"
+ ]
+ }
+ ],
+ "source": [
+ "for message in final_messages:\n",
+ " message.pretty_print()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d7f9457c-5665-4cd5-9a99-d54c84270616",
+ "metadata": {},
+ "source": [
+ "You can see that the content of the `ToolMessage` is the same as the output we streamed above"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "langgraph",
+ "language": "python",
+ "name": "langgraph"
+ },
+ "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.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}