diff --git a/examples/streaming-from-final-node.ipynb b/examples/streaming-from-final-node.ipynb index ee7e14a4b..30496a3a1 100644 --- a/examples/streaming-from-final-node.ipynb +++ b/examples/streaming-from-final-node.ipynb @@ -23,7 +23,8 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture --no-stderr\n%pip install -U langgraph langchain-openai" + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" ] }, { @@ -41,7 +42,16 @@ } ], "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\")" + "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\")" ] }, { @@ -55,33 +65,42 @@ { "cell_type": "code", "execution_count": 3, - "id": "1d51c35c-dbf2-4c01-932d-c5d308ea37d2", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Literal\nfrom langchain_community.tools.tavily_search import TavilySearchResults\nfrom langchain_core.runnables import ConfigurableField\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.prebuilt import create_react_agent\nfrom langgraph.prebuilt import ToolNode\n\n\n@tool\ndef 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\ntools = [get_weather]\nmodel = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\nfinal_model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n\nmodel = model.bind_tools(tools)\n# NOTE: this is where we're adding a tag that we'll be using later to filter the outputs of the final node\nfinal_model = final_model.with_config(tags=[\"final_node\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0af37212-e592-484d-9194-35d53fa79678", + "id": "5e62618d-0e0c-483c-acd3-40a26e61894a", "metadata": {}, "outputs": [], "source": [ + "from typing import Literal\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.runnables import ConfigurableField\n", + "from langchain_core.tools import tool\n", + "from langchain_openai import ChatOpenAI\n", + "from langgraph.prebuilt import create_react_agent\n", + "from langgraph.prebuilt import ToolNode\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", + "model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", + "final_model = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", + "\n", + "model = model.bind_tools(tools)\n", + "# NOTE: this is where we're adding a tag that we'll can use later to filter the model stream events to only the model called in the final node.\n", + "# This is not necessary if you call a single LLM but might be important in case you call multiple models within the node and want to filter events\n", + "# from only one of them.\n", + "final_model = final_model.with_config(tags=[\"final_node\"])\n", "tool_node = ToolNode(tools=tools)" ] }, - { - "cell_type": "code", - "execution_count": 5, - "id": "ac9d4f5b-655a-48f3-b514-a4a0815714a6", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import TypedDict, Annotated\n\nfrom langgraph.graph import END, StateGraph, START\nfrom langgraph.graph.message import MessagesState\nfrom langchain_core.messages import BaseMessage" - ] - }, { "cell_type": "markdown", "id": "9acef997-5dd6-4108-baf1-c4d6be3e4999", @@ -92,21 +111,18 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "3948c6b8-0317-4001-b699-32b25306a023", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import SystemMessage, HumanMessage" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2efe9fb4-c6c2-4171-becd-d45bbf899209", + "execution_count": 4, + "id": "8c7339d2-1835-4b5a-a99c-a60e150280af", "metadata": {}, "outputs": [], "source": [ + "from typing import TypedDict, Annotated\n", + "\n", + "from langgraph.graph import END, StateGraph, START\n", + "from langgraph.graph.message import MessagesState\n", + "from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage\n", + "\n", + "\n", "def should_continue(state: MessagesState) -> Literal[\"tools\", \"final\"]:\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", @@ -135,16 +151,8 @@ " )\n", " # overwrite the last AI message from the agent\n", " response.id = last_ai_message.id\n", - " return {\"messages\": [response]}" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "b1a9a981-8629-4d25-a0e1-d666c3968b30", - "metadata": {}, - "outputs": [], - "source": [ + " return {\"messages\": [response]}\n", + "\n", "workflow = StateGraph(MessagesState)\n", "\n", "workflow.add_node(\"agent\", call_model)\n", @@ -159,38 +167,20 @@ ")\n", "\n", "workflow.add_edge(\"tools\", \"agent\")\n", - "workflow.add_edge(\"final\", END)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a7b0251f-dcee-49d6-8133-af50d4a55e22", - "metadata": {}, - "outputs": [], - "source": [ + "workflow.add_edge(\"final\", END)\n", + "\n", "app = workflow.compile()" ] }, { "cell_type": "code", - "execution_count": 10, - "id": "f8b77e74-17e9-4fee-a164-4637013b55ff", - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import display, Image" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "98adcef2-1dec-4503-99cc-48613cdb7a85", + "execution_count": 5, + "id": "2ab6d079-ba06-48ba-abe5-e72df24407af", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/jpeg": "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", "text/plain": [ "" ] @@ -200,6 +190,8 @@ } ], "source": [ + "from IPython.display import display, Image\n", + "\n", "display(Image(app.get_graph().draw_mermaid_png()))" ] }, @@ -213,18 +205,38 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 6, + "id": "84d65cbe-4cfe-44f8-b49e-b37632887c91", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "from langchain_core._api import LangChainBetaWarning\n", + "warnings.filterwarnings('ignore', category=LangChainBetaWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "5cfaeb64-5506-4546-96c0-4891e6288ad9", + "metadata": {}, + "source": [ + "### Filter on event metadata" + ] + }, + { + "cell_type": "markdown", + "id": "f218a05d-1590-4d5c-b0b7-97d94c744efb", + "metadata": {}, + "source": [ + "First option to get the LLM events from within a specific node (`final` node in our case) is to filter on the `langgraph_node` field in the event metadata. This will be sufficient in case you need to stream events from ALL LLM calls inside the node. This means that if you have multiple different LLMs invoked inside the node, this filter will include events from all of them." + ] + }, + { + "cell_type": "code", + "execution_count": 7, "id": "a37c3a5f-5a43-46db-940e-c583df776520", "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", @@ -234,15 +246,71 @@ } ], "source": [ - "inputs = {\"messages\": [(\"human\", \"what's the weather in nyc?\")]}\nasync for event in app.astream_events(inputs, version=\"v2\"):\n kind = event[\"event\"]\n tags = event.get(\"tags\", [])\n if kind == \"on_chat_model_stream\" and \"final_node\" in tags:\n data = event[\"data\"]\n if data[\"chunk\"].content:\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[\"chunk\"].content, end=\"|\")" + "inputs = {\"messages\": [(\"human\", \"what's the weather in nyc?\")]}\n", + "async for event in app.astream_events(inputs, version=\"v2\"):\n", + " kind = event[\"event\"]\n", + " tags = event.get(\"tags\", [])\n", + " # filter on the langgraph node name\n", + " if kind == \"on_chat_model_stream\" and event[\"metadata\"].get(\"langgraph_node\") == \"final\":\n", + " data = event[\"data\"]\n", + " if data[\"chunk\"].content:\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[\"chunk\"].content, end=\"|\", flush=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b0bb447a-6650-4166-b124-2d5b99a1f88b", + "metadata": {}, + "source": [ + "### Filter on custom tags" + ] + }, + { + "cell_type": "markdown", + "id": "ea4db927-44b6-46ab-8b8d-f237edaf1438", + "metadata": {}, + "source": [ + "Alternatively, you can add configuration with custom tags to your LLM, like we did in the beginning, by adding `final_model.with_config(tags=[\"final_node\"])`. This will allow us to more precisely filter the events to keep the ones only from this model." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "55d60dfa-96e3-442f-9924-0c99f46baed8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Well| folks|,| looks| like| we|'ve| got| some| cloudy| skies| in| the| Big| Apple| today|.| So| grab| your| umbrella| just| in| case|,| and| don|'t| let| those| clouds| rain| on| your| parade|!|" + ] + } + ], + "source": [ + "inputs = {\"messages\": [(\"human\", \"what's the weather in nyc?\")]}\n", + "async for event in app.astream_events(inputs, version=\"v2\"):\n", + " kind = event[\"event\"]\n", + " tags = event.get(\"tags\", [])\n", + " # filter on the custom tag\n", + " if kind == \"on_chat_model_stream\" and \"final_node\" in event.get(\"tags\", []):\n", + " data = event[\"data\"]\n", + " if data[\"chunk\"].content:\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[\"chunk\"].content, end=\"|\", flush=True)" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "langgraph", "language": "python", - "name": "python3" + "name": "langgraph" }, "language_info": { "codemirror_mode": { @@ -254,7 +322,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.9" } }, "nbformat": 4,