diff --git a/examples/streaming-tokens.ipynb b/examples/streaming-tokens.ipynb index abefd51eb..bed3dbe4e 100644 --- a/examples/streaming-tokens.ipynb +++ b/examples/streaming-tokens.ipynb @@ -39,23 +39,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 26, "id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph langchain_openai" + "%pip install --quiet -U langgraph langchain_openai langsmith" ] }, { @@ -68,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "a372be6f", "metadata": {}, "outputs": [], @@ -95,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "907bf5e8", "metadata": {}, "outputs": [], @@ -124,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "17ef4967", "metadata": {}, "outputs": [], @@ -157,7 +147,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 4, "id": "9a8bc61e", "metadata": {}, "outputs": [], @@ -186,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, "id": "4d6ac180", "metadata": {}, "outputs": [], @@ -214,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 6, "id": "42c0af37", "metadata": {}, "outputs": [], @@ -236,7 +226,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 7, "id": "2bbdd3bc", "metadata": {}, "outputs": [], @@ -283,24 +273,26 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 8, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ + "from langgraph.graph import StateGraph, END, START\n", "from langchain_core.runnables import RunnableConfig\n", + "from typing import Literal\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", - "def should_continue(state: State):\n", + "def should_continue(state: State) -> Literal[\"__end__\", \"tools\"]:\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", " if not last_message.tool_calls:\n", - " return \"end\"\n", + " return END\n", " # Otherwise if there is, we continue\n", " else:\n", - " return \"continue\"\n", + " return \"tools\"\n", "\n", "\n", "# Define the function that calls the model\n", @@ -308,7 +300,6 @@ " messages = state[\"messages\"]\n", " # Note: Passing the config through explicitly is required for python < 3.11\n", " # Since context var support wasn't added before then: https://docs.python.org/3/library/asyncio-task.html#creating-tasks\n", - " # (1)\n", " response = await model.ainvoke(messages, config)\n", " # We return a list, because this will get added to the existing list\n", " return {\"messages\": response}" @@ -319,8 +310,6 @@ "id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b", "metadata": {}, "source": [ - "1. :man_raising_hand: I'm a code annotation! I can contain `code`, __formatted\n", - " text__, images, ... basically anything that can be written in Markdown.\n", "## Define the graph\n", "\n", "We can now put it all together and define the graph!" @@ -328,23 +317,21 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 9, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], "source": [ - "from langgraph.graph import StateGraph, END\n", - "\n", "# Define a new graph\n", "workflow = StateGraph(State)\n", "\n", "# Define the two nodes we will cycle between\n", "workflow.add_node(\"agent\", call_model)\n", - "workflow.add_node(\"action\", tool_node)\n", + "workflow.add_node(\"tools\", tool_node)\n", "\n", "# Set the entrypoint as `agent`\n", "# This means that this node is the first one called\n", - "workflow.set_entry_point(\"agent\")\n", + "workflow.add_edge(START, \"agent\")\n", "\n", "# We now add a conditional edge\n", "workflow.add_conditional_edges(\n", @@ -353,23 +340,9 @@ " \"agent\",\n", " # Next, we pass in the function that will determine which node is called next.\n", " should_continue,\n", - " # Finally we pass in a mapping.\n", - " # The keys are strings, and the values are other nodes.\n", - " # END is a special node marking that the graph should finish.\n", - " # What will happen is we will call `should_continue`, and then the output of that\n", - " # will be matched against the keys in this mapping.\n", - " # Based on which one it matches, that node will then be called.\n", - " {\n", - " # If `tools`, then we call the tool node.\n", - " \"continue\": \"action\",\n", - " # Otherwise we finish.\n", - " \"end\": END,\n", - " },\n", ")\n", "\n", - "# We now add a normal edge from `tools` to `agent`.\n", - "# This means that after `tools` is called, `agent` node is called next.\n", - "workflow.add_edge(\"action\", \"agent\")\n", + "workflow.add_edge(\"tools\", \"agent\")\n", "\n", "# Finally, we compile it!\n", "# This compiles it into a LangChain Runnable,\n", @@ -379,13 +352,13 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 10, "id": "72785b66", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/jpeg": "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", "text/plain": [ "" ] @@ -415,10 +388,18 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 11, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/wfh/code/lc/langgraph/.venv/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", @@ -456,13 +437,84 @@ " 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": null, - "id": "08ae8246-11d5-40e1-8567-361e5bef8917", + "execution_count": 15, + "id": "486a01a0", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "from langgraph.graph import StateGraph, END, START\n", + "from langchain_core.runnables import RunnableGenerator\n", + "from langchain_core.messages import AIMessage\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(State)\n", + "\n", + "async def my_generator(state: State):\n", + " messages = [\"Four\", \"score\", \"and\", \"seven\", \"years\", \"ago\", \"our\", \"fathers\", \"...\"]\n", + " for message in messages:\n", + " yield message\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": { @@ -481,7 +533,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.12.2" } }, "nbformat": 4,