From f88ef74002c22dac124439e1eeba3ac03b62d7f9 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 1 Apr 2024 17:17:39 -0700 Subject: [PATCH] Add tests for both values of CheckpointAt - fix bug when using at=END_OF_RUN together with interrupt_before - fix bug when calling update_state after only node has run - update some notebooks to new streaming format (ie. no __end__ node) --- examples/human-in-the-loop.ipynb | 87 +- examples/persistence.ipynb | 40 +- examples/time-travel.ipynb | 76 +- langgraph/pregel/__init__.py | 21 +- tests/__snapshots__/test_pregel.ambr | 1542 +++++++++++++++++++++++++- tests/test_pregel.py | 94 +- tests/test_pregel_async.py | 90 +- 7 files changed, 1797 insertions(+), 153 deletions(-) diff --git a/examples/human-in-the-loop.ipynb b/examples/human-in-the-loop.ipynb index fef9e0737..1ab370ed7 100644 --- a/examples/human-in-the-loop.ipynb +++ b/examples/human-in-the-loop.ipynb @@ -373,6 +373,38 @@ "app = workflow.compile(checkpointer=memory, interrupt_before=[\"action\"])" ] }, + { + "cell_type": "markdown", + "id": "d5b8e9dd-856b-42de-a9b6-8060a24d4c3e", + "metadata": {}, + "source": [ + "## Preview the graph" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cd13cc10-c74c-415c-871d-ca2a1e547cff", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Image\n", + "\n", + "Image(app.get_graph().draw_png())" + ] + }, { "cell_type": "markdown", "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159", @@ -385,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -393,23 +425,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='Hello Bob! How can I assist you today?'\n" + "content='Hello Bob! How can I assist you today?' id='76cf2bad-06c7-4210-9258-9595d7de6dea'\n" ] } ], "source": [ "from langchain_core.messages import HumanMessage\n", "\n", + "thread = {\"configurable\": {\"thread_id\": \"2\"}}\n", "inputs = [HumanMessage(content=\"hi! I'm bob\")]\n", - "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "for event in app.stream(inputs, thread):\n", + " for v in event.values():\n", + " print(v)" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "08ae8246-11d5-40e1-8567-361e5bef8917", "metadata": {}, "outputs": [ @@ -417,21 +449,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='Your name is Bob! How can I help you, Bob?'\n" + "content='Your name is Bob. How can I help you, Bob?' id='7dbe3b16-df76-4053-93a3-1db95e51954f'\n" ] } ], "source": [ "inputs = [HumanMessage(content=\"what is my name?\")]\n", - "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "for event in app.stream(inputs, thread):\n", + " for v in event.values():\n", + " print(v)" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba", "metadata": {}, "outputs": [ @@ -439,16 +470,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='' additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}\n" + "content='' additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco now\"}', 'name': 'tavily_search_results_json'}} id='aef7d52c-9127-409d-8428-b6a6c33ee6b4'\n" ] } ], "source": [ "inputs = [HumanMessage(content=\"what's the weather in sf now?\")]\n", - "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "for event in app.stream(inputs, thread):\n", + " for v in event.values():\n", + " print(v)" ] }, { @@ -463,7 +493,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "51923913-20f7-4ee1-b9ba-d01f5fb2869b", "metadata": {}, "outputs": [ @@ -471,17 +501,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='[{\\'url\\': \\'https://weather.com/weather/hourbyhour/l/USCA0987:1:US\\', \\'content\\': \"recents\\\\nSpecialty Forecasts\\\\nHourly Weather-San Francisco, CA\\\\nBeach Hazard Statement\\\\nSaturday, November 25\\\\n5 pm\\\\nClear\\\\n6 pm\\\\nClear\\\\n7 pm\\\\nClear\\\\n8 pm\\\\nMostly Clear\\\\n9 pm\\\\nPartly Cloudy\\\\n10 pm\\\\nPartly Cloudy\\\\n11 pm\\\\nPartly Cloudy\\\\nSunday, November 26\\\\n12 am\\\\nPartly Cloudy\\\\n1 am\\\\nMostly Cloudy\\\\n2 am\\\\nMostly Cloudy\\\\n3 am\\\\nMostly Cloudy\\\\n4 am\\\\nCloudy\\\\n5 am\\\\nCloudy\\\\n6 am\\\\nMostly Cloudy\\\\n7 am\\\\nMostly Cloudy\\\\n8 am\\\\nMostly Cloudy\\\\n9 am\\\\nMostly Cloudy\\\\n10 am\\\\nPartly Cloudy\\\\n11 am\\\\nPartly Cloudy\\\\n12 pm\\\\nPartly Cloudy\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nPartly Cloudy\\\\n3 pm\\\\nPartly Cloudy\\\\n4 pm\\\\nPartly Cloudy\\\\n5 pm\\\\nPartly Cloudy\\\\n6 pm\\\\nPartly Cloudy\\\\n7 pm\\\\nPartly Cloudy\\\\n8 pm\\\\nPartly Cloudy\\\\n9 pm\\\\nMostly Clear\\\\n10 pm\\\\nMostly Clear\\\\n11 pm\\\\nMostly Clear\\\\nMonday, November 27\\\\n12 am\\\\nClear\\\\n1 am\\\\nMostly Clear\\\\n2 am\\\\nMostly Clear\\\\n3 am\\\\nPartly Cloudy\\\\n4 am\\\\nMostly Clear\\\\n5 am\\\\nMostly Clear\\\\n6 am\\\\nClear\\\\n7 am\\\\nClear\\\\n8 am\\\\nSunny\\\\n9 am\\\\nSunny\\\\n10 am\\\\nSunny\\\\n11 am\\\\nSunny\\\\n12 pm\\\\nMostly Sunny\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nMostly Sunny\\\\n3 pm\\\\nMostly Sunny\\\\n4 pm\\\\nPartly Cloudy\\\\nRadar\\\\nSafety First!\\\\n Don\\'t Miss\\\\nIrresistible\\\\nWeather Wonders\\\\nOur Amazing World\\\\nCelestial Symphony\\\\nFried Turkey Fail\\\\nLook At That!\\\\n Health & Activities\\\\nSeasonal Allergies and Pollen Count Forecast\\\\nNo pollen detected in your area\\\\nCold & Flu Forecast\\\\nFlu risk is low in your area\\\\nWe recognize our responsibility to use data and technology for good. Changes For Critters\\\\nHurricane Tracker\\\\nStay Safe\\\\nAir Quality Index\\\\nAir quality is considered satisfactory, and air pollution poses little or no risk.\\\\n Take control of your data.\\\\n\"}]' name='tavily_search_results_json'\n", - "content='The current weather in San Francisco is partly cloudy. The temperature is expected to be pleasant. If you need more detailed information, you can visit [this link](https://weather.com/weather/hourbyhour/l/USCA0987:1:US). Is there anything else you would like to know, Bob?'\n" + "content='[{\\'url\\': \\'https://weather.com/weather/hourbyhour/l/USCA0987:1:US\\', \\'content\\': \"recents\\\\nSpecialty Forecasts\\\\nHourly Weather-San Francisco, CA\\\\nBeach Hazard Statement\\\\nSaturday, November 25\\\\n5 pm\\\\nClear\\\\n6 pm\\\\nClear\\\\n7 pm\\\\nClear\\\\n8 pm\\\\nMostly Clear\\\\n9 pm\\\\nPartly Cloudy\\\\n10 pm\\\\nPartly Cloudy\\\\n11 pm\\\\nPartly Cloudy\\\\nSunday, November 26\\\\n12 am\\\\nPartly Cloudy\\\\n1 am\\\\nMostly Cloudy\\\\n2 am\\\\nMostly Cloudy\\\\n3 am\\\\nMostly Cloudy\\\\n4 am\\\\nCloudy\\\\n5 am\\\\nCloudy\\\\n6 am\\\\nMostly Cloudy\\\\n7 am\\\\nMostly Cloudy\\\\n8 am\\\\nMostly Cloudy\\\\n9 am\\\\nMostly Cloudy\\\\n10 am\\\\nPartly Cloudy\\\\n11 am\\\\nPartly Cloudy\\\\n12 pm\\\\nPartly Cloudy\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nPartly Cloudy\\\\n3 pm\\\\nPartly Cloudy\\\\n4 pm\\\\nPartly Cloudy\\\\n5 pm\\\\nPartly Cloudy\\\\n6 pm\\\\nPartly Cloudy\\\\n7 pm\\\\nPartly Cloudy\\\\n8 pm\\\\nPartly Cloudy\\\\n9 pm\\\\nMostly Clear\\\\n10 pm\\\\nMostly Clear\\\\n11 pm\\\\nMostly Clear\\\\nMonday, November 27\\\\n12 am\\\\nClear\\\\n1 am\\\\nMostly Clear\\\\n2 am\\\\nMostly Clear\\\\n3 am\\\\nPartly Cloudy\\\\n4 am\\\\nMostly Clear\\\\n5 am\\\\nMostly Clear\\\\n6 am\\\\nClear\\\\n7 am\\\\nClear\\\\n8 am\\\\nSunny\\\\n9 am\\\\nSunny\\\\n10 am\\\\nSunny\\\\n11 am\\\\nSunny\\\\n12 pm\\\\nMostly Sunny\\\\n1 pm\\\\nPartly Cloudy\\\\n2 pm\\\\nMostly Sunny\\\\n3 pm\\\\nMostly Sunny\\\\n4 pm\\\\nPartly Cloudy\\\\nRadar\\\\nSafety First!\\\\n Don\\'t Miss\\\\nIrresistible\\\\nWeather Wonders\\\\nOur Amazing World\\\\nCelestial Symphony\\\\nFried Turkey Fail\\\\nLook At That!\\\\n Health & Activities\\\\nSeasonal Allergies and Pollen Count Forecast\\\\nNo pollen detected in your area\\\\nCold & Flu Forecast\\\\nFlu risk is low in your area\\\\nWe recognize our responsibility to use data and technology for good. Changes For Critters\\\\nHurricane Tracker\\\\nStay Safe\\\\nAir Quality Index\\\\nAir quality is considered satisfactory, and air pollution poses little or no risk.\\\\n Take control of your data.\\\\n\"}]' name='tavily_search_results_json' id='7db116fc-37bc-4821-a444-3c4f7467b469'\n", + "content='The current weather in San Francisco is clear. The hourly forecast shows clear skies for the next few hours. If you need more detailed information, you can visit [this link](https://weather.com/weather/hourbyhour/l/USCA0987:1:US). Let me know if you need any more assistance!' id='ddfdbdbf-7fec-4c65-93d7-d12c4eb8f163'\n" ] } ], "source": [ - "for event in app.stream(None, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "for event in app.stream(None, thread):\n", + " for v in event.values():\n", + " print(v)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c599e69a-83c9-4ec0-83ec-abf555d6b7f3", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/examples/persistence.ipynb b/examples/persistence.ipynb index 9272e5079..2cb1ee988 100644 --- a/examples/persistence.ipynb +++ b/examples/persistence.ipynb @@ -369,7 +369,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -377,23 +377,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='Hello Bob! How can I assist you today?'\n" + "content='Hello Bob! How can I assist you today?' id='a34fcaf6-a812-47f8-921d-4ba57b8271cd'\n" ] } ], "source": [ "from langchain_core.messages import HumanMessage\n", "\n", - "inputs = [HumanMessage(content=\"hi! I'm bob\")]\n", - "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "thread = {\"configurable\": {\"thread_id\": \"2\"}}\n", + "inputs = HumanMessage(content=\"hi! I'm bob\")\n", + "for event in app.stream(inputs, thread):\n", + " for v in event.values():\n", + " print(v)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "08ae8246-11d5-40e1-8567-361e5bef8917", "metadata": {}, "outputs": [ @@ -401,16 +401,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='Your name is Bob.'\n" + "content='Your name is Bob. How can I help you, Bob?' id='dcf332e8-f9be-41c1-8a09-7a815a7de6d0'\n" ] } ], "source": [ - "inputs = [HumanMessage(content=\"what is my name?\")]\n", - "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"2\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + "inputs = HumanMessage(content=\"what is my name?\")\n", + "for event in app.stream(inputs, thread):\n", + " for v in event.values():\n", + " print(v)" ] }, { @@ -423,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba", "metadata": {}, "outputs": [ @@ -431,16 +430,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content=\"I'm sorry, but I don't have access to personal information.\"\n" + "content=\"I'm sorry, but I don't have access to your personal information, including your name. How can I assist you today?\" id='d7ef71f4-0393-4770-b8aa-969756791ec6'\n" ] } ], "source": [ - "inputs = [HumanMessage(content=\"what is my name?\")]\n", + "inputs = HumanMessage(content=\"what is my name?\")\n", "for event in app.stream(inputs, {\"configurable\": {\"thread_id\": \"3\"}}):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + " for v in event.values():\n", + " print(v)" ] }, { @@ -468,7 +466,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/examples/time-travel.ipynb b/examples/time-travel.ipynb index 7a9c1dc84..c34f49cf9 100644 --- a/examples/time-travel.ipynb +++ b/examples/time-travel.ipynb @@ -377,7 +377,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='Hello Bob! How can I assist you today?' response_metadata={'finish_reason': 'stop', 'logprobs': None} id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'\n" + "content='Hello Bob! How can I assist you today?' id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'\n" ] } ], @@ -385,11 +385,10 @@ "from langchain_core.messages import HumanMessage\n", "\n", "thread = {\"configurable\": {\"thread_id\": '3'}}\n", - "inputs = [HumanMessage(content=\"hi! I'm bob\")]\n", + "inputs = HumanMessage(content=\"hi! I'm bob\")\n", "for event in app.stream(inputs, thread):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + " for v in event.values():\n", + " print(v)" ] }, { @@ -409,7 +408,7 @@ { "data": { "text/plain": [ - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:10.780714+00:00'}})" + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:43.801248+00:00'}}, parent_config=None)" ] }, "execution_count": 10, @@ -439,16 +438,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='' additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}} response_metadata={'finish_reason': 'function_call', 'logprobs': None} id='af48af47-01bc-4099-a7d6-14cc2df5709e'\n" + "content='' additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}} id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f'\n" ] } ], "source": [ - "inputs = [HumanMessage(content=\"what is the weather in sf currently\")]\n", + "inputs = HumanMessage(content=\"what is the weather in sf currently\")\n", "for event in app.stream(inputs, thread):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + " for v in event.values():\n", + " print(v)" ] }, { @@ -511,7 +509,7 @@ "data": { "text/plain": [ "{'configurable': {'thread_id': '3',\n", - " 'thread_ts': '2024-03-15T21:24:37.208417+00:00'}}" + " 'thread_ts': '2024-04-02T00:09:44.323782+00:00'}}" ] }, "execution_count": 14, @@ -540,7 +538,7 @@ { "data": { "text/plain": [ - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e')], next=('agent:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:37.208417+00:00'}})" + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'), HumanMessage(content='what is the weather in sf currently', id='4138a1b0-3053-4b12-ad15-6198be969947'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f')], next=('action',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:44.323782+00:00'}}, parent_config=None)" ] }, "execution_count": 15, @@ -570,16 +568,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='[{\\'url\\': \\'https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US\\', \\'content\\': \"Today\\'s and tonight\\'s San Francisco, CA weather forecast, weather conditions and Doppler radar from The Weather Channel and Weather.com\"}]' name='tavily_search_results_json' id='dbba7582-c1b3-4d6a-8727-9421c431f7dc'\n", - "content='You can check the current weather in San Francisco by visiting [The Weather Channel](https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US).' response_metadata={'finish_reason': 'stop', 'logprobs': None} id='4a32c94c-63bc-4f65-b9dd-bbaeaff3c54a'\n" + "content=\"[{'url': 'https://forecast.weather.gov/zipcity.php?inputstring=San francisco,CA', 'content': 'Detailed Forecast. Today. Mostly sunny, with a high near 62. Light and variable wind becoming west southwest 5 to 8 mph in the afternoon. Tonight. Mostly clear, with a low around 49. West wind 5 to 8 mph becoming north northwest after midnight. Monday. Sunny, with a high near 67.'}]\" name='tavily_search_results_json' id='388a34ca-6b79-4eb2-84e7-34849ce7c4cf'\n", + "content='The weather in San Francisco today is mostly sunny with a high near 62 degrees Fahrenheit. The wind is light and variable, becoming west-southwest at 5 to 8 mph in the afternoon. Tonight will be mostly clear with a low around 49 degrees Fahrenheit. Tomorrow is expected to be sunny with a high near 67 degrees Fahrenheit.' id='5d5ac23c-0e64-462d-b9ef-c499a417e906'\n" ] } ], "source": [ "for event in app.stream(None, thread):\n", - " for k, v in event.items():\n", - " if k != \"__end__\":\n", - " print(v)" + " for v in event.values():\n", + " print(v)" ] }, { @@ -594,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 19, "id": "8578a66d-6489-4e03-8c23-fd0530278455", "metadata": {}, "outputs": [ @@ -602,31 +599,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e'), FunctionMessage(content='[{\\'url\\': \\'https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US\\', \\'content\\': \"Today\\'s and tonight\\'s San Francisco, CA weather forecast, weather conditions and Doppler radar from The Weather Channel and Weather.com\"}]', name='tavily_search_results_json', id='dbba7582-c1b3-4d6a-8727-9421c431f7dc'), AIMessage(content='You can check the current weather in San Francisco by visiting [The Weather Channel](https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US).', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='4a32c94c-63bc-4f65-b9dd-bbaeaff3c54a')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:59.635105+00:00'}})\n", + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'), HumanMessage(content='what is the weather in sf currently', id='4138a1b0-3053-4b12-ad15-6198be969947'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f'), FunctionMessage(content=\"[{'url': 'https://forecast.weather.gov/zipcity.php?inputstring=San francisco,CA', 'content': 'Detailed Forecast. Today. Mostly sunny, with a high near 62. Light and variable wind becoming west southwest 5 to 8 mph in the afternoon. Tonight. Mostly clear, with a low around 49. West wind 5 to 8 mph becoming north northwest after midnight. Monday. Sunny, with a high near 67.'}]\", name='tavily_search_results_json', id='388a34ca-6b79-4eb2-84e7-34849ce7c4cf'), AIMessage(content='The weather in San Francisco today is mostly sunny with a high near 62 degrees Fahrenheit. The wind is light and variable, becoming west-southwest at 5 to 8 mph in the afternoon. Tonight will be mostly clear with a low around 49 degrees Fahrenheit. Tomorrow is expected to be sunny with a high near 67 degrees Fahrenheit.', id='5d5ac23c-0e64-462d-b9ef-c499a417e906')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:48.961894+00:00'}}, parent_config=None)\n", "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e'), FunctionMessage(content='[{\\'url\\': \\'https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US\\', \\'content\\': \"Today\\'s and tonight\\'s San Francisco, CA weather forecast, weather conditions and Doppler radar from The Weather Channel and Weather.com\"}]', name='tavily_search_results_json', id='dbba7582-c1b3-4d6a-8727-9421c431f7dc'), AIMessage(content='You can check the current weather in San Francisco by visiting [The Weather Channel](https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US).', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='4a32c94c-63bc-4f65-b9dd-bbaeaff3c54a')], next=('agent:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:59.629900+00:00'}})\n", + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'), HumanMessage(content='what is the weather in sf currently', id='4138a1b0-3053-4b12-ad15-6198be969947'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f')], next=('action',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:44.323782+00:00'}}, parent_config=None)\n", "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e'), FunctionMessage(content='[{\\'url\\': \\'https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US\\', \\'content\\': \"Today\\'s and tonight\\'s San Francisco, CA weather forecast, weather conditions and Doppler radar from The Weather Channel and Weather.com\"}]', name='tavily_search_results_json', id='dbba7582-c1b3-4d6a-8727-9421c431f7dc')], next=('agent',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:58.501730+00:00'}})\n", + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'), HumanMessage(content='what is the weather in sf currently', id='4138a1b0-3053-4b12-ad15-6198be969947'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}, id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f')], next=('action',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:44.242352+00:00'}}, parent_config=None)\n", "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e'), FunctionMessage(content='[{\\'url\\': \\'https://weather.com/weather/today/l/San+Francisco+CA+USCA0987:1:US\\', \\'content\\': \"Today\\'s and tonight\\'s San Francisco, CA weather forecast, weather conditions and Doppler radar from The Weather Channel and Weather.com\"}]', name='tavily_search_results_json', id='dbba7582-c1b3-4d6a-8727-9421c431f7dc')], next=('action:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:58.498596+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e')], next=('action',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:57.092121+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco today\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e')], next=('agent:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:37.208417+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}, response_metadata={'finish_reason': 'function_call', 'logprobs': None}, id='af48af47-01bc-4099-a7d6-14cc2df5709e')], next=('agent:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:20.413423+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63')], next=('agent',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:19.721216+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63')], next=('__start__:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:19.717873+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:10.780714+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1')], next=('agent:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:10.773938+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d')], next=('agent',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:10.163843+00:00'}})\n", - "--\n", - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d')], next=('__start__:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:10.162581+00:00'}})\n", + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0')], next=(), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:43.801248+00:00'}}, parent_config=None)\n", "--\n" ] } @@ -635,7 +614,7 @@ "for state in app.get_state_history(thread):\n", " print(state)\n", " print('--')\n", - " if len(state.values) == 3:\n", + " if len(state.values) == 4:\n", " to_replay = state" ] }, @@ -649,17 +628,17 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "21e7fc18-6fd9-4e11-a84b-e0325c9640c8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='5e326da4-44ee-4334-b2e6-869dd6d3218d'), AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'finish_reason': 'stop', 'logprobs': None}, id='9467300a-f8ea-47db-9ade-3d9130c9f6d1'), HumanMessage(content='what is the weather in sf currently', id='bc16849a-fa20-4f07-815c-b254c6d77f63')], next=('__start__:edges',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-03-15T21:24:19.717873+00:00'}})" + "StateSnapshot(values=[HumanMessage(content=\"hi! I'm bob\", id='774c03a0-4f61-49f1-8447-6fa5cbc3e56e'), AIMessage(content='Hello Bob! How can I assist you today?', id='019fe7a8-f43d-4f83-8bbd-b1c553f32aa0'), HumanMessage(content='what is the weather in sf currently', id='4138a1b0-3053-4b12-ad15-6198be969947'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}}, id='79bcb08c-0cb4-4b37-aa68-4df08b3a290f')], next=('action',), config={'configurable': {'thread_id': '3', 'thread_ts': '2024-04-02T00:09:44.242352+00:00'}}, parent_config=None)" ] }, - "execution_count": 18, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -678,7 +657,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "e986f94f-706f-4b6f-b3c4-f95483b9e9b8", "metadata": {}, "outputs": [ @@ -686,7 +665,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='' additional_kwargs={'function_call': {'arguments': '{\"query\":\"current weather in San Francisco\"}', 'name': 'tavily_search_results_json'}} response_metadata={'finish_reason': 'function_call', 'logprobs': None} id='3a853de3-bdd9-4186-9f10-56bd151f63da'\n" + "content=\"[{'url': 'https://www.accuweather.com/en/us/san-francisco/94103/current-weather/347629', 'content': 'Current weather in San Francisco, CA. Check current conditions in San Francisco, CA with radar, hourly, and more.'}]\" name='tavily_search_results_json' id='6ce0ce23-4fb7-4d3a-acbf-f882f8d52054'\n", + "content='You can check the current weather in San Francisco by visiting [AccuWeather](https://www.accuweather.com/en/us/san-francisco/94103/current-weather/347629).' id='0f071adc-a949-4242-ac3e-7ccd2132653d'\n" ] } ], @@ -722,7 +702,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/langgraph/pregel/__init__.py b/langgraph/pregel/__init__.py index d2aee38ac..dfe2ac00f 100644 --- a/langgraph/pregel/__init__.py +++ b/langgraph/pregel/__init__.py @@ -407,7 +407,9 @@ class Pregel( ) # if two nodes updated the state at the same time, it's ambiguous if last_seen_by_node: - if last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: as_node = last_seen_by_node[-1][1] if as_node is None: raise InvalidUpdateError("Ambiguous update, specify as_node") @@ -464,7 +466,9 @@ class Pregel( ) # if two nodes updated the state at the same time, it's ambiguous if last_seen_by_node: - if last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: as_node = last_seen_by_node[-1][1] if as_node is None: raise InvalidUpdateError("Ambiguous update, specify as_node") @@ -611,7 +615,7 @@ class Pregel( # channels are guaranteed to be immutable for the duration of the step, # with channel updates applied only at the transition between steps for step in range(config["recursion_limit"] + 1): - checkpoint, next_tasks = _prepare_next_tasks( + next_checkpoint, next_tasks = _prepare_next_tasks( checkpoint, processes, channels, for_execution=True ) @@ -636,6 +640,8 @@ class Pregel( next_tasks, ): break + else: + checkpoint = next_checkpoint if debug: print_step_start(step, next_tasks) @@ -816,7 +822,7 @@ class Pregel( # channels are guaranteed to be immutable for the duration of the step, # channel updates being applied only at the transition between steps for step in range(config["recursion_limit"] + 1): - checkpoint, next_tasks = _prepare_next_tasks( + next_checkpoint, next_tasks = _prepare_next_tasks( checkpoint, processes, channels, for_execution=True ) @@ -841,6 +847,8 @@ class Pregel( next_tasks, ): break + else: + checkpoint = next_checkpoint if debug: print_step_start(step, next_tasks) @@ -1038,11 +1046,12 @@ def _should_interrupt( snapshot_channels: Sequence[str], tasks: list[PregelExecutableTask], ) -> bool: + # defaultdicts are mutated on access :( so we need to copy + seen = checkpoint["versions_seen"].copy()[INTERRUPT].copy() return ( # interrupt if any of snapshopt_channels has been updated since last interrupt any( - checkpoint["channel_versions"][chan] - > checkpoint["versions_seen"][INTERRUPT][chan] + checkpoint["channel_versions"][chan] > seen[chan] for chan in snapshot_channels ) # and any channel written to is in interrupt_nodes list diff --git a/tests/__snapshots__/test_pregel.ambr b/tests/__snapshots__/test_pregel.ambr index 874c691f7..7159161b2 100644 --- a/tests/__snapshots__/test_pregel.ambr +++ b/tests/__snapshots__/test_pregel.ambr @@ -307,7 +307,7 @@ +---------+ ''' # --- -# name: test_conditional_graph +# name: test_conditional_graph[end_of_run] ''' { "nodes": [ @@ -384,7 +384,7 @@ } ''' # --- -# name: test_conditional_graph.1 +# name: test_conditional_graph[end_of_run].1 ''' +-----------+ | __start__ | @@ -409,7 +409,7 @@ +---------+ +-------+ ''' # --- -# name: test_conditional_graph.2 +# name: test_conditional_graph[end_of_run].2 ''' { "nodes": [ @@ -647,7 +647,7 @@ } ''' # --- -# name: test_conditional_graph.3 +# name: test_conditional_graph[end_of_run].3 ''' +-----------+ | __start__ | @@ -696,13 +696,402 @@ +---------+ +-------+ ''' # --- -# name: test_conditional_graph_state +# name: test_conditional_graph[end_of_step] + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": { + "title": "LangGraphInput" + } + }, + { + "id": "__end__", + "type": "schema", + "data": { + "title": "LangGraphOutput" + } + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnableAssign" + ], + "name": "RunnableAssign" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "name": "execute_tools" + } + }, + { + "id": "agent_should_continue", + "type": "unknown", + "data": "agent_should_continue" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "agent_should_continue" + }, + { + "source": "agent_should_continue", + "target": "tools", + "data": "continue" + }, + { + "source": "agent_should_continue", + "target": "__end__", + "data": "exit" + } + ] + } + ''' +# --- +# name: test_conditional_graph[end_of_step].1 + ''' + +-----------+ + | __start__ | + +-----------+ + * + * + * + +-------+ + | agent | + *+-------+* + ** *** + ** ** + ** ** + +-----------------------+ ** + | agent_should_continue | * + +-----------------------+ * + * **** * + * ***** * + * *** * + +---------+ +-------+ + | __end__ | | tools | + +---------+ +-------+ + ''' +# --- +# name: test_conditional_graph[end_of_step].2 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": { + "title": "LangGraphInput" + } + }, + { + "id": "__end__", + "type": "schema", + "data": { + "title": "LangGraphOutput" + } + }, + { + "id": 2, + "type": "schema", + "data": { + "title": "RunnableParallelInput", + "type": "object", + "properties": {} + } + }, + { + "id": 3, + "type": "schema", + "data": { + "title": "RunnableParallelOutput", + "type": "object", + "properties": { + "agent_outcome": { + "title": "Agent Outcome", + "anyOf": [ + { + "$ref": "#/definitions/AgentAction" + }, + { + "$ref": "#/definitions/AgentFinish" + } + ] + } + }, + "definitions": { + "AgentAction": { + "title": "AgentAction", + "description": "A full description of an action for an ActionAgent to execute.", + "type": "object", + "properties": { + "tool": { + "title": "Tool", + "type": "string" + }, + "tool_input": { + "title": "Tool Input", + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentAction", + "enum": [ + "AgentAction" + ], + "type": "string" + } + }, + "required": [ + "tool", + "tool_input", + "log" + ] + }, + "AgentFinish": { + "title": "AgentFinish", + "description": "The final return value of an ActionAgent.", + "type": "object", + "properties": { + "return_values": { + "title": "Return Values", + "type": "object" + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentFinish", + "enum": [ + "AgentFinish" + ], + "type": "string" + } + }, + "required": [ + "return_values", + "log" + ] + } + } + } + }, + { + "id": 4, + "type": "runnable", + "data": { + "id": [ + "langchain", + "prompts", + "prompt", + "PromptTemplate" + ], + "name": "PromptTemplate" + } + }, + { + "id": 5, + "type": "runnable", + "data": { + "id": [ + "langchain_community", + "llms", + "fake", + "FakeStreamingListLLM" + ], + "name": "FakeStreamingListLLM" + } + }, + { + "id": 6, + "type": "runnable", + "data": { + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "name": "agent_parser" + } + }, + { + "id": 7, + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnablePassthrough" + ], + "name": "RunnablePassthrough" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "name": "execute_tools" + } + }, + { + "id": "agent_should_continue", + "type": "unknown", + "data": "agent_should_continue" + } + ], + "edges": [ + { + "source": 4, + "target": 5 + }, + { + "source": 5, + "target": 6 + }, + { + "source": 2, + "target": 4 + }, + { + "source": 6, + "target": 3 + }, + { + "source": 2, + "target": 7 + }, + { + "source": 7, + "target": 3 + }, + { + "source": "__start__", + "target": 2 + }, + { + "source": "tools", + "target": 2 + }, + { + "source": 3, + "target": "agent_should_continue" + }, + { + "source": "agent_should_continue", + "target": "tools", + "data": "continue" + }, + { + "source": "agent_should_continue", + "target": "__end__", + "data": "exit" + } + ] + } + ''' +# --- +# name: test_conditional_graph[end_of_step].3 + ''' + +-----------+ + | __start__ | + +-----------+ + * + * + * + +------------------------------+ + | ParallelInput | + +------------------------------+ + ****** * ***** + ****** * ***** + *** * ****** + +----------------+ * *** + | PromptTemplate | * * + +----------------+ * * + * * * + * * * + * * * + +----------------------+ * * + | FakeStreamingListLLM | * * + +----------------------+ * * + * * * + * * * + * * * + +----------------------+ +-------------+ * + | Lambda(agent_parser) | | Passthrough | * + +----------------------+ +-------------+ * + ** ** * + *** *** * + ** ** * + +-------------------------------+ * + | ParallelOutput | * + +-------------------------------+ * + * * + * * + * * + +-----------------------+ * + | agent_should_continue | * + +-----------------------+******** * + * ******** * + * ******** * + * ******** * + +---------+ ***+-------+ + | __end__ | | tools | + +---------+ +-------+ + ''' +# --- +# name: test_conditional_graph_state[end_of_run] '{"title": "LangGraphInput", "$ref": "#/definitions/AgentState", "definitions": {"AgentAction": {"title": "AgentAction", "description": "A full description of an action for an ActionAgent to execute.", "type": "object", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"title": "Tool Input", "anyOf": [{"type": "string"}, {"type": "object"}]}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentAction", "enum": ["AgentAction"], "type": "string"}}, "required": ["tool", "tool_input", "log"]}, "AgentFinish": {"title": "AgentFinish", "description": "The final return value of an ActionAgent.", "type": "object", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentFinish", "enum": ["AgentFinish"], "type": "string"}}, "required": ["return_values", "log"]}, "AgentState": {"title": "AgentState", "type": "object", "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"title": "Agent Outcome", "anyOf": [{"$ref": "#/definitions/AgentAction"}, {"$ref": "#/definitions/AgentFinish"}]}, "intermediate_steps": {"title": "Intermediate Steps", "type": "array", "items": {"type": "array", "minItems": 2, "maxItems": 2, "items": [{"$ref": "#/definitions/AgentAction"}, {"type": "string"}]}}}}}}' # --- -# name: test_conditional_graph_state.1 +# name: test_conditional_graph_state[end_of_run].1 '{"title": "LangGraphOutput", "type": "object", "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"title": "Agent Outcome", "anyOf": [{"$ref": "#/definitions/AgentAction"}, {"$ref": "#/definitions/AgentFinish"}]}, "intermediate_steps": {"title": "Intermediate Steps", "type": "array", "items": {"type": "array", "minItems": 2, "maxItems": 2, "items": [{"$ref": "#/definitions/AgentAction"}, {"type": "string"}]}}}, "definitions": {"AgentAction": {"title": "AgentAction", "description": "A full description of an action for an ActionAgent to execute.", "type": "object", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"title": "Tool Input", "anyOf": [{"type": "string"}, {"type": "object"}]}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentAction", "enum": ["AgentAction"], "type": "string"}}, "required": ["tool", "tool_input", "log"]}, "AgentFinish": {"title": "AgentFinish", "description": "The final return value of an ActionAgent.", "type": "object", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentFinish", "enum": ["AgentFinish"], "type": "string"}}, "required": ["return_values", "log"]}}}' # --- -# name: test_conditional_graph_state.2 +# name: test_conditional_graph_state[end_of_run].2 ''' { "nodes": [ @@ -989,7 +1378,7 @@ } ''' # --- -# name: test_conditional_graph_state.3 +# name: test_conditional_graph_state[end_of_run].3 ''' +-----------+ | __start__ | @@ -1014,13 +1403,331 @@ +---------+ +-------+ ''' # --- -# name: test_message_graph +# name: test_conditional_graph_state[end_of_step] + '{"title": "LangGraphInput", "$ref": "#/definitions/AgentState", "definitions": {"AgentAction": {"title": "AgentAction", "description": "A full description of an action for an ActionAgent to execute.", "type": "object", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"title": "Tool Input", "anyOf": [{"type": "string"}, {"type": "object"}]}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentAction", "enum": ["AgentAction"], "type": "string"}}, "required": ["tool", "tool_input", "log"]}, "AgentFinish": {"title": "AgentFinish", "description": "The final return value of an ActionAgent.", "type": "object", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentFinish", "enum": ["AgentFinish"], "type": "string"}}, "required": ["return_values", "log"]}, "AgentState": {"title": "AgentState", "type": "object", "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"title": "Agent Outcome", "anyOf": [{"$ref": "#/definitions/AgentAction"}, {"$ref": "#/definitions/AgentFinish"}]}, "intermediate_steps": {"title": "Intermediate Steps", "type": "array", "items": {"type": "array", "minItems": 2, "maxItems": 2, "items": [{"$ref": "#/definitions/AgentAction"}, {"type": "string"}]}}}}}}' +# --- +# name: test_conditional_graph_state[end_of_step].1 + '{"title": "LangGraphOutput", "type": "object", "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"title": "Agent Outcome", "anyOf": [{"$ref": "#/definitions/AgentAction"}, {"$ref": "#/definitions/AgentFinish"}]}, "intermediate_steps": {"title": "Intermediate Steps", "type": "array", "items": {"type": "array", "minItems": 2, "maxItems": 2, "items": [{"$ref": "#/definitions/AgentAction"}, {"type": "string"}]}}}, "definitions": {"AgentAction": {"title": "AgentAction", "description": "A full description of an action for an ActionAgent to execute.", "type": "object", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"title": "Tool Input", "anyOf": [{"type": "string"}, {"type": "object"}]}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentAction", "enum": ["AgentAction"], "type": "string"}}, "required": ["tool", "tool_input", "log"]}, "AgentFinish": {"title": "AgentFinish", "description": "The final return value of an ActionAgent.", "type": "object", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"title": "Type", "default": "AgentFinish", "enum": ["AgentFinish"], "type": "string"}}, "required": ["return_values", "log"]}}}' +# --- +# name: test_conditional_graph_state[end_of_step].2 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": { + "title": "LangGraphInput", + "$ref": "#/definitions/AgentState", + "definitions": { + "AgentAction": { + "title": "AgentAction", + "description": "A full description of an action for an ActionAgent to execute.", + "type": "object", + "properties": { + "tool": { + "title": "Tool", + "type": "string" + }, + "tool_input": { + "title": "Tool Input", + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentAction", + "enum": [ + "AgentAction" + ], + "type": "string" + } + }, + "required": [ + "tool", + "tool_input", + "log" + ] + }, + "AgentFinish": { + "title": "AgentFinish", + "description": "The final return value of an ActionAgent.", + "type": "object", + "properties": { + "return_values": { + "title": "Return Values", + "type": "object" + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentFinish", + "enum": [ + "AgentFinish" + ], + "type": "string" + } + }, + "required": [ + "return_values", + "log" + ] + }, + "AgentState": { + "title": "AgentState", + "type": "object", + "properties": { + "input": { + "title": "Input", + "type": "string" + }, + "agent_outcome": { + "title": "Agent Outcome", + "anyOf": [ + { + "$ref": "#/definitions/AgentAction" + }, + { + "$ref": "#/definitions/AgentFinish" + } + ] + }, + "intermediate_steps": { + "title": "Intermediate Steps", + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "$ref": "#/definitions/AgentAction" + }, + { + "type": "string" + } + ] + } + } + } + } + } + } + }, + { + "id": "__end__", + "type": "schema", + "data": { + "title": "LangGraphOutput", + "type": "object", + "properties": { + "input": { + "title": "Input", + "type": "string" + }, + "agent_outcome": { + "title": "Agent Outcome", + "anyOf": [ + { + "$ref": "#/definitions/AgentAction" + }, + { + "$ref": "#/definitions/AgentFinish" + } + ] + }, + "intermediate_steps": { + "title": "Intermediate Steps", + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "$ref": "#/definitions/AgentAction" + }, + { + "type": "string" + } + ] + } + } + }, + "definitions": { + "AgentAction": { + "title": "AgentAction", + "description": "A full description of an action for an ActionAgent to execute.", + "type": "object", + "properties": { + "tool": { + "title": "Tool", + "type": "string" + }, + "tool_input": { + "title": "Tool Input", + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentAction", + "enum": [ + "AgentAction" + ], + "type": "string" + } + }, + "required": [ + "tool", + "tool_input", + "log" + ] + }, + "AgentFinish": { + "title": "AgentFinish", + "description": "The final return value of an ActionAgent.", + "type": "object", + "properties": { + "return_values": { + "title": "Return Values", + "type": "object" + }, + "log": { + "title": "Log", + "type": "string" + }, + "type": { + "title": "Type", + "default": "AgentFinish", + "enum": [ + "AgentFinish" + ], + "type": "string" + } + }, + "required": [ + "return_values", + "log" + ] + } + } + } + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "name": "RunnableSequence" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "name": "execute_tools" + } + }, + { + "id": "agent_should_continue", + "type": "unknown", + "data": "agent_should_continue" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "agent_should_continue" + }, + { + "source": "agent_should_continue", + "target": "tools", + "data": "continue" + }, + { + "source": "agent_should_continue", + "target": "__end__", + "data": "exit" + } + ] + } + ''' +# --- +# name: test_conditional_graph_state[end_of_step].3 + ''' + +-----------+ + | __start__ | + +-----------+ + * + * + * + +-------+ + | agent | + *+-------+* + ** *** + ** ** + ** ** + +-----------------------+ ** + | agent_should_continue | * + +-----------------------+ * + * **** * + * ***** * + * *** * + +---------+ +-------+ + | __end__ | | tools | + +---------+ +-------+ + ''' +# --- +# name: test_message_graph[end_of_run] '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- -# name: test_message_graph.1 +# name: test_message_graph[end_of_run].1 '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' # --- -# name: test_message_graph.2 +# name: test_message_graph[end_of_run].2 ''' { "nodes": [ @@ -1800,7 +2507,818 @@ } ''' # --- -# name: test_message_graph.3 +# name: test_message_graph[end_of_run].3 + ''' + +-----------+ + | __start__ | + +-----------+ + * + * + * + +-------+ + | agent | + *+-------+* + ** *** + ** ** + ** ** + +-----------------------+ ** + | agent_should_continue | * + +-----------------------+ * + * ***** * + * **** * + * *** * + +---------+ +--------+ + | __end__ | | action | + +---------+ +--------+ + ''' +# --- +# name: test_message_graph[end_of_step] + '{"title": "LangGraphInput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' +# --- +# name: test_message_graph[end_of_step].1 + '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"AIMessage": {"title": "AIMessage", "description": "Message from an AI.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior, usually passed in as the first of a sequence\\nof input messages.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a function back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' +# --- +# name: test_message_graph[end_of_step].2 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": { + "title": "LangGraphInput", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AIMessage" + }, + { + "$ref": "#/definitions/HumanMessage" + }, + { + "$ref": "#/definitions/ChatMessage" + }, + { + "$ref": "#/definitions/SystemMessage" + }, + { + "$ref": "#/definitions/FunctionMessage" + }, + { + "$ref": "#/definitions/ToolMessage" + } + ] + }, + "definitions": { + "AIMessage": { + "title": "AIMessage", + "description": "Message from an AI.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "ai", + "enum": [ + "ai" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "example": { + "title": "Example", + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ] + }, + "HumanMessage": { + "title": "HumanMessage", + "description": "Message from a human.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "human", + "enum": [ + "human" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "example": { + "title": "Example", + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ] + }, + "ChatMessage": { + "title": "ChatMessage", + "description": "Message that can be assigned an arbitrary speaker (i.e. role).", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "chat", + "enum": [ + "chat" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "role": { + "title": "Role", + "type": "string" + } + }, + "required": [ + "content", + "role" + ] + }, + "SystemMessage": { + "title": "SystemMessage", + "description": "Message for priming AI behavior, usually passed in as the first of a sequence\nof input messages.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "system", + "enum": [ + "system" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + } + }, + "required": [ + "content" + ] + }, + "FunctionMessage": { + "title": "FunctionMessage", + "description": "Message for passing the result of executing a function back to a model.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "function", + "enum": [ + "function" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + } + }, + "required": [ + "content", + "name" + ] + }, + "ToolMessage": { + "title": "ToolMessage", + "description": "Message for passing the result of executing a tool back to a model.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "tool", + "enum": [ + "tool" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "tool_call_id": { + "title": "Tool Call Id", + "type": "string" + } + }, + "required": [ + "content", + "tool_call_id" + ] + } + } + } + }, + { + "id": "__end__", + "type": "schema", + "data": { + "title": "LangGraphOutput", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/AIMessage" + }, + { + "$ref": "#/definitions/HumanMessage" + }, + { + "$ref": "#/definitions/ChatMessage" + }, + { + "$ref": "#/definitions/SystemMessage" + }, + { + "$ref": "#/definitions/FunctionMessage" + }, + { + "$ref": "#/definitions/ToolMessage" + } + ] + }, + "definitions": { + "AIMessage": { + "title": "AIMessage", + "description": "Message from an AI.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "ai", + "enum": [ + "ai" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "example": { + "title": "Example", + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ] + }, + "HumanMessage": { + "title": "HumanMessage", + "description": "Message from a human.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "human", + "enum": [ + "human" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "example": { + "title": "Example", + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ] + }, + "ChatMessage": { + "title": "ChatMessage", + "description": "Message that can be assigned an arbitrary speaker (i.e. role).", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "chat", + "enum": [ + "chat" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "role": { + "title": "Role", + "type": "string" + } + }, + "required": [ + "content", + "role" + ] + }, + "SystemMessage": { + "title": "SystemMessage", + "description": "Message for priming AI behavior, usually passed in as the first of a sequence\nof input messages.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "system", + "enum": [ + "system" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + } + }, + "required": [ + "content" + ] + }, + "FunctionMessage": { + "title": "FunctionMessage", + "description": "Message for passing the result of executing a function back to a model.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "function", + "enum": [ + "function" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + } + }, + "required": [ + "content", + "name" + ] + }, + "ToolMessage": { + "title": "ToolMessage", + "description": "Message for passing the result of executing a tool back to a model.", + "type": "object", + "properties": { + "content": { + "title": "Content", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + ] + }, + "additional_kwargs": { + "title": "Additional Kwargs", + "type": "object" + }, + "type": { + "title": "Type", + "default": "tool", + "enum": [ + "tool" + ], + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "tool_call_id": { + "title": "Tool Call Id", + "type": "string" + } + }, + "required": [ + "content", + "tool_call_id" + ] + } + } + } + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "tests", + "test_pregel", + "FakeFuntionChatModel" + ], + "name": "FakeFuntionChatModel" + } + }, + { + "id": "action", + "type": "runnable", + "data": { + "id": [ + "langchain_core", + "runnables", + "base", + "RunnableLambda" + ], + "name": "call_tool" + } + }, + { + "id": "agent_should_continue", + "type": "unknown", + "data": "agent_should_continue" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "action", + "target": "agent" + }, + { + "source": "agent", + "target": "agent_should_continue" + }, + { + "source": "agent_should_continue", + "target": "action", + "data": "continue" + }, + { + "source": "agent_should_continue", + "target": "__end__", + "data": "end" + } + ] + } + ''' +# --- +# name: test_message_graph[end_of_step].3 ''' +-----------+ | __start__ | diff --git a/tests/test_pregel.py b/tests/test_pregel.py index f9752d5a4..90197f1dd 100644 --- a/tests/test_pregel.py +++ b/tests/test_pregel.py @@ -16,6 +16,7 @@ from langgraph.channels.binop import BinaryOperatorAggregate from langgraph.channels.context import Context from langgraph.channels.last_value import LastValue from langgraph.channels.topic import Topic +from langgraph.checkpoint.base import CheckpointAt from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.graph import END, Graph from langgraph.graph.message import MessageGraph @@ -188,12 +189,17 @@ def test_invoke_two_processes_in_out(mocker: MockerFixture) -> None: assert step == 2 -def test_invoke_two_processes_in_out_interrupt(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_invoke_two_processes_in_out_interrupt( + mocker: MockerFixture, checkpoint_at: CheckpointAt +) -> None: add_one = mocker.Mock(side_effect=lambda x: x + 1) one = Channel.subscribe_to("input") | add_one | Channel.write_to("inbox") two = Channel.subscribe_to("inbox") | add_one | Channel.write_to("output") - memory = MemorySaverAssertImmutable() + memory = MemorySaverAssertImmutable(at=checkpoint_at) app = Pregel( nodes={"one": one, "two": two}, checkpointer=memory, @@ -391,7 +397,10 @@ def test_invoke_two_processes_two_in_two_out_valid(mocker: MockerFixture) -> Non assert app.invoke(2) == [3, 3] -def test_invoke_checkpoint(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_invoke_checkpoint(mocker: MockerFixture, checkpoint_at: CheckpointAt) -> None: add_one = mocker.Mock(side_effect=lambda x: x["total"] + x["input"]) def raise_if_above_10(input: int) -> int: @@ -406,7 +415,7 @@ def test_invoke_checkpoint(mocker: MockerFixture) -> None: | raise_if_above_10 ) - memory = MemorySaverAssertImmutable() + memory = MemorySaverAssertImmutable(at=checkpoint_at) app = Pregel( nodes={"one": one}, @@ -441,7 +450,12 @@ def test_invoke_checkpoint(mocker: MockerFixture) -> None: assert checkpoint["channel_values"].get("total") == 5 -def test_invoke_checkpoint_sqlite(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_invoke_checkpoint_sqlite( + mocker: MockerFixture, checkpoint_at: CheckpointAt +) -> None: add_one = mocker.Mock(side_effect=lambda x: x["total"] + x["input"]) def raise_if_above_10(input: int) -> int: @@ -457,6 +471,7 @@ def test_invoke_checkpoint_sqlite(mocker: MockerFixture) -> None: ) with SqliteSaver.from_conn_string(":memory:") as memory: + memory.at = checkpoint_at app = Pregel( nodes={"one": one}, channels={"total": BinaryOperatorAggregate(int, operator.add)}, @@ -683,7 +698,12 @@ def test_channel_enter_exit_timing(mocker: MockerFixture) -> None: assert cleanup.call_count == 1, "Expected cleanup to be called once" -def test_conditional_graph(snapshot: SnapshotAssertion) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_conditional_graph( + snapshot: SnapshotAssertion, checkpoint_at: CheckpointAt +) -> None: from copy import deepcopy from langchain.llms.fake import FakeStreamingListLLM @@ -885,7 +905,8 @@ def test_conditional_graph(snapshot: SnapshotAssertion) -> None: # test state get/update methods with interrupt_after app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -1034,7 +1055,8 @@ def test_conditional_graph(snapshot: SnapshotAssertion) -> None: # test state get/update methods with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["tools"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["tools"], ) config = {"configurable": {"thread_id": "2"}} llm.i = 0 # reset the llm @@ -1178,7 +1200,8 @@ def test_conditional_graph(snapshot: SnapshotAssertion) -> None: # test re-invoke to continue with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["tools"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["tools"], ) config = {"configurable": {"thread_id": "2"}} llm.i = 0 # reset the llm @@ -1344,7 +1367,12 @@ def test_conditional_entrypoint_graph(snapshot: SnapshotAssertion) -> None: ] -def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_conditional_graph_state( + snapshot: SnapshotAssertion, checkpoint_at: CheckpointAt +) -> None: from langchain.llms.fake import FakeStreamingListLLM from langchain_community.tools import tool from langchain_core.agents import AgentAction, AgentFinish @@ -1511,7 +1539,8 @@ def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None: # test state get/update methods with interrupt_after app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -1625,7 +1654,7 @@ def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None: # test state get/update methods with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), interrupt_before=["tools"], debug=True, ) @@ -2104,8 +2133,13 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None: ] +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) def test_message_graph( - snapshot: SnapshotAssertion, deterministic_uuids: MockerFixture + snapshot: SnapshotAssertion, + checkpoint_at: CheckpointAt, + deterministic_uuids: MockerFixture, ) -> None: from copy import deepcopy @@ -2317,7 +2351,8 @@ def test_message_graph( ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -2463,7 +2498,8 @@ def test_message_graph( ) app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["action"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["action"], ) config = {"configurable": {"thread_id": "2"}} model.i = 0 # reset the llm @@ -2676,7 +2712,10 @@ def test_in_one_fan_out_out_one_graph_state() -> None: ] -def test_in_one_fan_out_state_graph_waiting_edge() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_in_one_fan_out_state_graph_waiting_edge(checkpoint_at: CheckpointAt) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2776,7 +2815,8 @@ def test_in_one_fan_out_state_graph_waiting_edge() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}} @@ -2796,7 +2836,12 @@ def test_in_one_fan_out_state_graph_waiting_edge() -> None: ] -def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_in_one_fan_out_state_graph_waiting_edge_via_branch( + checkpoint_at: CheckpointAt, +) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2898,7 +2943,8 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}} @@ -2918,7 +2964,12 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: ] -def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +def test_in_one_fan_out_state_graph_waiting_edge_plus_regular( + checkpoint_at: CheckpointAt, +) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2987,7 +3038,8 @@ def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}} diff --git a/tests/test_pregel_async.py b/tests/test_pregel_async.py index 0ec6df93f..13906bc82 100644 --- a/tests/test_pregel_async.py +++ b/tests/test_pregel_async.py @@ -23,6 +23,7 @@ from langgraph.channels.context import Context from langgraph.channels.last_value import LastValue from langgraph.channels.topic import Topic from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver +from langgraph.checkpoint.base import CheckpointAt from langgraph.graph import END, Graph, StateGraph from langgraph.graph.message import MessageGraph from langgraph.prebuilt.chat_agent_executor import ( @@ -196,12 +197,17 @@ async def test_invoke_two_processes_in_out(mocker: MockerFixture) -> None: assert step == 2 -async def test_invoke_two_processes_in_out_interrupt(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_invoke_two_processes_in_out_interrupt( + mocker: MockerFixture, checkpoint_at: CheckpointAt +) -> None: add_one = mocker.Mock(side_effect=lambda x: x + 1) one = Channel.subscribe_to("input") | add_one | Channel.write_to("inbox") two = Channel.subscribe_to("inbox") | add_one | Channel.write_to("output") - memory = MemorySaverAssertImmutable() + memory = MemorySaverAssertImmutable(at=checkpoint_at) app = Pregel( nodes={"one": one, "two": two}, checkpointer=memory, @@ -408,7 +414,12 @@ async def test_invoke_two_processes_two_in_two_out_valid(mocker: MockerFixture) assert await app.ainvoke(2) == [3, 3] -async def test_invoke_checkpoint(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_invoke_checkpoint( + mocker: MockerFixture, checkpoint_at: CheckpointAt +) -> None: add_one = mocker.Mock(side_effect=lambda x: x["total"] + x["input"]) def raise_if_above_10(input: int) -> int: @@ -423,7 +434,7 @@ async def test_invoke_checkpoint(mocker: MockerFixture) -> None: | raise_if_above_10 ) - memory = MemorySaverAssertImmutable() + memory = MemorySaverAssertImmutable(at=checkpoint_at) app = Pregel( nodes={"one": one}, @@ -458,7 +469,12 @@ async def test_invoke_checkpoint(mocker: MockerFixture) -> None: assert checkpoint["channel_values"].get("total") == 5 -async def test_invoke_checkpoint_aiosqlite(mocker: MockerFixture) -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_invoke_checkpoint_aiosqlite( + mocker: MockerFixture, checkpoint_at: CheckpointAt +) -> None: add_one = mocker.Mock(side_effect=lambda x: x["total"] + x["input"]) def raise_if_above_10(input: int) -> int: @@ -474,6 +490,7 @@ async def test_invoke_checkpoint_aiosqlite(mocker: MockerFixture) -> None: ) async with AsyncSqliteSaver.from_conn_string(":memory:") as memory: + memory.at = checkpoint_at app = Pregel( nodes={"one": one}, channels={"total": BinaryOperatorAggregate(int, operator.add)}, @@ -726,7 +743,10 @@ async def test_channel_enter_exit_timing(mocker: MockerFixture) -> None: assert cleanup_async.call_count == 1, "Expected cleanup to be called once" -async def test_conditional_graph() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_conditional_graph(checkpoint_at: CheckpointAt) -> None: from copy import deepcopy from langchain.llms.fake import FakeStreamingListLLM @@ -931,7 +951,8 @@ async def test_conditional_graph() -> None: # test state get/update methods with interrupt_after app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -1077,7 +1098,8 @@ async def test_conditional_graph() -> None: # test state get/update methods with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["tools"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["tools"], ) config = {"configurable": {"thread_id": "2"}} llm.i = 0 @@ -1224,7 +1246,8 @@ async def test_conditional_graph() -> None: # test re-invoke to continue with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["tools"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["tools"], ) config = {"configurable": {"thread_id": "2"}} llm.i = 0 # reset the llm @@ -1349,7 +1372,10 @@ async def test_conditional_graph() -> None: ] -async def test_conditional_graph_state() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_conditional_graph_state(checkpoint_at: CheckpointAt) -> None: from langchain.llms.fake import FakeStreamingListLLM from langchain_community.tools import tool from langchain_core.agents import AgentAction, AgentFinish @@ -1511,7 +1537,8 @@ async def test_conditional_graph_state() -> None: # test state get/update methods with interrupt_after app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -1630,7 +1657,8 @@ async def test_conditional_graph_state() -> None: # test state get/update methods with interrupt_before app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_before=["tools"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_before=["tools"], ) config = {"configurable": {"thread_id": "2"}} llm.i = 0 # reset the llm @@ -2137,7 +2165,10 @@ async def test_prebuilt_chat() -> None: ] -async def test_message_graph() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_message_graph(checkpoint_at: CheckpointAt) -> None: from langchain.chat_models.fake import FakeMessagesListChatModel from langchain_community.tools import tool from langchain_core.agents import AgentAction @@ -2306,7 +2337,8 @@ async def test_message_graph() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["agent"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["agent"], ) config = {"configurable": {"thread_id": "1"}} @@ -2511,7 +2543,12 @@ async def test_in_one_fan_out_out_one_graph_state() -> None: ] -async def test_in_one_fan_out_state_graph_waiting_edge() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_in_one_fan_out_state_graph_waiting_edge( + checkpoint_at: CheckpointAt, +) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2575,7 +2612,8 @@ async def test_in_one_fan_out_state_graph_waiting_edge() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}} @@ -2598,7 +2636,12 @@ async def test_in_one_fan_out_state_graph_waiting_edge() -> None: ] -async def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_in_one_fan_out_state_graph_waiting_edge_via_branch( + checkpoint_at: CheckpointAt, +) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2700,7 +2743,8 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}} @@ -2723,7 +2767,12 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None: ] -async def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None: +@pytest.mark.parametrize( + "checkpoint_at", [CheckpointAt.END_OF_RUN, CheckpointAt.END_OF_STEP] +) +async def test_in_one_fan_out_state_graph_waiting_edge_plus_regular( + checkpoint_at: CheckpointAt, +) -> None: def sorted_add( x: list[str], y: Union[list[str], list[tuple[str, str]]] ) -> list[str]: @@ -2792,7 +2841,8 @@ async def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None: ] app_w_interrupt = workflow.compile( - checkpointer=MemorySaverAssertImmutable(), interrupt_after=["retriever_one"] + checkpointer=MemorySaverAssertImmutable(at=checkpoint_at), + interrupt_after=["retriever_one"], ) config = {"configurable": {"thread_id": "1"}}