Compare commits

..
Author SHA1 Message Date
Sydney Runkle ed648f87bf update pydantic in lockfile 2025-05-12 07:49:30 -04:00
98 changed files with 3340 additions and 2087 deletions
+1 -2
View File
@@ -12,7 +12,6 @@
[![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph)
[![Open Issues](https://img.shields.io/github/issues-raw/langchain-ai/langgraph)](https://github.com/langchain-ai/langgraph/issues)
[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://langchain-ai.github.io/langgraph/)
[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/langchain-ai/langgraph)](https://gitmcp.io/langchain-ai/langgraph)
Trusted by companies shaping the future of agents including Klarna, Replit, Elastic, and more LangGraph is a powerful low-level orchestration framework for building, managing, and deploying long-running, stateful agents.
@@ -81,4 +80,4 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
## Acknowledgements
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
+1 -5
View File
@@ -67,8 +67,6 @@ REDIRECT_MAP = {
"cloud/concepts/api.md": "concepts/langgraph_server.md",
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
"cloud/how-tos/human_in_the_loop_edit_state.md": "cloud/how-tos/add-human-in-the-loop.md",
"cloud/how-tos/human_in_the_loop_user_input.md": "cloud/how-tos/add-human-in-the-loop.md",
# cloud streaming redirects
"cloud/how-tos/stream_values.md": "cloud/how-tos/streaming.md#stream-graph-state",
"cloud/how-tos/stream_updates.md": "cloud/how-tos/streaming.md#stream-graph-state",
@@ -96,9 +94,7 @@ REDIRECT_MAP = {
"tutorials/introduction.ipynb": "concepts/why-langgraph.md",
# deployment redirects
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
# assistant redirects
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md"
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md"
}
+5 -11
View File
@@ -5,7 +5,6 @@ import os
import json
import click
import nbformat
import re
logger = logging.getLogger(__name__)
NOTEBOOK_DIRS = ("docs/how-tos","docs/tutorials")
@@ -89,10 +88,12 @@ def has_blocklisted_command(code: str, metadata: dict) -> bool:
return True
return False
MERMAID_PATTERN = re.compile(r'display\(Image\((\w+)\.get_graph\(\)\.draw_mermaid_png\(\)\)\)')
def remove_mermaid(code: str) -> str:
return MERMAID_PATTERN.sub('print()', code)
return code.replace(
"display(Image(graph.get_graph().draw_mermaid_png()))",
# replace with a dummy statement
"print()"
)
def add_vcr_to_notebook(
@@ -107,8 +108,6 @@ def add_vcr_to_notebook(
continue
lines = cell.source.splitlines()
# remove the special tag for hidden cells
lines = [line for line in lines if not line.strip().startswith("# hide-cell")]
# skip if empty cell
if not lines:
continue
@@ -196,11 +195,6 @@ def remove_mermaid_from_notebook(notebook: nbformat.NotebookNode) -> nbformat.No
continue
cell.source = remove_mermaid(cell.source)
# skip the cell entirely if it contains PYPPETEER
if "PYPPETEER" in cell.source:
cell.source = ""
return notebook
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
eNqdVW1wU1UabmEFHGVgRBwFZoiZbp3B3uTefKchs7RJbUuTJk0aS1E3ntx7knub++X9SHOL9aOorCuLcx0Bv6ZTaWhoCQVsRVw+FnRcVt0iA/7Y7q44O86qCzviOs6wjgx48lFpB355fiT33vc973nO8zzvOQP5DJRkRuCrCwyvQAmQCnqR9YG8BB9Toaw8M8JBhRaoXDgU7RxWJWZ6Da0oolxvNgORMQki5AFjIgXOnCHMJA0UM3oWWVgqk0sIlDb97CYjB2UZpKBsrH9ok5EU0Eq8Yqw3RqAsCjxlYHhDqwJYBvAmY51REliIgrImK5Az9tfNnkEz1xNUGUrG/kfqjJxAQRZ9SIkKZjMRGMfwxTRZkSDgjPVJwMqwP09DQKG9vpijBVnRx+ei3w9IEqLpkCcFiuFT+kSqjxHrDBRMskCBdYY+WaHGEA4elhjSx9IQihjCnIEj5bn6ASCKLEOCYtzcIwt8oYIbUzQR3hgeK24AQ7Twij4ZQlAaWs1hDZHNGwiT027CD2QxWQEMzyL2MBYgVCNiKX5kdkAEZBoVwSpC6iPlyeOzcwRZ3x0EZCg6pySQSFrfDSTOYZuY/V1SeYXhoJ73hW9crhK8vpzVRBAm98E5hWWNJ/XdJeLfmTMZKpKGkQKqob+Jj8/ww0I+pdD6MGG17pFKnpDh5hE0TVHlgRxSBP71L/mKh3aF2makPF91d86P1NGPdapIIsJqCALNYMEtdgNO1Fsc9YTF0BzsLPgqy3TeVIaDnRLg5SSSomlG/DxJq3waUmO+mwp+rCg42k0RPvIiBrOiIEOsgkovbMAi5ebBWv0TZY9hgpQCPNNXWlYfLYqJmoXhJythURKKJdHiGCfrwzaHc7wSmeF5DO0Lxwgcw4ljWeTTjKBhqlj2OIaMlGFIiJVkG7Y58T9mMQkxwzIcg+gt/VYaGjnBiqNx+MYMRUhD1PujhB0vj+OzcyTIIcBFhNcrWdxoHL151s/VbO7SmIsJyQxnYRq2cPLhG+OVGrtwuZCdScYYSp+uQS/xpCWRpPBEMknaYNJGuUmQxF1WEpBOp4NwgMS7iB2GRFWKAouCpCCeSHSEKZo+XceBbLH3vFbCbnWgvXrQKUSyKgWjasIvFDchewyiBFkBUPvJJIbK0hAre1LP+7vbG4KtvrEoAukThDQDX/p79d3xOJmMJzivyxlIt1gkuSsjKfHHmFjEYetu6BL7iFggzYuhcLSFTHUSrYkOfwojnEgut9vutGCECTcR6Pxq64qxqbTUYyU6/f6WYEtDlHKGm/0tCda+EfRGKNN6SyAcCIiNYXd0fXscpuIdgq+5vc8dkNJUs7OJDHDxHq1PbqcbYyGptylhstjYNhMObSQe6tmQoF0dNt4aCnQEu9EWgUJ7zR4DMjGDSPdWWglDrYSVG8k+00geA1Uixmuae3h6DC3oSgjxrOYxRIsMQ/QPOBhlFOhtF3g4/TIiRs0wlDdtaaUjXfZGXom6ep0N7bFGNd7b2EbEOwMC0+fLapSwXo1beJ8Ym8WMy+3E8Ao5DtzmKpnzOvRfiOrQBmz2yYCFxPLdl+cFmWeSyZEo6ioo6WMkK6gUugckOOJ7AIs0dOuTbiuEdop04kk77iRBAmvqihyYqfbzOZIrXiJ5wCLjZUh9grZ6jfU2m9XoMXDA63LYcLx0Qz49UjQqn/qgenz1C4uqSmP+1sjJbefwpce+un/rJ1Njo2+tPHvb2Yxb/NeJ6l/Ry92na+4R1/55cKhp71WPdNbxn3l37Fz15dQm7ZI2uOzeGodkzn2z/OLJV7+/eu6rq59d3LKQu23lhdXxwfNP/v9vx/ep2pOH9/671rLn6oLXPimsDG62nmjJPPf2wg+WxiaM5kNQe+i/tY8OLQjk1G3nrtS2LZx85ZtDR/6hvJ7vfmLX5WdWDPzzhf3PL21cG/qc/tTxbc3lxBF3aMGiBtVweeev783dt+y+yLNPPb0dvtu9LrjjZPv5JXu+3u46U/Vex6KaO7cV1g4Pjb/vTO/Ykrl9zcZ3hon713W3FRLff3jpi1vdv1214sIb+fqjQx/dhT13/LuhedNf1yIDvHnK/7tTo8//b80byuCCU8vP9b7yxM7PpiRJe2p5tqfVctDy8eO9tY+MxhYfaFz2YHD9PmXJ1HsP93SRmyfnvRT78jdTV4KbTv/h0I+rf7jlzIpVvqh7yeILF098/NqW7OC1jUeUtffsqL4S+jRMDLx46cy6vQ9YP996+5/mvR9+m/Z8e/qLx1379v7+LdNHK74rbP+Q+GFxVdW1a/Ormo729zfPr6r6CVs9pnc=
@@ -1 +0,0 @@
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
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
@@ -1 +1 @@
eNqNVX9sE3UU75ygIxoDIj806tkMZqTf7q7XbmtBFLaBQMoWWoENcXx792172/XuvPu20OE0GwwRouFAiCHTCOtarBO2jIQM0JAA4efCgoZY/sBEgyEhkYAuoBjn97p2P9wG3F/tvfd57/Pe5713TYkIUjVBlnLaBQkjFXKY/NH0poSK3gsjDW+OhxAOynysssLjbQ2rQmpWEGNFcxUWQkWwQgkHVVkROCsnhwojTGEIaRoMIC3mk/loSt5oDsENNViuQ5JmdjG0zW4xZ13MrjUbzaosIrPLHNaQaraYOZmQkDB5sT4IcYFGhaKUBEPoDXPDWgKUeSQSGyfCMI8ACxxAkyUJYSBCTLiaGxJBBHlS0DXT5FhQ1rDeOYrkIchxSMEASZzMC1JA7wrUC4qF4pHfiGKh6jXMJzkjbroXerIOIQVAUYigrg1Aw1CQRFIBwEIIyWGsf728wluzeMnK8uXxgdB6B1QUUeCgAS+sJRTbM3UBHFXQaHPSqB6QlkhYP7IgS7awMkoaL1G01e600h3DU4uQ8I4rafux4QYFcnUkDsiIqscHwAeH+8ia3uaGXIVnREiockG9DaqhIvuIKtWwZBSqJ0orR6fLGIfSsVaGsTo7RwTWohKnt/mhqKHOQSUGIUkbbWMBXQRo5siI0AirUcDJJIO+jz6YbaCIpAAO6q0MwxxQkaaQWUWb4gSGw1pTjCiKLp5NZAZsf8WyoXmYGSsj6urfecNEYoal3DBKkdQOiqZdDsZlZ6jFbm97aSaNd0ydOr0qlDQ/0ao8OzwJLhiW6hCfLB1zYFKzhypWSX5RCAkYCJISJrOQXgqQfqXH7bTxpF57qL+KQqRHRu4sZvYjYDSE9cNGvYB2AIb1Zqqmq1MFY6HJXI+i2FaSzjbn4f5DFDOYgkfBjEORqU6Zx4Jn7lOWXcw+bisGPYd4xVin0/mQuOMxYp3VKWos5P8EHSg9/wGew6Uc8KYe6D2uiMkMaSDw+nHyu4ZmmNJKO7d66cpy/9sb6m2CV3urCpaHuzWsChwJaQyyIqsYaIgjRx1H9ZSFXGrjCL3OMg62iLCZSwkSJ5JL6wn7ymSDqjaXUlQkypDvHiIqqwEoCfXpNTEIpPJZZxHr4FkfQD4/D+zOkmLgdNoY4LPZSnh7CVNs54taIwLUk4yVoQKyHBDRIc4POMgFERhYZj1RVrV8gXtJaftqsEL2yUQQLyTCSbKE4h6kkvuhJzlRDvPkbKsoXroIrFhQpR92sgjZfDZk5/yM3V9cDMpXrejIrvXg2saMm5/+RDXGjZZIgdM581/e/qQp/eTyO5i6k28+1dy/rffy6Tz3wi31t3qZqa7Y1NaW3fe2gu6CLzzP3vl5+w8Tn3ctOX733p+9M7lPZkUiB16afrPPXxI60yMde+f2733g1NpbL067wV3ybOu5cWL1usuRKWcte3eVTPtJ6AF7ZlSfd1xa2dV30bu54crOLrf+1S+WT6/f/2sL/ndvy81Xb0+o3f3ER6038hb15G12fXmSbZ7YdP7EubOtk1r2F/cyn3vUTQ2+ukl08wv79zU2LlzHKhN2Tv77VOmP4vr8XHevZnuu9pXz73ruak2RvQ21OVVX89hvr1T6tn4whz5zYXHjb1dXfXzO21G25+idlsc/fPrXSxenzFh2LrRm3ffzO+dxn83feL2eZyzTLzyW27zlmx33qvcdfWbetX9Oefvv2u/XoD7c/X6uydTfn2v6Y9nSXddyTKb/AC82UmY=
eNqNVGtMHFUUXoQ0FavRGAt/bCcThCrMMjMsWxarlAIlpVIorKW8bG5nLuzAvJjH8rKmYKkxlbRDUxoTCElZdulKYRGMTaiExpKYtFFrDbAxaeIfpRok0USNreDdZReovDrJJDP3nO+c73znnNvqcUJF5SQxYoATNagARkM/qtHqUWCdDlXtjFuAmkNiXYUFxfZeXeH8rzg0TVbTk5OBzJmBqDkUSeYYMyMJyU4qWYCqCqqh6jopsY3++mZcAA0nNKkWiiqejlEkbUnC8LATOilvxhWJh+gL11Wo4MjKSIiJqAWO6h1AS1AxoRETgQAz8FOVAbTEQj5gZXigs5BIIVIJVRJFqBE80BBn/JTHAQGLCjvvckiqZgyvoToEGAbKGgFFRmI5sdq4Vt3EyUkYC6sCMbxMIF5QC8NbC6FMAJ5zQvcSyvABWeY5BgTsyTUo90CIM6E1ynCt2RsojUAVi5rxeWaYR3JhI1JWxEhzisVM+xoIVQOcyCNtUB2IklsO2sdWG2TA1KI4RKhrhnsJPLjaR1KNvnzAFBQ/FhIojMPoA4pgtYysPld0UeMEaHiyCtemCxlX0qWYKcpsG34ssNooMkZfFeBVOLws8jLES5N0CkFaCZIaDKvEQ7Facxi9FGXrV6Aqo4mD77tRSE1XW12oI/DOV57QkFwpOBzu5n1TjCsbdcf4ogSySRhNY3lAxFD8VIym0klbOkVjufn2gaxQGvu6zRi2K0BUq1BDcsLN9zAOXayFrDdr3bb78ZWyFJSf5wROI0ILgpoV+DVcFpIk/fGbeipQQKoFMrpSbDbbFnGRMlAzRgP1IfEImraHqqTK1s/DibKORjC4bCFW7gArxOu1Lf1XuIUx8U+A2YAhXeZPWA8t6doain1pwWyJW/uvUAxhEp4EszFFbD34/+RbShS3iedq4Za8sU29N+TjDXWe4FjjBvo+QVJHGw5AzuI8mi2WHM+rLSnJrSm0C1KvkwOGlzJTWLUkVfNwKOsgkQUYBySKgytkeLJLj2TmH8oaOE4USSclNEt2gGZOlEToLoYKWk3Dy/CSzqLLToFuBC/KLDVGbaSVTrOSaawtjdzL0hYip6TIF16m5WVxBW7K4PXeglZWQUeTEW/vPrfdFHwi0bu4yF7IPxyZuePsYt6dyduDo+VvXI95tb/w23Otu7I766z6B1/H6x1XO79bNI+V9TdE75vPPeCMLRsYnv6riu7O2Nc01nP3J+fDR1pGzPi/12+Mf/mUMOa+meP9rds3EVdnOpLab7Mm+S534lOzvbeJSyO/X7GD+MRPDulvdttf2jnb1j7je8//cl77+Nm/02oqTv/RWflRuaOt4NljST2+S12nKwsu2Dp+MU3sOTj7IErsmp94RtpzdQTb0XMzffbp7yvoNojPRRe2TEq1P1ujWi53/NNzpvkiz8zd3TZXgW93bvumg3n909hjUfHPz3SM+u81dBVVvPPjwmf7U0v3ftgVzbTfEhgbtRCdt/vjmRenupgk8vziQH1sQ/c1qvSFxKl73v3z7+a2F1h2zTxKfeu5Jnx6YTruB8o29OdDKahdpOnXnbfuP4gwmf4DBVoOEg==
@@ -1 +1 @@
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
@@ -1 +0,0 @@
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
@@ -1 +0,0 @@
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
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
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
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
@@ -1 +0,0 @@
eNqNVX1sE2UYLxkYQlyiEf5QEzgbBon0be96t3YdYFy2AYPsg7WCfDnf3r1tj13vPe59b6GDiU6UEIx6MQrGj8DWtVgX2AIJjA9jIhBiQCVgTBNi0BiNGogxkohR53tduw+3AfdXe+/z+z2/3/M873Pd2Q5kEhXrM/pVnSITypT9IXZ31kTbLETo7kwS0QRW0i3N4UivZar5igSlBqn2+aCheqFOEyY2VNkr46SvQ/AlESEwjkg6ipVU/q0d7iTc3kZxO9KJu1rg/ZLHXQpxV2/a4TaxhtzVbosg0+1xy5iJ0Cl7sd5UKeIgRxLYpNxWRsDBKLYoJydUucDWtYVRYQVpLFrWoKUgIIIgIFjXEQUapEw9o6QoaTBf1DJZHt7Ld2UTCCrM9Leuh9MJTKg9OMnIUSjLyKAA6TJWVD1uH4t3qoaHU1DM4fVwnYQqOdnJVKiXnWtHyABQUzvQse2AUKjqGnMJqJpETLT9UVNzpG1lw7r6pswItT0ADUNTZejAfVuZ6P6id0BTBpp8nHMqBFjZdGqfqCmJ9bWkWHN0jvdKIS8/MD61BpnujFE4Pz3+wIByO+MBxcbbmRHwkfExmNh9jVBuDk+ghKacsPugmQxIE1yalu4YtbO1LZPTFQ/H0oleQfCGBicQk5Qu230xqBE0ONqJUUjOz/tFwAcAL5yYQI2omQIyZhnsQ/yRUgE1pMdpwu4VpOBhExGDzTN6OcNg1CLdadZRdOlitjiEPc1rxubh0XQd6659thHrHk7wc40wxbHUlZwgVYtSNV/FrWyM9NcW00Sm7NNgxIQ6ibFe1ZeGJysnLL0dKbnaKQcmv2jMMZtTpKlJlQJVNyw2C4WLAwqv7Iyfd578k/eMN1GS1cjJXcIsug8MQdQ+7vgFfCUQ/JGS6435xVOh2VxPkthXVci25N7xYxKLmMX3g5leonsqeHGHldSlpWlLMRo5pisthkKhe/BOqyi4Mc9NhfxfQ0esL7xL5PhWjkRzd42etkK5omigKvYZ9ruNF4TaFhGHhTXtJCAlOyuDIX8y1h4eItRUZUbpDLLBti8gSGaLn6bsvIdtc2cJLReFSjHA1CzlVF3W2O4NW9E67EglSznDRBqGytCYUGzGoa52Fq6JIyC/UAwFxEpFjAIUjSlAClUFQSjkF0DU769SpCohKCmB3g4V2jnBK3BxjOMaOirHgAzlBAIjl9nO1m1oqmlsqO1/FrTiKGYNiUDWOB3rKBNGJtsfdk7WsKWwtW2iTO0K0FqzwT4eEhGE/pAUUIJRKcr2Sv361oHStR69tmln5xc+Yy9lnJLo8fMz5i3YN9tVeMqUN2rWnOPn7h5evfOnuaeesS/X/3ajYdbaudv6zv2QvxBrO5XyfWX9vGv/ls0ty8ovBUV+6ez5D/YPtf96/vLVz06eOT184PrNgYZv/F3L37k1S7rYca5rzrvJPQefyJ4MesjzgwsPwg+WreBv9ID5H1/9pef7ut2tlRZ5LrivImHsndnw6Zlbr/d2X/3khYfKvvxn3Z0bb/9xePOC6y+mnwpz6OvTFbuGeuZtOXTslbWz1p69PWfxHsG99/eh/g3dj/8588dguUfVxI1NMx94c+eSikf+dl1orDFnv7r6tcc+/+69fM3BwVVL1Kej/D6x/ItT8zftv7J61bV/y6+t/PDKX8OeeXduSreX7SpzuYaHy1zvH15VZs1wuf4DgoVD8g==
+1 -1
View File
@@ -89,4 +89,4 @@ LangGraph Studio Web is a specialized UI that you can connect to LangGraph API s
## Deployment
Once your LangGraph app is running locally, you can deploy it using LangGraph Platform. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models.
Once your LangGraph app is running locally, you can deploy it using LangGraph Cloud or self-hosted options. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models.
+1 -1
View File
@@ -13,7 +13,7 @@ You can use a prebuilt chat UI for interacting with any LangGraph agent through
## Run agent in UI
First, set up LangGraph API server [locally](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
First, set up LangGraph API server [locally](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Cloud](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the repository and [run the dev server locally](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#setup):
+2 -2
View File
@@ -5,11 +5,11 @@ There are many situations in which it is useful to run an assistant on a schedul
For example, say that you're building an assistant that runs daily and sends an email summary
of the day's news. You could use a cron job to run the assistant every day at 8:00 PM.
LangGraph Platform supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will:
LangGraph Cloud supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will:
- Create a new thread with the specified assistant
- Send the specified input to that thread
Note that this sends the same input to the thread every time. See the [how-to guide](../../cloud/how-tos/cron_jobs.md) for creating cron jobs.
The LangGraph Platform API provides several endpoints for creating and managing cron jobs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details.
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details.
+1 -1
View File
@@ -2,4 +2,4 @@
A run is an invocation of an [assistant](../../concepts/assistants.md). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./threads.md).
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
+4 -5
View File
@@ -1,12 +1,11 @@
# Threads
A thread contains the accumulated state of a sequence of [runs](./runs.md). When a run is executed, the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread contains the accumulated state of a sequence of [runs](./runs.md). If a run is executed on a thread, then the [state](../../concepts/low_level.md#state) of the underlying graph of the assistant will be persisted to the thread.
A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
The state of a thread at a particular point in time is called a [checkpoint](../../concepts/persistence.md#checkpoints). Checkpoints are persisted and can be used to restore the state of a thread at a later time.
The state of a thread at a particular point in time is called a [checkpoint](../../concepts/persistence.md#checkpoints). Checkpoints can be used to restore the state of a thread at a later time.
## Learn more
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/persistence.md).
* For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/persistence.md).
* The LangGraph Platform API provides several endpoints for creating and managing threads and thread state. See the [API reference](../../cloud/reference/api/api_ref.html#tag/threads) for more details.
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../../cloud/reference/api/api_ref.html#tag/threads) for more details.
+2 -2
View File
@@ -1,7 +1,7 @@
# Webhooks
Webhooks enable event-driven communication from your LangGraph Platform application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Platform has finished running.
Webhooks enable event-driven communication from your LangGraph Cloud application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Cloud has finished running.
Many LangGraph Platform endpoints accept a `webhook` parameter. If this parameter is specified by a an endpoint that can accept POST requests, LangGraph Platform will send a request at the completion of a run.
Many LangGraph Cloud endpoints accept a `webhook` parameter. If this parameter is specified by a an endpoint that can accept POST requests, LangGraph Cloud will send a request at the completion of a run.
See the corresponding [how-to guide](../../cloud/how-tos/webhooks.md) for more detail.
+5 -5
View File
@@ -4,14 +4,14 @@ Before deploying, review the [conceptual guide for the Cloud SaaS](../../concept
## Prerequisites
1. LangGraph Platform applications are deployed from GitHub repositories. Configure and upload a LangGraph Platform application to a GitHub repository in order to deploy it to LangGraph Platform.
1. [Verify that the LangGraph API runs locally](../../tutorials/langgraph-platform/local-server.md). If the API does not run successfully (i.e. `langgraph dev`), deploying to LangGraph Platform will fail as well.
1. LangGraph Cloud applications are deployed from GitHub repositories. Configure and upload a LangGraph Cloud application to a GitHub repository in order to deploy it to LangGraph Cloud.
1. [Verify that the LangGraph API runs locally](../../tutorials/langgraph-platform/local-server.md). If the API does not run successfully (i.e. `langgraph dev`), deploying to LangGraph Cloud will fail as well.
## Create New Deployment
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. In the top-right corner, select `+ New Deployment` to create a new deployment.
1. In the `Create New Deployment` panel, fill out the required fields.
1. `Deployment details`
@@ -38,7 +38,7 @@ When [creating a new deployment](#create-new-deployment), a new revision is crea
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select an existing deployment to create a new revision for.
1. In the `Deployment` view, in the top-right corner, select `+ New Revision`.
1. In the `New Revision` modal, fill out the required fields.
@@ -79,7 +79,7 @@ Starting from the `LangGraph Platform` view...
Starting from the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>...
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Platform deployments.
1. In the left-hand navigation panel, select `LangGraph Platform`. The `LangGraph Platform` view contains a list of existing LangGraph Cloud deployments.
1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`.
1. A `Confirmation` modal will appear. Select `Delete`.
+3 -3
View File
@@ -1,11 +1,11 @@
# How to Set Up a LangGraph Application with requirements.txt
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies.
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `requirements.txt` to specify project dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example), which you can play around with to learn more about how to setup your LangGraph application for deployment.
!!! tip "Setup with pyproject.toml"
If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Platform.
If you prefer using poetry for dependency management, check out [this how-to guide](./setup_pyproject.md) on using `pyproject.toml` for LangGraph Cloud.
!!! tip "Setup with a Monorepo"
If you are interested in deploying a graph located inside a monorepo, take a look at [this repository](https://github.com/langchain-ai/langgraph-example-monorepo) for an example of how to do so.
@@ -130,7 +130,7 @@ graph = workflow.compile()
```
!!! warning "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
Example file directory:
@@ -1,6 +1,6 @@
# How to Set Up a LangGraph.js Application
A [LangGraph.js](https://langchain-ai.github.io/langgraphjs/) application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph.js application for deployment using `package.json` to specify project dependencies.
A [LangGraph.js](https://langchain-ai.github.io/langgraphjs/) application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph.js application for deployment using `package.json` to specify project dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraphjs-studio-starter), which you can play around with to learn more about how to setup your LangGraph application for deployment.
@@ -157,7 +157,7 @@ export const graph = workflow.compile();
!!! info "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a JavaScript module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a JavaScript module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
Example file directory:
@@ -1,6 +1,6 @@
# How to Set Up a LangGraph Application with pyproject.toml
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Platform (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies.
A LangGraph application must be configured with a [LangGraph configuration file](../reference/cli.md#configuration-file) in order to be deployed to LangGraph Cloud (or to be self-hosted). This how-to guide discusses the basic steps to setup a LangGraph application for deployment using `pyproject.toml` to define your package's dependencies.
This walkthrough is based on [this repository](https://github.com/langchain-ai/langgraph-example-pyproject), which you can play around with to learn more about how to setup your LangGraph application for deployment.
@@ -59,7 +59,7 @@ Example `pyproject.toml` file:
[tool.poetry]
name = "my-agent"
version = "0.0.1"
description = "An excellent agent build for LangGraph Platform."
description = "An excellent agent build for LangGraph cloud."
authors = ["Polly the parrot <1223+polly@users.noreply.github.com>"]
license = "MIT"
readme = "README.md"
@@ -138,7 +138,7 @@ graph = workflow.compile()
```
!!! warning "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
The build process for LangGraph Cloud requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
Example file directory:
@@ -1,342 +0,0 @@
# Human-in-the-loop
LangGraph supports robust **human-in-the-loop (HIL)** workflows, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context.
Please see [the overview of LangGraph human-in-the-loop](../../concepts/human_in_the_loop.md) features for more information.
## `interrupt`
The [`interrupt` function][langgraph.types.interrupt] in LangGraph enables human-in-the-loop workflows by pausing the graph at a specific node, presenting information to a human, and resuming the graph with their input. It's useful for tasks like approvals, edits, or gathering additional context.
The graph is resumed using a [`Command`](../reference/types.md#langgraph.types.Command) object that provides the human's response.
**Graph node with `interrupt`:**
```python
# highlight-next-line
from langgraph.types import interrupt, Command
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
**LangGraph API invoke & resume:**
=== "Python"
```python
from langgraph_sdk import get_client
# highlight-next-line
from langgraph_sdk.schema import Command
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
# highlight-next-line
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph until the interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } // (1)!
);
console.log(result['__interrupt__']); // (2)!
// > [
// > {
// > 'value': {'text_to_revise': 'original text'},
// > 'resumable': True,
// > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
// > 'when': 'during'
// > }
// > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
// highlight-next-line
{ command: { resume: "Edited text" }} // (3)!
));
// > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
=== "cURL"
Create a thread:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit.:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
??? example "Extended example: using `interrupt`"
This is an example graph you can run in the LangGraph API server.
See [LangGraph Platform quickstart](../quick_start.md) for more details.
```python
from typing import TypedDict
import uuid
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
# highlight-next-line
from langgraph.types import interrupt, Command
class State(TypedDict):
some_text: str
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
# Build the graph
graph_builder = StateGraph(State)
graph_builder.add_node("human_node", human_node)
graph_builder.add_edge(START, "human_node")
graph = graph_builder.compile()
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
Once you have a running LangGraph API server, you can interact with it using
[LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)
=== "Python"
```python
from langgraph_sdk import get_client
# highlight-next-line
from langgraph_sdk.schema import Command
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the interrupt is hit.
result = await client.runs.wait(
thread_id,
assistant_id,
input={"some_text": "original text"} # (1)!
)
print(result['__interrupt__']) # (2)!
# > [
# > {
# > 'value': {'text_to_revise': 'original text'},
# > 'resumable': True,
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
# > 'when': 'during'
# > }
# > ]
# Resume the graph
print(await client.runs.wait(
thread_id,
assistant_id,
# highlight-next-line
command=Command(resume="Edited text") # (3)!
))
# > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph until the interrupt is hit.
const result = await client.runs.wait(
threadID,
assistantID,
{ input: { "some_text": "original text" } } // (1)!
);
console.log(result['__interrupt__']); // (2)!
// > [
// > {
// > 'value': {'text_to_revise': 'original text'},
// > 'resumable': True,
// > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
// > 'when': 'during'
// > }
// > ]
// Resume the graph
console.log(await client.runs.wait(
threadID,
assistantID,
// highlight-next-line
{ command: { resume: "Edited text" }} // (3)!
));
// > {'some_text': 'Edited text'}
```
1. The graph is invoked with some initial state.
2. When the graph hits the interrupt, it returns an interrupt object with the payload and metadata.
3. The graph is resumed with a `{ resume: ... }` command object, injecting the human's input and continuing execution.
=== "cURL"
Create a thread:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph until the interrupt is hit:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"some_text\": \"original text\"}
}"
```
Resume the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"Edited text\"
}
}"
```
## Learn more
- [**LangGraph human-in-the-loop overview**](../../concepts/human_in_the_loop.md): learn more about LangGraph human-in-the-loop features.
- [**Design patterns**](../../how-tos/human_in_the_loop/add-human-in-the-loop.md#design-patterns): learn how to implement patterns like approving/rejecting actions, requesting user input, and more.
- [**How to review tool calls**](./human_in_the_loop_review_tool_calls.md): detailed examples of how to review and approve/edit tool calls or provide feedback to the tool-calling LLM.
@@ -0,0 +1,152 @@
# How to version Assistants
!!! info "Prerequisites"
- [Assistants Overview](../../concepts/assistants.md)
- [How to create an Assistant](./configuration_cloud.md)
In this guide we will show you how to create, manage and use multiple versions of an assistant. If you have not already, please first see [this](./configuration_cloud.md) guide on creating an assistant. For this example, assume you have a graph with the following configuration schema:
=== "Python"
```python
class Config(BaseModel):
model_name: Literal["anthropic", "openai"] = "anthropic"
system_prompt: str
builder = StateGraph(State, config_schema=Config)
```
=== "Javascript"
```js
const ConfigAnnotation = Annotation.Root({
modelName: Annotation<z.enum(["openai", "anthropic"])>({
default: () => "anthropic",
}),
systemPrompt: Annotation<String>
});
// the rest of your code
const builder = new StateGraph(StateAnnotation, ConfigAnnotation);
```
And that you have the following assistant already created:
{
"assistant_id": "62e209ca-9154-432a-b9e9-2d75c7a9219b",
"graph_id": "agent",
"name": "Open AI Assistant"
"config": {
"configurable": {
"model_name": "openai",
"system_prompt": "You are a helpful assistant."
}
},
"metadata": {}
"created_at": "2024-08-31T03:09:10.230718+00:00",
"updated_at": "2024-08-31T03:09:10.230718+00:00",
}
## Create a new version for your assistant
### LangGraph SDK
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](../reference/sdk/python_sdk_ref.md#langgraph_sdk.client.AssistantsClient.update) and [JS](../reference/sdk/js_ts_sdk_ref.md#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previously versions.
For example, to update your assistant's system prompt:
=== "Python"
```python
openai_assistant_v2 = await client.assistants.update(
openai_assistant["assistant_id"],
config={
"configurable": {
"model_name": "openai",
"system_prompt": "You are an unhelpful assistant!",
}
},
)
```
=== "Javascript"
```js
const openaiAssistantV2 = await client.assistants.update(
openai_assistant["assistant_id"],
{
config: {
configurable: {
model_name: 'openai',
system_prompt: 'You are an unhelpful assistant!',
},
},
});
```
=== "CURL"
```bash
curl --request PATCH \
--url <DEPOLYMENT_URL>/assistants/<ASSISTANT_ID> \
--header 'Content-Type: application/json' \
--data '{
"config": {"model_name": "openai", "system_prompt": "You are an unhelpful assistant!"}
}'
```
This will create a new version of the assistant with the updated parameters and set this as the active version of your assistant. If you now run your graph and pass in this assistant id, it will use this latest version.
### LangGraph Platform UI
You can also edit assistants from the LangGraph Platform UI.
Inside your deployment, select the "Assistants" tab. This will load a table of all of the assistants in your deployment, across all graphs.
To edit an existing assistant, select the "Edit" button for the specified assistant. This will open a form where you can edit the assistant's name, description, and configuration.
Additionally, if using LangGraph Studio, you can edit the assistants and create new versions via the "Manage Assistants" button.
## Use a previous assistant version
### LangGraph SDK
You can also change the active version of your assistant. To do so, use the `setLatest` method.
In the example above, to rollback to the first version of the assistant:
=== "Python"
```python
await client.assistants.set_latest(openai_assistant['assistant_id'], 1)
```
=== "Javascript"
```js
await client.assistants.setLatest(openaiAssistant['assistant_id'], 1);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/<ASSISTANT_ID>/latest \
--header 'Content-Type: application/json' \
--data '{
"version": 1
}'
```
If you now run your graph and pass in this assistant id, it will use the first version of the assistant.
### LangGraph Platform UI
If using LangGraph Studio, to set the active version of your asssistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
!!! warning "Deleting Assistants"
Deleting as assistant will delete ALL of it's versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
@@ -0,0 +1,203 @@
# Check the Status of your Threads
## Setup
To start, we can setup our client with whatever URL you are hosting your graph from:
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Find idle threads
We can use the following commands to find threads that are idle, which means that all runs executed on the thread have finished running:
=== "Python"
```python
print(await client.threads.search(status="idle",limit=1))
```
=== "Javascript"
```js
console.log(await client.threads.search({ status: "idle", limit: 1 }));
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"status": "idle", "limit": 1}'
```
Output:
[{'thread_id': 'cacf79bb-4248-4d01-aabc-938dbd60ed2c',
'created_at': '2024-08-14T17:36:38.921660+00:00',
'updated_at': '2024-08-14T17:36:38.921660+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'idle',
'config': {'configurable': {}}}]
## Find interrupted threads
We can use the following commands to find threads that have been interrupted in the middle of a run, which could either mean an error occurred before the run finished or a human-in-the-loop breakpoint was reached and the run is waiting to continue:
=== "Python"
```python
print(await client.threads.search(status="interrupted",limit=1))
```
=== "Javascript"
```js
console.log(await client.threads.search({ status: "interrupted", limit: 1 }));
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"status": "interrupted", "limit": 1}'
```
Output:
[{'thread_id': '0d282b22-bbd5-4d95-9c61-04dcc2e302a5',
'created_at': '2024-08-14T17:41:50.235455+00:00',
'updated_at': '2024-08-14T17:41:50.235455+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'interrupted',
'config': {'configurable': {}}}]
## Find busy threads
We can use the following commands to find threads that are busy, meaning they are currently handling the execution of a run:
=== "Python"
```python
print(await client.threads.search(status="busy",limit=1))
```
=== "Javascript"
```js
console.log(await client.threads.search({ status: "busy", limit: 1 }));
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"status": "busy", "limit": 1}'
```
Output:
[{'thread_id': '0d282b22-bbd5-4d95-9c61-04dcc2e302a5',
'created_at': '2024-08-14T17:41:50.235455+00:00',
'updated_at': '2024-08-14T17:41:50.235455+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'busy',
'config': {'configurable': {}}}]
## Find specific threads
You may also want to check the status of specific threads, which you can do in a few ways:
### Find by ID
You can use the `get` function to find the status of a specific thread, as long as you have the ID saved
=== "Python"
```python
print((await client.threads.get(<THREAD_ID>))['status'])
```
=== "Javascript"
```js
console.log((await client.threads.get(<THREAD_ID>)).status);
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID> \
--header 'Content-Type: application/json' | jq -r '.status'
```
Output:
'idle'
### Find by metadata
The search endpoint for threads also allows you to filter on metadata, which can be helpful if you use metadata to tag threads in order to keep them organized:
=== "Python"
```python
print((await client.threads.search(metadata={"foo":"bar"},limit=1))[0]['status'])
```
=== "Javascript"
```js
console.log((await client.threads.search({ metadata: { "foo": "bar" }, limit: 1 }))[0].status);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"metadata": {"foo":"bar"}, "limit": 1}' | jq -r '.[0].status'
```
Output:
'idle'
+11 -108
View File
@@ -1,6 +1,11 @@
# Manage assistants
# How to create Assistants
In this guide we will show how to create, configure, and manage an [assistant](../../concepts/assistants.md).
!!! info "Prerequisites"
- [Assistants Overview](../../concepts/assistants.md)
- [Configuration](../../concepts/low_level.md#configuration)
In this guide we will show how to create and configure an assistant.
First, as a brief refresher on the concept of configurations, consider the following simple `call_model` node and configuration schema. Observe that this node tries to read and use the `model_name` as defined by the `config` object's `configurable`.
@@ -46,7 +51,7 @@ First, as a brief refresher on the concept of configurations, consider the follo
For more information on configurations, [see here](../../concepts/low_level.md#configuration).
## Create an assistant
## Creating an Assistant
### LangGraph SDK
@@ -74,7 +79,7 @@ This example uses the same configuration schema as above, and creates an assista
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const openAIAssistant = await client.assistants.create({
let openAIAssistant = await client.assistants.create({
graphId: 'agent',
name: "Open AI Assistant",
config: { "configurable": { "model_name": "openai" } },
@@ -118,7 +123,7 @@ To create a new assistant, select the "+ New assistant" button. This will open a
To confirm, click "Create assistant". This will take you to [LangGraph Studio](../../concepts/langgraph_studio.md) where you can test the assistant. If you go back to the "Assistants" tab in the deployment, you will see the newly created assistant in the table.
## Use an assistant
## Using an Assistant
### LangGraph SDK
@@ -145,7 +150,7 @@ We have now created an assistant called "Open AI Assistant" that has `model_name
```js
const thread = await client.threads.create();
const input = { "messages": [{ "role": "user", "content": "who made you?" }] };
let input = { "messages": [{ "role": "user", "content": "who made you?" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
@@ -223,105 +228,3 @@ Output:
### LangGraph Platform UI
Inside your deployment, select the "Assistants" tab. For the assistant you would like to use, click the "Studio" button. This will open LangGraph Studio with the selected assistant. When you submit an input (either in Graph or Chat mode), the selected assistant and its configuration will be used.
## Create a new version for your assistant
### LangGraph SDK
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](../reference/sdk/python_sdk_ref.md#langgraph_sdk.client.AssistantsClient.update) and [JS](../reference/sdk/js_ts_sdk_ref.md#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previously versions.
For example, to update your assistant's system prompt:
=== "Python"
```python
openai_assistant_v2 = await client.assistants.update(
openai_assistant["assistant_id"],
config={
"configurable": {
"model_name": "openai",
"system_prompt": "You are an unhelpful assistant!",
}
},
)
```
=== "Javascript"
```js
const openaiAssistantV2 = await client.assistants.update(
openai_assistant["assistant_id"],
{
config: {
configurable: {
model_name: 'openai',
system_prompt: 'You are an unhelpful assistant!',
},
},
});
```
=== "CURL"
```bash
curl --request PATCH \
--url <DEPOLYMENT_URL>/assistants/<ASSISTANT_ID> \
--header 'Content-Type: application/json' \
--data '{
"config": {"model_name": "openai", "system_prompt": "You are an unhelpful assistant!"}
}'
```
This will create a new version of the assistant with the updated parameters and set this as the active version of your assistant. If you now run your graph and pass in this assistant id, it will use this latest version.
### LangGraph Platform UI
You can also edit assistants from the LangGraph Platform UI.
Inside your deployment, select the "Assistants" tab. This will load a table of all of the assistants in your deployment, across all graphs.
To edit an existing assistant, select the "Edit" button for the specified assistant. This will open a form where you can edit the assistant's name, description, and configuration.
Additionally, if using LangGraph Studio, you can edit the assistants and create new versions via the "Manage Assistants" button.
## Use a previous assistant version
### LangGraph SDK
You can also change the active version of your assistant. To do so, use the `setLatest` method.
In the example above, to rollback to the first version of the assistant:
=== "Python"
```python
await client.assistants.set_latest(openai_assistant['assistant_id'], 1)
```
=== "Javascript"
```js
await client.assistants.setLatest(openaiAssistant['assistant_id'], 1);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/<ASSISTANT_ID>/latest \
--header 'Content-Type: application/json' \
--data '{
"version": 1
}'
```
If you now run your graph and pass in this assistant id, it will use the first version of the assistant.
### LangGraph Platform UI
If using LangGraph Studio, to set the active version of your asssistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
!!! warning "Deleting Assistants"
Deleting as assistant will delete ALL of it's versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
+134
View File
@@ -0,0 +1,134 @@
# Copying Threads
You may wish to copy (i.e. "fork") an existing thread in order to keep the existing thread's history and create independent runs that do not affect the original thread. This guide shows how you can do that.
## Setup
This code assumes you already have a thread to copy.
For more information, see these guides on [Threads](../../cloud/concepts/threads.md) and [Streaming](../../concepts/streaming.md).
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url="<DEPLOYMENT_URL>")
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: "<DEPLOYMENT_URL>" });
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{
"metadata": {}
}'
```
## Copying a thread
The code below assumes that a thread you'd like to copy already exists.
Copying a thread will create a new thread with the same history as the existing thread, and then allow you to continue executing runs.
### Create copy
=== "Python"
```python
copied_thread = await client.threads.copy(<THREAD_ID>)
```
=== "Javascript"
```js
let copiedThread = await client.threads.copy(<THREAD_ID>);
```
=== "CURL"
```bash
curl --request POST --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/copy \
--header 'Content-Type: application/json'
```
### Verify copy
We can verify that the history from the prior thread did indeed copy over correctly:
=== "Python"
```python
def remove_thread_id(d):
if 'metadata' in d and 'thread_id' in d['metadata']:
del d['metadata']['thread_id']
return d
original_thread_history = list(map(remove_thread_id,await client.threads.get_history(<THREAD_ID>)))
copied_thread_history = list(map(remove_thread_id,await client.threads.get_history(copied_thread['thread_id'])))
# Compare the two histories
assert original_thread_history == copied_thread_history
# if we made it here the assertion passed!
print("The histories are the same.")
```
=== "Javascript"
```js
function removeThreadId(d) {
if (d.metadata && d.metadata.thread_id) {
delete d.metadata.thread_id;
}
return d;
}
// Assuming `client.threads.getHistory(threadId)` is an async function that returns a list of dicts
async function compareThreadHistories(threadId, copiedThreadId) {
const originalThreadHistory = (await client.threads.getHistory(threadId)).map(removeThreadId);
const copiedThreadHistory = (await client.threads.getHistory(copiedThreadId)).map(removeThreadId);
// Compare the two histories
console.assert(JSON.stringify(originalThreadHistory) === JSON.stringify(copiedThreadHistory));
// if we made it here the assertion passed!
console.log("The histories are the same.");
}
// Example usage
compareThreadHistories(<THREAD_ID>, copiedThread.thread_id);
```
=== "CURL"
```bash
if diff <(
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -S 'map(del(.metadata.thread_id))'
) <(
curl --request GET --url <DEPLOYMENT_URL>/threads/<COPIED_THREAD_ID>/history | jq -S 'map(del(.metadata.thread_id))'
) >/dev/null; then
echo "The histories are the same."
else
echo "The histories are different."
fi
```
Output:
The histories are the same.
+2 -2
View File
@@ -1,6 +1,6 @@
# Use cron jobs
# Cron Jobs
Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Platform allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.
Sometimes you don't want to run your graph based on user interaction, but rather you would like to schedule your graph to run on a schedule - for example if you wish for your graph to compose and send out a weekly email of to-dos for your team. LangGraph Cloud allows you to do this without having to write your own script by using the `Crons` client. To schedule a graph job, you need to pass a [cron expression](https://crontab.cronhub.io/) to inform the client when you want to run the graph. `Cron` jobs are run in the background and do not interfere with normal invocations of the graph.
## Setup
@@ -1,86 +1,24 @@
# Breakpoints
# How to add static breakpoints
[Breakpoints](../../concepts/breakpoints.md) pause graph execution at defined points and let you step through each stage. They use LangGraph's [**persistence layer**](../../concepts/persistence.md), which saves the graph state after each step.
!!! tip "Prerequisites"
With breakpoints, you can inspect the graph's state and node inputs at any point. Execution pauses **indefinitely** until you resume, as the checkpointer preserves the state.
This guide assumes familiarity with the following concepts:
## Set breakpoints
* [Breakpoints](../../concepts/breakpoints.md)
* [LangGraph Glossary](../../concepts/low_level.md)
=== "Compile time"
Human-in-the-loop (HIL) interactions are crucial for [agentic systems](../../concepts/agentic_concepts.md#human-in-the-loop). [Breakpoints](../../concepts/breakpoints.md) are a common HIL interaction pattern, allowing the graph to stop at specific steps and seek human approval before proceeding (e.g., for sensitive actions).
```python
# highlight-next-line
graph = graph_builder.compile( # (1)!
# highlight-next-line
interrupt_before=["node_a"], # (2)!
# highlight-next-line
interrupt_after=["node_b", "node_c"], # (3)!
)
```
Breakpoints are built on top of LangGraph [checkpoints](../../concepts/persistence.md#checkpoints), which save the graph's state after each node execution. Checkpoints are saved in [threads](../../concepts/persistence.md#threads) that preserve graph state and can be accessed after a graph has finished execution. This allows for graph execution to pause at specific points, await human approval, and then resume execution from the last checkpoint.
1. The breakpoints are set during `compile` time.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
## Setup
=== "Run time"
### Code for your graph
=== "Python"
In this how-to we use a simple ReAct style hosted graph (you can see the full code for defining it [here](../../how-tos/human_in_the_loop/breakpoints.ipynb)). The important thing is that there are two nodes (one named `agent` that calls the LLM, and one named `action` that calls the tool), and a routing function from `agent` that determines whether to call `action` next or just end the graph run (the `action` node always calls the `agent` node after execution).
```python
# highlight-next-line
await client.runs.wait( # (1)!
thread_id,
assistant_id,
inputs=inputs,
# highlight-next-line
interrupt_before=["node_a"], # (2)!
# highlight-next-line
interrupt_after=["node_b", "node_c"] # (3)!
)
```
1. `client.runs.wait` is called with the `interrupt_before` and `interrupt_after` parameters. This is a run-time configuration and can be changed for every invocation.
2. `interrupt_before` specifies the nodes where execution should pause before the node is executed.
3. `interrupt_after` specifies the nodes where execution should pause after the node is executed.
=== "JavaScript"
```js
// highlight-next-line
await client.runs.wait( // (1)!
threadID,
assistantID,
{
input: input,
// highlight-next-line
interruptBefore: ["node_a"], // (2)!
// highlight-next-line
interruptAfter: ["node_b", "node_c"] // (3)!
}
)
```
1. `client.runs.wait` is called with the `interruptBefore` and `interruptAfter` parameters. This is a run-time configuration and can be changed for every invocation.
2. `interruptBefore` specifies the nodes where execution should pause before the node is executed.
3. `interruptAfter` specifies the nodes where execution should pause after the node is executed.
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"interrupt_before\": [\"node_a\"],
\"interrupt_after\": [\"node_b\", \"node_c\"],
\"input\": <INPUT>
}"
```
!!! tip
This example shows how to add **static** breakpoints. See [this guide](../../how-tos/human_in_the_loop/breakpoints.ipynb) for more options for how to add breakpoints.
### SDK Initialization
=== "Python"
@@ -88,97 +26,130 @@ With breakpoints, you can inspect the graph's state and node inputs at any point
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph until the breakpoint
result = await client.runs.wait(
thread_id,
assistant_id,
input=inputs # (1)!
)
# Resume the graph
await client.runs.wait(
thread_id,
assistant_id,
input=None # (2)!
)
```
1. The graph is run until the first breakpoint is hit.
2. The graph is resumed by passing in `None` for the input. This will run the graph until the next breakpoint is hit.
=== "JavaScript"
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const assistantId = "agent";
const thread = await client.threads.create();
const threadID = thread["thread_id"];
```
// Run the graph until the breakpoint
const result = await client.runs.wait(
threadID,
assistantID,
{ input: input } // (1)!
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Adding a breakpoint
We now want to add a breakpoint in our graph run, which we will do before a tool is called.
We can do this by adding `interrupt_before=["action"]`, which tells us to interrupt before calling the action node.
We can do this either when compiling the graph or when kicking off a run.
Here we will do it when kicking of a run, if you would like to to do it at compile time you need to edit the python file where your graph is defined and add the `interrupt_before` parameter when you call `.compile`.
First let's access our hosted LangGraph instance through the SDK:
And, now let's compile it with a breakpoint before the tool node:
=== "Python"
```python
input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
interrupt_before=["action"],
):
print(f"Receiving new event of type: {chunk.event}...")
print(chunk.data)
print("\n\n")
```
=== "Javascript"
```js
const input = { messages: [{ role: "human", content: "what's the weather in sf" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
interruptBefore: ["action"]
}
);
// Resume the graph
await client.runs.wait(
threadID,
assistantID,
{ input: null } // (2)!
);
for await (const chunk of streamResponse) {
console.log(`Receiving new event of type: ${chunk.event}...`);
console.log(chunk.data);
console.log("\n\n");
}
```
1. The graph is run until the first breakpoint is hit.
2. The graph is resumed by passing in `null` for the input. This will run the graph until the next breakpoint is hit.
=== "cURL"
Create a thread:
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in sf\"}]},
\"interrupt_before\": [\"action\"],
\"stream_mode\": [
\"messages\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "") {
print data_content "\n"
}
sub(/^event: /, "Receiving event of type: ", $0)
printf "%s...\n", $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "") {
print data_content "\n"
}
}
'
```
Run the graph until the breakpoint:
Output:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": <INPUT>
}"
```
Receiving new event of type: metadata...
{'run_id': '3b77ef83-687a-4840-8858-0371f91a92c3'}
Receiving new event of type: data...
{'agent': {'messages': [{'content': [{'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB', 'input': {'query': 'weather in san francisco'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-e5d17791-4d37-4ad2-815f-a0c4cba62585', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in san francisco'}, 'id': 'toolu_01HwZqM1ptX6E15A5LAmyZTB'}], 'invalid_tool_calls': []}]}}
Receiving new event of type: end...
None
Resume the graph:
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\"
}"
```
## Learn more
- [**LangGraph breakpoints guide**](../../how-tos/human_in_the_loop/breakpoints.ipynb): learn more about adding breakpoints in LangGraph.
@@ -0,0 +1,277 @@
# How to Edit State of a Deployed Graph
When creating LangGraph agents, it is often nice to add a human-in-the-loop component. This can be helpful when giving them access to tools. Often in these situations you may want to edit the graph state before continuing (for example, to edit what tool is being called, or how it is being called).
This can be in several ways, but the primary supported way is to add an "interrupt" before a node is executed. This interrupts execution at that node. You can then use update_state to update the state, and then resume from that spot to continue.
## Setup
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/time-travel.ipynb) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Editing state
### Initial invocation
Now let's invoke our graph, making sure to interrupt before the `action` node.
=== "Python"
```python
input = { 'messages':[{ "role":"user", "content":"search for weather in SF" }] }
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
interrupt_before=["action"],
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = { messages: [{ role: "human", content: "search for weather in SF" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
interruptBefore: ["action"],
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"search for weather in SF\"}]},
\"interrupt_before\": [\"action\"],
\"stream_mode\": [
\"updates\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
```
Output:
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll search for the current weather in San Francisco for you using the search function. Here's how I'll do that:", 'type': 'text'}, {'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-6dbb0167-f8f6-4e2a-ab68-229b2d1fbb64', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
### Edit the state
Now, let's assume we actually meant to search for the weather in Sidi Frej (another city with the initials SF). We can edit the state to properly reflect that:
=== "Python"
```python
# First, lets get the current state
current_state = await client.threads.get_state(thread['thread_id'])
# Let's now get the last message in the state
# This is the one with the tool calls that we want to update
last_message = current_state['values']['messages'][-1]
# Let's now update the args for that tool call
last_message['tool_calls'][0]['args'] = {'query': 'current weather in Sidi Frej'}
# Let's now call `update_state` to pass in this message in the `messages` key
# This will get treated as any other update to the state
# It will get passed to the reducer function for the `messages` key
# That reducer function will use the ID of the message to update it
# It's important that it has the right ID! Otherwise it would get appended
# as a new message
await client.threads.update_state(thread['thread_id'], {"messages": last_message})
```
=== "Javascript"
```js
// First, let's get the current state
const currentState = await client.threads.getState(thread["thread_id"]);
// Let's now get the last message in the state
// This is the one with the tool calls that we want to update
let lastMessage = currentState.values.messages.slice(-1)[0];
// Let's now update the args for that tool call
lastMessage.tool_calls[0].args = { query: "current weather in Sidi Frej" };
// Let's now call `update_state` to pass in this message in the `messages` key
// This will get treated as any other update to the state
// It will get passed to the reducer function for the `messages` key
// That reducer function will use the ID of the message to update it
// It's important that it has the right ID! Otherwise it would get appended
// as a new message
await client.threads.updateState(thread["thread_id"], { values: { messages: lastMessage } });
```
=== "CURL"
```bash
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | \
jq '.values.messages[-1] | (.tool_calls[0].args = {"query": "current weather in Sidi Frej"})' | \
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json' \
--data @-
```
Output:
{'configurable': {'thread_id': '9c8f1a43-9dd8-4017-9271-2c53e57cf66a',
'checkpoint_ns': '',
'checkpoint_id': '1ef58e7e-3641-649f-8002-8b4305a64858'}}
### Resume invocation
Now we can resume our graph run but with the updated state:
=== "Python"
```python
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=None,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: null,
streamMode: "updates",
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"stream_mode\": [
\"updates\"
]
}"| \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
```
Output:
{'action': {'messages': [{'content': '["I looked up: current weather in Sidi Frej. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '1161b8d1-bee4-4188-9be8-698aecb69f10', 'tool_call_id': 'toolu_01KEJMBFozSiZoS4mAcPZeqQ'}]}}
{'agent': {'messages': [{'content': [{'text': 'I apologize for the confusion in my search query. It seems the search function interpreted "SF" as "Sidi Frej" instead of "San Francisco" as we intended. Let me search again with the full city name to get the correct information:', 'type': 'text'}, {'id': 'toolu_0111rrwgfAcmurHZn55qjqTR', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-b8c25779-cfb4-46fc-a421-48553551242f', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '6bc632ae-5ee6-4d01-9532-79c524a2d443', 'tool_call_id': 'toolu_0111rrwgfAcmurHZn55qjqTR'}]}}
{'agent': {'messages': [{'content': "Now, based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. \n\nIt's worth noting that the search result included an unusual comment about Gemini, which doesn't seem directly related to the weather. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of weather information, we can focus on the fact that it's sunny in San Francisco right now.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other location?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-227a042b-dd97-476e-af32-76a3703af5d8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
As you can see it now looks up the current weather in Sidi Frej (although our dummy search node still returns results for SF because we don't actually do a search in this example, we just return the same "It's sunny in San Francisco ..." result every time).
@@ -86,7 +86,7 @@ First, we need to setup our client so that we can communicate with our hosted gr
const thread = await client.threads.create();
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -135,7 +135,7 @@ First, let's run the agent with an input that requires tool calls with approval:
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -194,7 +194,7 @@ To approve the tool call, we need to let `human_review_node` know what value to
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -257,7 +257,7 @@ Let's now say we want to edit the tool call. E.g. change some of the parameters
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -323,7 +323,7 @@ To do this, we will use `Command` with a different resume value of `{"action": "
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -395,7 +395,7 @@ For this example we will just add a single tool call representing the feedback (
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -462,7 +462,7 @@ To do this, we will use `Command` with a different resume value of `{"action": "
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -525,7 +525,7 @@ We can see that we now get to another interrupt - because it went back to the mo
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
@@ -1,240 +1,386 @@
# Time travel
# How to Replay and Branch from Prior States
LangGraph provides [**time travel**](../../concepts/time-travel.md) functionality to **resume execution from a prior checkpoint** — either replaying the same state or modifying it to explore alternatives. In all cases, resuming past execution produces a **new fork** in the history.
With LangGraph Cloud you have the ability to return to any of your prior states and either re-run the graph to reproduce issues noticed during testing, or branch out in a different way from what was originally done in the prior states. In this guide we will show a quick example of how to rerun past states and how to branch off from previous states as well.
## Use time travel
## Setup
To use time-travel in LangGraph:
The examples below are executed against a specific deployment on LangGraph Cloud. You will use
the SDK in a similar way, but you will expect to see different results based on the graph you have deployed.
1. **Run the graph** with initial inputs using [LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/)'s [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs.
2. **Identify a checkpoint in an existing thread**: Use [`client.threads.get_history`][langgraph_sdk.client.ThreadsClient.get_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`.
Alternatively, set a [breakpoint](./human_in_the_loop_breakpoint.md) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.
3. **(Optional) modify the graph state**: Use the [`client.threads.update_state`][langgraph_sdk.client.ThreadsClient.update_state] method to modify the graphs state at the checkpoint and resume execution from alternative state.
4. **Resume execution from the checkpoint**: Use the [`client.runs.wait`][langgraph_sdk.client.RunsClient.wait] or [`client.runs.stream`][langgraph_sdk.client.RunsClient.stream] APIs with an input of `None` and the appropriate `thread_id` and `checkpoint_id`.
### SDK initialization
## Example
??? example "Example graph"
```python
from typing_extensions import TypedDict, NotRequired
from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
topic: NotRequired[str]
joke: NotRequired[str]
llm = init_chat_model(
"anthropic:claude-3-7-sonnet-latest",
temperature=0,
)
def generate_topic(state: State):
"""LLM call to generate a topic for the joke"""
msg = llm.invoke("Give me a funny topic for a joke")
return {"topic": msg.content}
def write_joke(state: State):
"""LLM call to write a joke based on the topic"""
msg = llm.invoke(f"Write a short joke about {state['topic']}")
return {"joke": msg.content}
# Build workflow
builder = StateGraph(State)
# Add nodes
builder.add_node("generate_topic", generate_topic)
builder.add_node("write_joke", write_joke)
# Add edges to connect nodes
builder.add_edge(START, "generate_topic")
builder.add_edge("generate_topic", "write_joke")
# Compile
graph = builder.compile()
```
### 1. Run the graph
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
# create a thread
thread = await client.threads.create()
thread_id = thread["thread_id"]
# Run the graph
result = await client.runs.wait(
thread_id,
assistant_id,
input={}
)
```
=== "JavaScript"
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantID = "agent";
// create a thread
const assistantId = "agent";
const thread = await client.threads.create();
const threadID = thread["thread_id"];
// Run the graph
const result = await client.runs.wait(
threadID,
assistantID,
{ input: {}}
);
```
=== "cURL"
Create a thread:
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Run the graph:
## Replay a state
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {}
}"
```
### Initial invocation
### 2. Identify a checkpoint
Before replaying a state - we need to create states to replay from! In order to do this, let's invoke our graph with a simple message:
=== "Python"
```python
# The states are returned in reverse chronological order.
states = await client.threads.get_history(thread_id)
selected_state = states[1]
print(selected_state)
```
input = {"messages": [{"role": "user", "content": "Please search the weather in SF"}]}
=== "JavaScript"
```js
// The states are returned in reverse chronological order.
const states = await client.threads.getHistory(threadID);
const selectedState = states[1];
console.log(selectedState);
```
=== "cURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history \
--header 'Content-Type: application/json'
```
### 3. Update the state (optional)
`update_state` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID.
=== "Python"
```python
new_config = await client.threads.update_state(
thread_id,
{"topic": "chickens"},
# highlight-next-line
checkpoint_id=selected_state["checkpoint_id"]
)
print(new_config)
```
=== "JavaScript"
```js
const newConfig = await client.threads.updateState(
threadID,
{
values: { "topic": "chickens" },
checkpointId: selectedState["checkpoint_id"]
}
);
console.log(newConfig);
```
=== "cURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": <CHECKPOINT_ID>,
\"values\": {\"topic\": \"chickens\"}
}"
```
### 4. Resume execution from the checkpoint
=== "Python"
```python
await client.runs.wait(
thread_id,
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
input=None,
# highlight-next-line
checkpoint_id=new_config["checkpoint_id"]
)
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "JavaScript"
=== "Javascript"
```js
await client.runs.wait(
threadID,
assistantID,
const input = { "messages": [{ "role": "user", "content": "Please search the weather in SF" }] }
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
input: null,
// highlight-next-line
checkpointId: newConfig["checkpoint_id"]
input: input,
streamMode: "updates",
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "cURL"
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": <CHECKPOINT_ID>
}"
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Please search the weather in SF\"}]},
\"stream_mode\": [
\"updates\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
```
Output:
{'agent': {'messages': [{'content': [{'text': "Certainly! I'll use the search function to look up the current weather in San Francisco for you. Let me do that now.", 'type': 'text'}, {'id': 'toolu_011vroKUtWU7SBdrngpgpFMn', 'input': {'query': 'current weather in San Francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-ee639877-d97d-40f8-96dc-d0d1ae22d203', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in San Francisco'}, 'id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '7bad0e72-5ebe-4b08-9b8a-b99b0fe22fb7', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This is great news for outdoor activities and enjoying the city's beautiful sights.\n\nIt's worth noting that the search result included an unusual comment about Geminis, which isn't typically part of a weather report. This might be due to the search engine including some astrological information or a joke in its results. However, for the purpose of answering your question about the weather, we can focus on the fact that it's sunny in San Francisco.\n\nIf you need any more specific information about the weather in San Francisco, such as temperature, wind speed, or forecast for the coming days, please let me know, and I'd be happy to search for that information for you.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-dbac539a-33c8-4f0c-9e20-91f318371e7c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
Now let's get our list of states, and invoke from the third state (right before the tool get called):
=== "Python"
```python
states = await client.threads.get_history(thread['thread_id'])
# We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node
state_to_replay = states[2]
print(state_to_replay['next'])
```
## Learn more
=== "Javascript"
- [**LangGraph time travel guide**](../../how-tos/human_in_the_loop/time-travel.ipynb): learn more about using time travel in LangGraph.
```js
const states = await client.threads.getHistory(thread['thread_id']);
// We can confirm that this state is correct by checking the 'next' attribute and seeing that it is the tool call node
const stateToReplay = states[2];
console.log(stateToReplay['next']);
```
=== "CURL"
```bash
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -r '.[2].next'
```
Output:
['action']
To rerun from a state, we need first issue an empty update to the thread state. Then we need to pass in the resulting `checkpoint_id` as follows:
=== "Python"
```python
state_to_replay = states[2]
updated_config = await client.threads.update_state(
thread["thread_id"],
{"messages": []},
checkpoint_id=state_to_replay["checkpoint_id"]
)
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id, # graph_id
input=None,
stream_mode="updates",
checkpoint_id=updated_config["checkpoint_id"]
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const stateToReplay = states[2];
const config = await client.threads.updateState(thread["thread_id"], { values: {"messages": [] }, checkpointId: stateToReplay["checkpoint_id"] });
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: null,
streamMode: "updates",
checkpointId: config["checkpoint_id"]
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | jq -c '
.[2] as $state_to_replay |
{
values: { messages: .[2].values.messages[-1] },
checkpoint_id: $state_to_replay.checkpoint_id
}' | \
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json' \
--data @- | jq .checkpoint_id | \
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": \"$1\",
\"stream_mode\": [
\"updates\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
```
Output:
{'action': {'messages': [{'content': '["I looked up: current weather in San Francisco. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': 'eba650e5-400e-4938-8508-f878dcbcc532', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco:\n\nThe weather in San Francisco is currently sunny. This is great news if you're planning any outdoor activities or simply want to enjoy a pleasant day in the city.\n\nIt's worth noting that the search result included an unusual comment about Geminis, which doesn't seem directly related to the weather. This appears to be a playful or humorous addition to the weather report, possibly from the source where this information was obtained.\n\nIs there anything else you'd like to know about the weather in San Francisco or any other information you need?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-bc6dca3f-a1e2-4f59-a69b-fe0515a348bb', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
As we can see, the graph restarted from the tool node with the same input as our original graph run.
## Branch off from previous state
Using LangGraph's checkpointing, you can do more than just replay past states. You can branch off previous locations to let the agent explore alternate trajectories or to let a user "version control" changes in a workflow.
Let's show how to do this to edit the state at a particular point in time. Let's update the state to change the input to the tool
=== "Python"
```python
# Let's now get the last message in the state
# This is the one with the tool calls that we want to update
last_message = state_to_replay['values']['messages'][-1]
# Let's now update the args for that tool call
last_message['tool_calls'][0]['args'] = {'query': 'current weather in SF'}
config = await client.threads.update_state(thread['thread_id'],{"messages":[last_message]},checkpoint_id=state_to_replay['checkpoint_id'])
```
=== "Javascript"
```js
// Let's now get the last message in the state
// This is the one with the tool calls that we want to update
let lastMessage = stateToReplay['values']['messages'][-1];
// Let's now update the args for that tool call
lastMessage['tool_calls'][0]['args'] = { 'query': 'current weather in SF' };
const config = await client.threads.updateState(thread['thread_id'], { values: { "messages": [lastMessage] }, checkpointId: stateToReplay['checkpoint_id'] });
```
=== "CURL"
```bash
curl -s --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/history | \
jq -c '
.[2] as $state_to_replay |
.[2].values.messages[-1].tool_calls[0].args.query = "current weather in SF" |
{
values: { messages: .[2].values.messages[-1] },
checkpoint_id: $state_to_replay.checkpoint_id
}' | \
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json' \
--data @-
```
Now we can rerun our graph with this new config, starting from the `new_state`, which is a branch of our `state_to_replay`:
=== "Python"
```python
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=None,
stream_mode="updates",
checkpoint_id=config['checkpoint_id']
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: null,
streamMode: "updates",
checkpointId: config['checkpoint_id'],
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl -s --request GET --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state | \
jq -c '.checkpoint_id' | \
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"checkpoint_id\": \"$1\",
\"stream_mode\": [
\"updates\"
]
}" | \
sed 's/\r$//' | \
awk '
/^event:/ {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
sub(/^event: /, "", $0)
event_type = $0
data_content = ""
}
/^data:/ {
sub(/^data: /, "", $0)
data_content = $0
}
END {
if (data_content != "" && event_type != "metadata") {
print data_content "\n"
}
}
'
```
Output:
{'action': {'messages': [{'content': '["I looked up: current weather in SF. Result: It\'s sunny in San Francisco, but you better look out if you\'re a Gemini 😈."]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': '2baf9941-4fda-4081-9f87-d76795d289f1', 'tool_call_id': 'toolu_011vroKUtWU7SBdrngpgpFMn'}]}}
{'agent': {'messages': [{'content': "Based on the search results, I can provide you with information about the current weather in San Francisco (SF):\n\nThe weather in San Francisco is currently sunny. This means it's a clear day with plenty of sunshine. \n\nIt's worth noting that the specific temperature wasn't provided in the search result, but sunny weather in San Francisco typically means comfortable temperatures. San Francisco is known for its mild climate, so even on sunny days, it's often not too hot.\n\nThe search result also included a playful reference to astrological signs, mentioning Gemini. However, this is likely just a joke or part of the search engine's presentation and not related to the actual weather conditions.\n\nIs there any specific information about the weather in San Francisco you'd like to know more about? I'd be happy to perform another search if you need details on temperature, wind conditions, or the forecast for the coming days.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-a83de52d-ed18-4402-9384-75c462485743', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
As we can see, the search query changed from San Francisco to SF, just as we had hoped!
@@ -0,0 +1,191 @@
# How to wait for user input using `interrupt`
!!! tip "Prerequisites"
This guide assumes familiarity with the following concepts:
* [Human-in-the-loop](../../concepts/human_in_the_loop.md)
* [LangGraph Glossary](../../concepts/low_level.md)
**Human-in-the-loop (HIL)** interactions are crucial for [agentic systems](../../concepts/agentic_concepts.md#human-in-the-loop). Waiting for human input is a common HIL interaction pattern, allowing the agent to ask the user clarifying questions and await input before proceeding.
We can implement this in LangGraph using the [`interrupt()`][langgraph.types.interrupt] function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input.
## Setup
We are not going to show the full code for the graph we are hosting, but you can see it [here](../../how-tos/human_in_the_loop/wait-user-input.ipynb#agent) if you want to. Once this graph is hosted, we are ready to invoke it and wait for user input.
### SDK initialization
First, we need to setup our client so that we can communicate with our hosted graph:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
# Using the graph deployed with the name "agent"
assistant_id = "agent"
thread = await client.threads.create()
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
// Using the graph deployed with the name "agent"
const assistantId = "agent";
const thread = await client.threads.create();
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
## Waiting for user input
### Initial invocation
Now, let's invoke our graph.
=== "Python"
```python
input = {
"messages": [
{
"role": "user",
"content": "Ask the user where they are, then look up the weather there",
}
]
}
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
input=input,
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const input = {
messages: [
{
role: "human",
content: "Ask the user where they are, then look up the weather there" }
]
};
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
input: input,
streamMode: "updates",
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Ask the user where they are, then look up the weather there\"}]},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'agent': {'messages': [{'content': [{'text': "I'll help you ask the user about their location and then search for weather information.", 'type': 'text'}, {'id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'input': {'question': 'Where are you located?'}, 'name': 'AskHuman', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01UBEdS6UvuFMetdokNsykVG', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 438, 'output_tokens': 76}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-1b1210d8-39e0-4607-9f0e-0ea932d28d5c-0', 'example': False, 'tool_calls': [{'name': 'AskHuman', 'args': {'question': 'Where are you located?'}, 'id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 438, 'output_tokens': 76, 'total_tokens': 514, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'__interrupt__': [{'value': 'Where are you located?', 'resumable': True, 'ns': ['ask_human:2d41f894-f297-211e-9bfe-1d162ecba54a'], 'when': 'during'}]}
You can see that our graph got interrupted inside the `ask_human` node, which is now waiting for a `location` to be provided.
### Providing human input
We can provide human input (`location`) by invoking the graph with a `Command(resume="<location>")`:
=== "Python"
```python
# highlight-next-line
from langgraph_sdk.schema import Command
async for chunk in client.runs.stream(
thread["thread_id"],
assistant_id,
# highlight-next-line
command=Command(resume="san francisco"),
stream_mode="updates",
):
if chunk.data and chunk.event != "metadata":
print(chunk.data)
```
=== "Javascript"
```js
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantId,
{
// highlight-next-line
command: { resume: "san francisco" },
streamMode: "updates"
}
);
for await (const chunk of streamResponse) {
if (chunk.data && chunk.event !== "metadata") {
console.log(chunk.data);
}
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data "{
\"assistant_id\": \"agent\",
\"command\": {
\"resume\": \"san francisco\"
},
\"stream_mode\": [
\"updates\"
]
}"
```
Output:
{'ask_human': {'messages': [{'tool_call_id': 'toolu_012JeNEvyePZFWK39d52Wdwi', 'type': 'tool', 'content': 'san francisco'}]}}
{'agent': {'messages': [{'content': [{'text': 'Let me search for the weather in San Francisco.', 'type': 'text'}, {'id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'input': {'query': 'current weather in san francisco'}, 'name': 'search', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_0152YFm7DtnzfZQuiMUzaSsw', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 527, 'output_tokens': 67}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-f509b5b2-eb30-4200-a8da-fa79ed68812a-0', 'example': False, 'tool_calls': [{'name': 'search', 'args': {'query': 'current weather in san francisco'}, 'id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 527, 'output_tokens': 67, 'total_tokens': 594, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
{'action': {'messages': [{'content': "I looked up: current weather in san francisco. Result: It's sunny in San Francisco, but you better look out if you're a Gemini 😈.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'search', 'id': 'cbd0f623-cc12-48a2-8c18-3cbb943e46e0', 'tool_call_id': 'toolu_019f9Y7ST6rNeDQkDjFCHk6C', 'artifact': None, 'status': 'success'}]}}
{'agent': {'messages': [{'content': "Based on the search results, it's currently sunny in San Francisco. Would you like any specific details about the weather forecast?", 'additional_kwargs': {}, 'response_metadata': {'id': 'msg_01FhzXj72CehBYkJGX69vsBc', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 639, 'output_tokens': 29}, 'model_name': 'claude-3-5-sonnet-20241022'}, 'type': 'ai', 'name': None, 'id': 'run-f48e818e-dd88-415e-9a0b-4a958498b553-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 639, 'output_tokens': 29, 'total_tokens': 668, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}}]}}
+1 -1
View File
@@ -1,6 +1,6 @@
# How to run multiple agents on the same thread
In LangGraph Platform, a thread is not explicitly associated with a particular agent.
In LangGraph Cloud, a thread is not explicitly associated with a particular agent.
This means that you can run multiple agents on the same thread, which allows a different agent to continue from an initial agent's progress.
In this example, we will create two agents and then call them both on the same thread.
+1 -1
View File
@@ -1,6 +1,6 @@
# Stateless Runs
Most of the time, you provide a `thread_id` to your client when you run your graph in order to keep track of prior runs through the persistent state implemented in LangGraph Platform. However, if you don't need to persist the runs you don't need to use the built in persistent state and can create stateless runs.
Most of the time, you provide a `thread_id` to your client when you run your graph in order to keep track of prior runs through the persistent state implemented in LangGraph Cloud. However, if you don't need to persist the runs you don't need to use the built in persistent state and can create stateless runs.
## Setup
-491
View File
@@ -1,491 +0,0 @@
# How to use threads
!!! info "Prerequisites"
- [Threads Overview](../concepts/threads.md)
In this guide, we will show how to create, view, and inspect threads.
## Create a thread
To run your graph and the state persisted, you must first create a thread.
### Empty thread
To create a new thread, use the [LangGraph SDK](../../concepts/sdk.md) `create` method. See the [Python](../reference/sdk/python_sdk_ref.md#langgraph_sdk.client.ThreadsClient.create) and [JS](../reference/sdk/js_ts_sdk_ref.md#create_3) SDK reference docs for more information.
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
thread = await client.threads.create()
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const thread = await client.threads.create();
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Output:
{
"thread_id": "123e4567-e89b-12d3-a456-426614174000",
"created_at": "2025-05-12T14:04:08.268Z",
"updated_at": "2025-05-12T14:04:08.268Z",
"metadata": {},
"status": "idle",
"values": {}
}
### Copy thread
Alternatively, if you already have a thread in your application whose state you wish to copy, you can use the `copy` method. This will create an independent thread whose history is identical to the original thread at the time of the operation. See the [Python](../reference/sdk/python_sdk_ref.md#langgraph_sdk.client.ThreadsClient.copy) and [JS](../reference/sdk/js_ts_sdk_ref.md#copy) SDK reference docs for more information.
=== "Python"
```python
copied_thread = await client.threads.copy(<THREAD_ID>)
```
=== "Javascript"
```js
const copiedThread = await client.threads.copy(<THREAD_ID>);
```
=== "CURL"
```bash
curl --request POST --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/copy \
--header 'Content-Type: application/json'
```
### Prepopulated State
Finally, you can create a thread with an arbitrary pre-defined state by providing a list of `supersteps` into the `create` method. The `supersteps` describe a list of a sequence of state updates. For example:
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
thread = await client.threads.create(
graph_id="agent",
supersteps=[
{
updates: [
{
values: {},
as_node: '__input__',
},
],
},
{
updates: [
{
values: {
messages: [
{
type: 'human',
content: 'hello',
},
],
},
as_node: '__start__',
},
],
},
{
updates: [
{
values: {
messages: [
{
content: 'Hello! How can I assist you today?',
type: 'ai',
},
],
},
as_node: 'call_model',
},
],
},
])
print(thread)
```
=== "Javascript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const thread = await client.threads.create({
graphId: 'agent',
supersteps: [
{
updates: [
{
values: {},
asNode: '__input__',
},
],
},
{
updates: [
{
values: {
messages: [
{
type: 'human',
content: 'hello',
},
],
},
asNode: '__start__',
},
],
},
{
updates: [
{
values: {
messages: [
{
content: 'Hello! How can I assist you today?',
type: 'ai',
},
],
},
asNode: 'call_model',
},
],
},
],
});
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{"metadata":{"graph_id":"agent"},"supersteps":[{"updates":[{"values":{},"as_node":"__input__"}]},{"updates":[{"values":{"messages":[{"type":"human","content":"hello"}]},"as_node":"__start__"}]},{"updates":[{"values":{"messages":[{"content":"Hello\u0021 How can I assist you today?","type":"ai"}]},"as_node":"call_model"}]}]}'
```
Output:
{
"thread_id": "f15d70a1-27d4-4793-a897-de5609920b7d",
"created_at": "2025-05-12T15:37:08.935038+00:00",
"updated_at": "2025-05-12T15:37:08.935046+00:00",
"metadata": {"graph_id": "agent"},
"status": "idle",
"config": {},
"values": {
"messages": [
{
"content": "hello",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": "8701f3be-959c-4b7c-852f-c2160699b4ab",
"example": false
},
{
"content": "Hello! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {},
"type": "ai",
"name": null,
"id": "4d8ea561-7ca1-409a-99f7-6b67af3e1aa3",
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
]
}
}
## List threads
### LangGraph SDK
To list threads, use the [LangGraph SDK](../../concepts/sdk.md) `search` method. This will list the threads in the application that match the provided filters. See the [Python](../reference/sdk/python_sdk_ref.md#langgraph_sdk.client.ThreadsClient.search) and [JS](../reference/sdk/js_ts_sdk_ref.md#search_2) SDK reference docs for more information.
#### Filter by thread status
Use the `status` field to filter threads based on their status. Supported values are `idle`, `busy`, `interrupted`, and `error`. See [here](../reference/sdk/python_sdk_ref.md/?h=thread+status#langgraph_sdk.auth.types.ThreadStatus) for information on each status. For example, to view `idle` threads:
=== "Python"
```python
print(await client.threads.search(status="idle",limit=1))
```
=== "Javascript"
```js
console.log(await client.threads.search({ status: "idle", limit: 1 }));
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"status": "idle", "limit": 1}'
```
Output:
[
{
'thread_id': 'cacf79bb-4248-4d01-aabc-938dbd60ed2c',
'created_at': '2024-08-14T17:36:38.921660+00:00',
'updated_at': '2024-08-14T17:36:38.921660+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'idle',
'config': {'configurable': {}}
}
]
#### Filter by metadata
The `search` method allows you to filter on metadata:
=== "Python"
```python
print((await client.threads.search(metadata={"graph_id":"agent"},limit=1)))
```
=== "Javascript"
```js
console.log((await client.threads.search({ metadata: { "graph_id": "agent" }, limit: 1 })));
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/search \
--header 'Content-Type: application/json' \
--data '{"metadata": {"graph_id":"agent"}, "limit": 1}'
```
Output:
[
{
'thread_id': 'cacf79bb-4248-4d01-aabc-938dbd60ed2c',
'created_at': '2024-08-14T17:36:38.921660+00:00',
'updated_at': '2024-08-14T17:36:38.921660+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'idle',
'config': {'configurable': {}}
}
]
#### Sorting
The SDK also supports sorting threads by `thread_id`, `status`, `created_at`, and `updated_at` using the `sort_by` and `sort_order` params.
### LangGraph Platform UI
You can also view threads in a deployment via the LangGraph Platform UI.
Inside your deployment, select the "Threads" tab. This will load a table of all of the threads in your deployment.
To filter by thread status, select a status in the top bar. To sort by a supported property, click on the arrow icon for the desired column.
## Inspect threads
### LangGraph SDK
#### Get Thread
To view a specific thread given its `thread_id`, use the `get` method:
=== "Python"
```python
print((await client.threads.get(<THREAD_ID>)))
```
=== "Javascript"
```js
console.log((await client.threads.get(<THREAD_ID>)));
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID> \
--header 'Content-Type: application/json'
```
Output:
{
'thread_id': 'cacf79bb-4248-4d01-aabc-938dbd60ed2c',
'created_at': '2024-08-14T17:36:38.921660+00:00',
'updated_at': '2024-08-14T17:36:38.921660+00:00',
'metadata': {'graph_id': 'agent'},
'status': 'idle',
'config': {'configurable': {}}
}
#### Inspect Thread State
To view the current state of a given thread, use the `get_state` method:
=== "Python"
```python
print((await client.threads.get_state(<THREAD_ID>)))
```
=== "Javascript"
```js
console.log((await client.threads.getState(<THREAD_ID>)));
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state \
--header 'Content-Type: application/json'
```
Output:
{
"values": {
"messages": [
{
"content": "hello",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": "8701f3be-959c-4b7c-852f-c2160699b4ab",
"example": false
},
{
"content": "Hello! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {},
"type": "ai",
"name": null,
"id": "4d8ea561-7ca1-409a-99f7-6b67af3e1aa3",
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
]
},
"next": [],
"tasks": [],
"metadata": {
"thread_id": "f15d70a1-27d4-4793-a897-de5609920b7d",
"checkpoint_id": "1f02f46f-7308-616c-8000-1b158a9a6955",
"graph_id": "agent_with_quite_a_long_name",
"source": "update",
"step": 1,
"writes": {
"call_model": {
"messages": [
{
"content": "Hello! How can I assist you today?",
"type": "ai"
}
]
}
},
"parents": {}
},
"created_at": "2025-05-12T15:37:09.008055+00:00",
"checkpoint": {
"checkpoint_id": "1f02f46f-733f-6b58-8001-ea90dcabb1bd",
"thread_id": "f15d70a1-27d4-4793-a897-de5609920b7d",
"checkpoint_ns": ""
},
"parent_checkpoint": {
"checkpoint_id": "1f02f46f-7308-616c-8000-1b158a9a6955",
"thread_id": "f15d70a1-27d4-4793-a897-de5609920b7d",
"checkpoint_ns": ""
},
"checkpoint_id": "1f02f46f-733f-6b58-8001-ea90dcabb1bd",
"parent_checkpoint_id": "1f02f46f-7308-616c-8000-1b158a9a6955"
}
Optionally, to view the state of a thread at a given checkpoint, simply pass in the checkpoint id (or the entire checkpoint object):
=== "Python"
```python
thread_state = await client.threads.get_state(
thread_id=<THREAD_ID>
checkpoint_id=<CHECKPOINT_ID>
)
```
=== "Javascript"
```js
const threadState = await client.threads.getState(<THREAD_ID>, <CHECKPOINT_ID>);
```
=== "CURL"
```bash
curl --request GET \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/state/<CHECKPOINT_ID> \
--header 'Content-Type: application/json'
```
#### Inspect Full Thread History
To view a thread's history, use the `get_history` method. This returns a list of every state the thread experienced. For more information see the [Python](../reference/sdk/python_sdk_ref.md/#langgraph_sdk.client.ThreadsClient.get_history) and [JS](../reference/sdk/js_ts_sdk_ref.md/#gethistory) reference docs.
### LangGraph Platform UI
You can also view threads in a deployment via the LangGraph Platform UI.
Inside your deployment, select the "Threads" tab. This will load a table of all of the threads in your deployment.
Select a thread to inspect its current state. To view it's full history and for further debugging, open the thread in [LangGraph Studio](../../concepts//langgraph_studio.md).
+16 -13
View File
@@ -1,10 +1,10 @@
# Use webhooks
# Using Webhooks
When working with LangGraph Platform, you may want to use webhooks to receive updates after an API call completes. Webhooks are useful for triggering actions in your service once a run has finished processing. To implement this, you need to expose an endpoint that can accept `POST` requests and pass this endpoint as a `webhook` parameter in your API request.
When working with LangGraph Cloud, you may want to use webhooks to receive updates after an API call completes. Webhooks are useful for triggering actions in your service once a run has finished processing. To implement this, you need to expose an endpoint that can accept `POST` requests and pass this endpoint as a `webhook` parameter in your API request.
Currently, the SDK does not provide built-in support for defining webhook endpoints, but you can specify them manually using API requests.
## Supported endpoints
## Supported Endpoints
The following API endpoints accept a `webhook` parameter:
@@ -20,7 +20,7 @@ The following API endpoints accept a `webhook` parameter:
In this guide, well show how to trigger a webhook after streaming a run.
## Set up your assistant and thread
## Setting Up Your Assistant and Thread
Before making API calls, set up your assistant and thread.
@@ -56,8 +56,7 @@ curl --request POST \
--data '{}'
```
Example response:
### Example Response
```json
{
"thread_id": "9dde5490-2b67-47c8-aa14-4bfec88af217",
@@ -70,9 +69,9 @@ Example response:
}
```
## Use a webhook with a graph run
## Using a Webhook with a Graph Run
To use a webhook, specify the `webhook` parameter in your API request. When the run completes, LangGraph Platform sends a `POST` request to the specified webhook URL.
To use a webhook, specify the `webhook` parameter in your API request. When the run completes, LangGraph Cloud sends a `POST` request to the specified webhook URL.
For example, if your server listens for webhook events at `https://my-server.app/my-webhook-endpoint`, include this in your request:
@@ -120,11 +119,11 @@ curl --request POST \
}'
```
## Webhook payload
## Webhook Payload
LangGraph Platform sends webhook notifications in the format of a [Run](../../cloud/concepts/runs.md). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field.
LangGraph Cloud sends webhook notifications in the format of a [Run](../../cloud/concepts/runs.md). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field.
## Secure webhooks
## Securing Webhooks
To ensure only authorized requests hit your webhook endpoint, consider adding a security token as a query parameter:
@@ -134,11 +133,15 @@ https://my-server.app/my-webhook-endpoint?token=YOUR_SECRET_TOKEN
Your server should extract and validate this token before processing requests.
## Test webhooks
## Testing Webhooks
You can test your webhook using online services like:
- **[Beeceptor](https://beeceptor.com/)** Quickly create a test endpoint and inspect incoming webhook payloads.
- **[Webhook.site](https://webhook.site/)** View, debug, and log incoming webhook requests in real time.
These tools help you verify that LangGraph Platform is correctly triggering and sending webhooks to your service.
These tools help you verify that LangGraph Cloud is correctly triggering and sending webhooks to your service.
---
By following these steps, you can integrate webhooks into your LangGraph Cloud workflow, automating actions based on completed runs.
+1 -1
View File
@@ -16,7 +16,7 @@ This quickstart uses the [pre-built Python ReAct agent template](https://github.
## 1. Create a repository on GitHub
To deploy a LangGraph application to **LangGraph Platform**, your application code must reside in a GitHub repository. Both public and private repositories are supported. For this quickstart, use the [pre-built Python ReAct agent template](https://github.com/langchain-ai/react-agent) for your application:
To deploy a LangGraph application to **LangGraph Cloud**, your application code must reside in a GitHub repository. Both public and private repositories are supported. For this quickstart, use the [pre-built Python ReAct agent template](https://github.com/langchain-ai/react-agent) for your application:
1. Go to the [ReAct Agent](https://github.com/langchain-ai/react-agent) repository.
1. Click the `Fork` button in the top right corner to fork the repository to your GitHub account.
+1 -1
View File
@@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>LangGraph Platform API Reference</title>
<title>LangGraph Cloud API Reference</title>
<meta charset="utf-8" />
<meta
name="viewport"
+2 -2
View File
@@ -1,12 +1,12 @@
# API Reference
The LangGraph Platform API reference is available with each deployment at the `/docs` URL path (e.g. `http://localhost:8124/docs`).
The LangGraph Cloud API reference is available with each deployment at the `/docs` URL path (e.g. `http://localhost:8124/docs`).
Click <a href="/langgraph/cloud/reference/api/api_ref.html" target="_blank">here</a> to view the API reference.
## Authentication
For deployments to LangGraph Platform, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Platform API. The value of the header should be set to a valid LangSmith API key for the organization where the API is deployed.
For deployments to LangGraph Cloud, authentication is required. Pass the `X-Api-Key` header with each request to the LangGraph Cloud API. The value of the header should be set to a valid LangSmith API key for the organization where the API is deployed.
Example `curl` command:
```shell
@@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>LangGraph Platform API Reference</title>
<title>LangGraph Cloud API Reference</title>
<meta charset="utf-8" />
<meta
name="viewport"
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -1,6 +1,6 @@
# LangGraph CLI
The LangGraph command line interface includes commands to build and run a LangGraph Platform API server locally in [Docker](https://www.docker.com/). For development and testing, you can use the CLI to deploy a local API server.
The LangGraph command line interface includes commands to build and run a LangGraph Cloud API server locally in [Docker](https://www.docker.com/). For development and testing, you can use the CLI to deploy a local API server.
## Installation
@@ -39,7 +39,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Platform API server. Dependencies can be one of the following: <ul><li>A single period (`"."`), which will look for local Python packages.</li><li>The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located.</br></br>For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.</li><li>A Python package name.</li></ul> |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: <ul><li>A single period (`"."`), which will look for local Python packages.</li><li>The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located.</br></br>For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.</li><li>A Python package name.</li></ul> |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
| <span style="white-space: nowrap;">`auth`</span> | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
| <span style="white-space: nowrap;">`base_image`</span> | Optional. Base image to use for the LangGraph API server. Defaults to `langchain/langgraph-api` or `langchain/langgraphjs-api`. Use this to pin your builds to a particular version of the langgraph API, such as `"langchain/langgraph-server:0.2"`. See https://hub.docker.com/r/langchain/langgraph-server/tags for more details. (added in `langgraph-cli==0.2.8`) |
@@ -336,7 +336,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "Python"
Build LangGraph Platform API server Docker image.
Build LangGraph Cloud API server Docker image.
**Usage**
@@ -350,13 +350,13 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Platform API server with locally built images. |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Cloud API server with locally built images. |
| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. |
| `--help` | | Display command documentation. |
=== "JS"
Build LangGraph Platform API server Docker image.
Build LangGraph Cloud API server Docker image.
**Usage**
@@ -379,7 +379,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "Python"
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use.
**Usage**
@@ -406,7 +406,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "JS"
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Platform closed beta. Requires a license key for production use.
Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use.
**Usage**
@@ -432,7 +432,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "Python"
Generate a Dockerfile for building a LangGraph Platform API server Docker image.
Generate a Dockerfile for building a LangGraph Cloud API server Docker image.
**Usage**
@@ -482,7 +482,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
=== "JS"
Generate a Dockerfile for building a LangGraph Platform API server Docker image.
Generate a Dockerfile for building a LangGraph Cloud API server Docker image.
**Usage**
+2 -2
View File
@@ -38,7 +38,7 @@ See <a href="https://docs.smith.langchain.com/how_to_guides/tracing/sample_trace
Type of authentication for the LangGraph Server deployment. Valid values: `langsmith`, `noop`.
For deployments to LangGraph Platform, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
For deployments to LangGraph Cloud, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
## `LANGSMITH_RUNS_ENDPOINTS`
@@ -118,7 +118,7 @@ Defaults to `''`.
## `REDIS_CLUSTER`
!!! info "Only Allowed in Self-Hosted Deployments"
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Cloud SaaS will provision a redis instance for you by default.
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
+2 -6
View File
@@ -11,7 +11,7 @@ Imagine a general-purpose writing agent built on a common graph architecture. Wh
![assistant versions](img/assistants.png)
## Configuring assistants
## Configuring Assistants
Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration).
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md).
@@ -19,11 +19,7 @@ This is due to the fact that Assistants are tightly coupled to your deployed gra
In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants.
## Versioning assistants
## Versioning Assistants
Assistants support versioning to track changes over time.
Once you've created an assistant, subsequent edits to that assistant will create new versions. See [this how-to](../cloud/how-tos/assistant_versioning.md) for more details on how to manage assistant versions.
## Learn more
* The LangGraph Cloud API provides several endpoints for creating and managing assistants their versions. See the [API reference](../../cloud/reference/api/api_ref.html#tag/assistants) for more details.
+2 -2
View File
@@ -22,14 +22,14 @@ In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](
LangGraph Platform provides different security defaults:
### LangGraph Platform
### LangGraph Cloud
- Uses LangSmith API keys by default
- Requires valid API key in `x-api-key` header
- Can be customized with your auth handler
!!! note "Custom auth"
Custom auth **is supported** for all plans in LangGraph Platform.
Custom auth **is supported** for all plans in LangGraph Cloud.
### Self-Hosted
@@ -534,11 +534,11 @@
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "LangGraph Platform Deployment",
"text": "LangGraph Cloud Deployment",
"textAlign": "center",
"verticalAlign": "top",
"containerId": null,
"originalText": "LangGraph Platform Deployment",
"originalText": "LangGraph Cloud Deployment",
"autoResize": true,
"lineHeight": 1.25
},
+2 -2
View File
@@ -58,11 +58,11 @@ LangGraph Server leverages a database for [persistence](persistence.md) and a ta
Currently, only [Postgres](https://www.postgresql.org/) is supported as a database for LangGraph Server and [Redis](https://redis.io/) as the task queue.
If you're deploying using [LangGraph Platform](./langgraph_cloud.md), these components are managed for you. If you're deploying LangGraph Server on your own infrastructure, you'll need to set up and manage these components yourself.
If you're deploying using [LangGraph Cloud](./langgraph_cloud.md), these components are managed for you. If you're deploying LangGraph Server on your own infrastructure, you'll need to set up and manage these components yourself.
Please review the [deployment options](./deployment_options.md) guide for more information on how these components are set up and managed.
## Learn more
* LangGraph [Application Structure](./application_structure.md) guide explains how to structure your LangGraph application for deployment.
* The [LangGraph Platform API Reference](../cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models.
* The [LangGraph Cloud API Reference](../cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models.
+2 -2
View File
@@ -428,7 +428,7 @@ for update in graph.stream(
print(update)
```
When we use the LangGraph Platform, either locally (e.g., in LangGraph Studio) or with LangGraph Platform, the base store is available to use by default and does not need to be specified during graph compilation. To enable semantic search, however, you **do** need to configure the indexing settings in your `langgraph.json` file. For example:
When we use the LangGraph Platform, either locally (e.g., in LangGraph Studio) or with LangGraph Cloud, the base store is available to use by default and does not need to be specified during graph compilation. To enable semantic search, however, you **do** need to configure the indexing settings in your `langgraph.json` file. For example:
```json
{
@@ -451,7 +451,7 @@ Under the hood, checkpointing is powered by checkpointer objects that conform to
* `langgraph-checkpoint`: The base interface for checkpointer savers ([BaseCheckpointSaver][langgraph.checkpoint.base.BaseCheckpointSaver]) and serialization/deserialization interface ([SerializerProtocol][langgraph.checkpoint.serde.base.SerializerProtocol]). Includes in-memory checkpointer implementation ([InMemorySaver][langgraph.checkpoint.memory.InMemorySaver]) for experimentation. LangGraph comes with `langgraph-checkpoint` included.
* `langgraph-checkpoint-sqlite`: An implementation of LangGraph checkpointer that uses SQLite database ([SqliteSaver][langgraph.checkpoint.sqlite.SqliteSaver] / [AsyncSqliteSaver][langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver]). Ideal for experimentation and local workflows. Needs to be installed separately.
* `langgraph-checkpoint-postgres`: An advanced checkpointer that uses Postgres database ([PostgresSaver][langgraph.checkpoint.postgres.PostgresSaver] / [AsyncPostgresSaver][langgraph.checkpoint.postgres.aio.AsyncPostgresSaver]), used in LangGraph Platform. Ideal for using in production. Needs to be installed separately.
* `langgraph-checkpoint-postgres`: An advanced checkpointer that uses Postgres database ([PostgresSaver][langgraph.checkpoint.postgres.PostgresSaver] / [AsyncPostgresSaver][langgraph.checkpoint.postgres.aio.AsyncPostgresSaver]), used in LangGraph Cloud. Ideal for using in production. Needs to be installed separately.
### Checkpointer interface
+1 -1
View File
@@ -33,7 +33,7 @@ There are three different plans for using it.
| Publish/subscribe API for state | -- | Coming Soon! | Coming Soon! |
| Scheduling prioritization | -- | Coming Soon! | Coming Soon! |
For pricing information, see [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing).
Please see the [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) for information on pricing.
## Related
+1 -44
View File
@@ -162,50 +162,7 @@ Use an MCP-compliant client to connect to the LangGraph server. The following ex
=== "Python"
Install the adapter with:
```bash
pip install langchain-mcp-adapters
```
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
```python
# Create server parameters for stdio connection
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
import asyncio
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
server_params = {
"url": "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
"headers": {
"X-Api-Key":"lsv2_pt_your_api_key"
}
}
async def main():
async with streamablehttp_client(**server_params) as (read, write, _):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# Load the remote graph as if it was a tool
tools = await load_mcp_tools(session)
# Create and run a react agent with the tools
agent = create_react_agent("openai:gpt-4.1", tools)
# Invoke the agent with a message
agent_response = await agent.ainvoke({"messages": "What can the finance agent do for me?"})
print(agent_response)
if __name__ == "__main__":
asyncio.run(main())
```
No official MCP client is available for Python yet.
## Session behavior
+1 -1
View File
@@ -97,4 +97,4 @@ After configuring the app properly and adding your API keys, you can start the a
See the following guides for more information on how to deploy your app:
- **[Launch Local LangGraph Server](../tutorials/langgraph-platform/local-server.md)**: This quick start guide shows how to start a LangGraph Server locally for the **ReAct Agent** template. The steps are similar for other templates.
- **[Deploy to LangGraph Platform](../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Platform.
- **[Deploy to LangGraph Cloud](../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
+2 -2
View File
@@ -11,9 +11,9 @@
???+ note "Support by deployment type"
Custom auth is supported for all deployments in the **managed LangGraph Platform**, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
Custom auth is supported for all deployments in the **managed LangGraph Cloud**, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Platform and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Cloud and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
## 1. Implement authentication
+2 -2
View File
@@ -11,9 +11,9 @@ This guide applies to all LangGraph Platform deployments (Cloud and self-hosted)
The default security scheme varies by deployment type:
=== "LangGraph Platform"
=== "LangGraph Cloud"
By default, LangGraph Platform requires a LangSmith API key in the `x-api-key` header:
By default, LangGraph Cloud requires a LangSmith API key in the `x-api-key` header:
```yaml
components:
+7 -1
View File
@@ -3219,7 +3219,13 @@
"from IPython.display import Image, display\n",
"from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles\n",
"\n",
"display(Image(app.get_graph().draw_mermaid_png()))"
"display(\n",
" Image(\n",
" app.get_graph().draw_mermaid_png(\n",
" draw_method=MermaidDrawMethod.API,\n",
" )\n",
" )\n",
")"
]
},
{
+1 -1
View File
@@ -75,7 +75,7 @@ You should see your startup message printed when the server starts, and your cle
## Deploying
You can deploy your app as-is to LangGraph Platform or to your self-hosted platform.
You can deploy your app as-is to LangGraph Cloud or to your self-hosted platform.
## Next steps
+1 -1
View File
@@ -68,7 +68,7 @@ Now any request to your server will include the custom header `X-Custom-Header`
## Deploying
You can deploy this app as-is to LangGraph Platform or to your self-hosted platform.
You can deploy this app as-is to LangGraph Cloud or to your self-hosted platform.
## Next steps
+1 -1
View File
@@ -67,7 +67,7 @@ If you navigate to `localhost:2024/hello` in your browser (`2024` is the default
## Deploying
You can deploy this app as-is to LangGraph Platform or to your self-hosted platform.
You can deploy this app as-is to LangGraph Cloud or to your self-hosted platform.
## Next steps
@@ -22,15 +22,15 @@ The graph is resumed using a [`Command`](../reference/types.md#langgraph.types.C
from langgraph.types import interrupt, Command
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
}
)
return {
"some_text": value # (3)!
}
graph = graph_builder.compile(checkpointer=checkpointer) # (4)!
@@ -62,67 +62,67 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
??? example "Extended example: using `interrupt`"
```python
from typing import TypedDict
import uuid
```python
from typing import TypedDict
import uuid
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
# highlight-next-line
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
# highlight-next-line
from langgraph.types import interrupt, Command
class State(TypedDict):
some_text: str
class State(TypedDict):
some_text: str
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
def human_node(state: State):
# highlight-next-line
value = interrupt( # (1)!
{
"text_to_revise": state["some_text"] # (2)!
"text_to_revise": state["some_text"] # (2)!
}
)
return {
)
return {
"some_text": value # (3)!
}
}
# Build the graph
graph_builder = StateGraph(State)
graph_builder.add_node("human_node", human_node)
graph_builder.add_edge(START, "human_node")
# Build the graph
graph_builder = StateGraph(State)
graph_builder.add_node("human_node", human_node)
graph_builder.add_edge(START, "human_node")
checkpointer = InMemorySaver() # (4)!
checkpointer = InMemorySaver() # (4)!
graph = graph_builder.compile(checkpointer=checkpointer)
graph = graph_builder.compile(checkpointer=checkpointer)
# Pass a thread ID to the graph to run it.
config = {"configurable": {"thread_id": uuid.uuid4()}}
# Pass a thread ID to the graph to run it.
config = {"configurable": {"thread_id": uuid.uuid4()}}
# Run the graph until the interrupt is hit.
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
# Run the graph until the interrupt is hit.
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
print(result['__interrupt__']) # (6)!
# > [
# > Interrupt(
# > value={'text_to_revise': 'original text'},
# > resumable=True,
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
# > )
# > ]
print(result['__interrupt__']) # (6)!
# > [
# > Interrupt(
# > value={'text_to_revise': 'original text'},
# > resumable=True,
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
# > )
# > ]
# highlight-next-line
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
# > {'some_text': 'Edited text'}
```
# highlight-next-line
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
# > {'some_text': 'Edited text'}
```
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
4. A checkpointer is required to persist graph state. In production, this should be durable (e.g., backed by a database).
5. The graph is invoked with some initial state.
6. When the graph hits the interrupt, it returns an `Interrupt` object with the payload and metadata.
7. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
1. `interrupt(...)` pauses execution at `human_node`, surfacing the given payload to a human.
2. Any JSON serializable value can be passed to the `interrupt` function. Here, a dict containing the text to revise.
3. Once resumed, the return value of `interrupt(...)` is the human-provided input, which is used to update the state.
4. A checkpointer is required to persist graph state. In production, this should be durable (e.g., backed by a database).
5. The graph is invoked with some initial state.
6. When the graph hits the interrupt, it returns an `Interrupt` object with the payload and metadata.
7. The graph is resumed with a `Command(resume=...)`, injecting the human's input and continuing execution.
@@ -657,120 +657,120 @@ def node_in_parent_graph(state: State):
??? example "Extended example: parent and subgraph execution flow"
Say we have a parent graph with 3 nodes:
Say we have a parent graph with 3 nodes:
**Parent Graph**: `node_1``node_2` (subgraph call) → `node_3`
**Parent Graph**: `node_1``node_2` (subgraph call) → `node_3`
And the subgraph has 3 nodes, where the second node contains an `interrupt`:
And the subgraph has 3 nodes, where the second node contains an `interrupt`:
**Subgraph**: `sub_node_1``sub_node_2` (`interrupt`) → `sub_node_3`
**Subgraph**: `sub_node_1``sub_node_2` (`interrupt`) → `sub_node_3`
When resuming the graph, the execution will proceed as follows:
When resuming the graph, the execution will proceed as follows:
1. **Skip `node_1`** in the parent graph (already executed, graph state was saved in snapshot).
2. **Re-execute `node_2`** in the parent graph from the start.
3. **Skip `sub_node_1`** in the subgraph (already executed, graph state was saved in snapshot).
4. **Re-execute `sub_node_2`** in the subgraph from the beginning.
5. Continue with `sub_node_3` and subsequent nodes.
1. **Skip `node_1`** in the parent graph (already executed, graph state was saved in snapshot).
2. **Re-execute `node_2`** in the parent graph from the start.
3. **Skip `sub_node_1`** in the subgraph (already executed, graph state was saved in snapshot).
4. **Re-execute `sub_node_2`** in the subgraph from the beginning.
5. Continue with `sub_node_3` and subsequent nodes.
Here is abbreviated example code that you can use to understand how subgraphs work with interrupts.
It counts the number of times each node is entered and prints the count.
Here is abbreviated example code that you can use to understand how subgraphs work with interrupts.
It counts the number of times each node is entered and prints the count.
```python
import uuid
from typing import TypedDict
```python
import uuid
from typing import TypedDict
from langgraph.graph import StateGraph
from langgraph.constants import START
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
from langgraph.constants import START
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
class State(TypedDict):
"""The graph state."""
state_counter: int
class State(TypedDict):
"""The graph state."""
state_counter: int
counter_node_in_subgraph = 0
counter_node_in_subgraph = 0
def node_in_subgraph(state: State):
"""A node in the sub-graph."""
global counter_node_in_subgraph
counter_node_in_subgraph += 1 # This code will **NOT** run again!
print(f"Entered `node_in_subgraph` a total of {counter_node_in_subgraph} times")
def node_in_subgraph(state: State):
"""A node in the sub-graph."""
global counter_node_in_subgraph
counter_node_in_subgraph += 1 # This code will **NOT** run again!
print(f"Entered `node_in_subgraph` a total of {counter_node_in_subgraph} times")
counter_human_node = 0
counter_human_node = 0
def human_node(state: State):
global counter_human_node
counter_human_node += 1 # This code will run again!
print(f"Entered human_node in sub-graph a total of {counter_human_node} times")
answer = interrupt("what is your name?")
print(f"Got an answer of {answer}")
def human_node(state: State):
global counter_human_node
counter_human_node += 1 # This code will run again!
print(f"Entered human_node in sub-graph a total of {counter_human_node} times")
answer = interrupt("what is your name?")
print(f"Got an answer of {answer}")
checkpointer = MemorySaver()
checkpointer = MemorySaver()
subgraph_builder = StateGraph(State)
subgraph_builder.add_node("some_node", node_in_subgraph)
subgraph_builder.add_node("human_node", human_node)
subgraph_builder.add_edge(START, "some_node")
subgraph_builder.add_edge("some_node", "human_node")
subgraph = subgraph_builder.compile(checkpointer=checkpointer)
subgraph_builder = StateGraph(State)
subgraph_builder.add_node("some_node", node_in_subgraph)
subgraph_builder.add_node("human_node", human_node)
subgraph_builder.add_edge(START, "some_node")
subgraph_builder.add_edge("some_node", "human_node")
subgraph = subgraph_builder.compile(checkpointer=checkpointer)
counter_parent_node = 0
counter_parent_node = 0
def parent_node(state: State):
"""This parent node will invoke the subgraph."""
global counter_parent_node
def parent_node(state: State):
"""This parent node will invoke the subgraph."""
global counter_parent_node
counter_parent_node += 1 # This code will run again on resuming!
print(f"Entered `parent_node` a total of {counter_parent_node} times")
# Please note that we're intentionally incrementing the state counter
# in the graph state as well to demonstrate that the subgraph update
# of the same key will not conflict with the parent graph (until
subgraph_state = subgraph.invoke(state)
return subgraph_state
counter_parent_node += 1 # This code will run again on resuming!
print(f"Entered `parent_node` a total of {counter_parent_node} times")
# Please note that we're intentionally incrementing the state counter
# in the graph state as well to demonstrate that the subgraph update
# of the same key will not conflict with the parent graph (until
subgraph_state = subgraph.invoke(state)
return subgraph_state
builder = StateGraph(State)
builder.add_node("parent_node", parent_node)
builder.add_edge(START, "parent_node")
builder = StateGraph(State)
builder.add_node("parent_node", parent_node)
builder.add_edge(START, "parent_node")
# A checkpointer must be enabled for interrupts to work!
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# A checkpointer must be enabled for interrupts to work!
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {
"configurable": {
"thread_id": uuid.uuid4(),
}
}
config = {
"configurable": {
"thread_id": uuid.uuid4(),
}
}
for chunk in graph.stream({"state_counter": 1}, config):
print(chunk)
for chunk in graph.stream({"state_counter": 1}, config):
print(chunk)
print('--- Resuming ---')
print('--- Resuming ---')
for chunk in graph.stream(Command(resume="35"), config):
print(chunk)
```
for chunk in graph.stream(Command(resume="35"), config):
print(chunk)
```
This will print out
This will print out
```pycon
Entered `parent_node` a total of 1 times
Entered `node_in_subgraph` a total of 1 times
Entered human_node in sub-graph a total of 1 times
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:4c3a0248-21f0-1287-eacf-3002bc304db4', 'human_node:2fe86d52-6f70-2a3f-6b2f-b1eededd6348'], when='during'),)}
--- Resuming ---
Entered `parent_node` a total of 2 times
Entered human_node in sub-graph a total of 2 times
Got an answer of 35
{'parent_node': {'state_counter': 1}}
```
```pycon
Entered `parent_node` a total of 1 times
Entered `node_in_subgraph` a total of 1 times
Entered human_node in sub-graph a total of 1 times
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:4c3a0248-21f0-1287-eacf-3002bc304db4', 'human_node:2fe86d52-6f70-2a3f-6b2f-b1eededd6348'], when='during'),)}
--- Resuming ---
Entered `parent_node` a total of 2 times
Entered human_node in sub-graph a total of 2 times
Got an answer of 35
{'parent_node': {'state_counter': 1}}
```
@@ -10,12 +10,10 @@
"\n",
"To use time-travel in LangGraph:\n",
"\n",
"\n",
"1. **Run the graph** with initial inputs using `invoke` or `stream` APIs.\n",
"2. **Identify a checkpoint in an existing thread**: Use the [`get_state_history()`][langgraph.graph.graph.CompiledGraph.get_state_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`. \n",
"1. **Identify a checkpoint in an existing thread**: Use the [`get_state_history()`][langgraph.graph.graph.CompiledGraph.get_state_history] method to retrieve the execution history for a specific `thread_id` and locate the desired `checkpoint_id`. \n",
" Alternatively, set a [breakpoint](../../../concepts/breakpoints/) before the node(s) where you want execution to pause. You can then find the most recent checkpoint recorded up to that breakpoint.\n",
"3. **(Optional) modify the graph state**: Use the [`update_state`][langgraph.graph.graph.CompiledGraph.update_state] method to modify the graphs state at the checkpoint and resume execution from alternative state.\n",
"4. **Resume execution from the checkpoint**: Use the `invoke` or `stream` APIs with an input of `None` and a configuration containing the appropriate `thread_id` and `checkpoint_id`.\n",
"2. **(Optional) modify the graph state**: Use the [`update_state`][langgraph.graph.graph.CompiledGraph.update_state] method to modify the graphs state at the checkpoint and resume execution from alternative state.\n",
"3. **Resume execution from the checkpoint**: Use the `invoke` or `stream` APIs with an input of `None` and a configuration containing the appropriate `thread_id` and `checkpoint_id`.\n",
"\n",
"## Example\n",
"\n",
@@ -150,7 +148,7 @@
"id": "7ef76675-02e4-42ef-b707-0a9ce36ba0ac",
"metadata": {},
"source": [
"### 1. Run the graph"
"### Run the graph"
]
},
{
@@ -191,7 +189,7 @@
"id": "2fc0fc7f-5a7b-4b48-b1e2-d413ad4a51aa",
"metadata": {},
"source": [
"### 2. Identify a checkpoint"
"### 1. Identify a checkpoint"
]
},
{
@@ -256,7 +254,7 @@
"id": "59d5ecb8-1291-4131-83b6-c666862a09fc",
"metadata": {},
"source": [
"### 3. Update the state (optional)\n",
"### 2. (Optional) update the state\n",
"\n",
"`update_state` will create a new checkpoint. The new checkpoint will be associated with the same thread, but a new checkpoint ID."
]
@@ -285,7 +283,7 @@
"id": "db25cd11-7907-4c6f-9c97-b6b3458f246c",
"metadata": {},
"source": [
"### 4. Resume execution from the checkpoint"
"### 3. Resume execution from the checkpoint"
]
},
{
+35 -3
View File
@@ -23,6 +23,38 @@
"This allows the agent to keep track of the conversation without exceeding the LLM's context window."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
"metadata": {},
"outputs": [],
"source": [
"# hide-cell\n",
"%%capture --no-stderr\n",
"%pip install --quiet -U langgraph \"langchain[anthropic]\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
"metadata": {},
"outputs": [],
"source": [
"# hide-cell\n",
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "markdown",
"id": "db38b03c-5609-49e3-9b93-bad0aab47ffb",
@@ -393,9 +425,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "langgraph",
"language": "python",
"name": "python3"
"name": "langgraph"
},
"language_info": {
"codemirror_mode": {
@@ -407,7 +439,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
"version": "3.11.9"
}
},
"nbformat": 4,
@@ -237,7 +237,7 @@
"travel_advisor = create_react_agent(\n",
" model,\n",
" travel_advisor_tools,\n",
" prompt=(\n",
" state_modifier=(\n",
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
@@ -258,7 +258,7 @@
"hotel_advisor = create_react_agent(\n",
" model,\n",
" hotel_advisor_tools,\n",
" prompt=(\n",
" state_modifier=(\n",
" \"You are a hotel expert that can provide hotel recommendations for a given destination. \"\n",
" \"If you need help picking travel destinations, ask 'travel_advisor' for help.\"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
@@ -89,7 +89,7 @@
"metadata": {},
"outputs": [
{
"name": "stdout",
"name": "stdin",
"output_type": "stream",
"text": [
"ANTHROPIC_API_KEY: ········\n"
@@ -238,7 +238,7 @@
"travel_advisor = create_react_agent(\n",
" model,\n",
" travel_advisor_tools,\n",
" prompt=(\n",
" state_modifier=(\n",
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
@@ -259,7 +259,7 @@
"hotel_advisor = create_react_agent(\n",
" model,\n",
" hotel_advisor_tools,\n",
" prompt=(\n",
" state_modifier=(\n",
" \"You are a hotel expert that can provide hotel recommendations for a given destination. \"\n",
" \"If you need help picking travel destinations, ask 'travel_advisor' for help.\"\n",
" \"You MUST include human-readable response before transferring to another agent.\"\n",
+3 -3
View File
@@ -635,9 +635,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "langgraph",
"language": "python",
"name": "python3"
"name": "langgraph"
},
"language_info": {
"codemirror_mode": {
@@ -649,7 +649,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
"version": "3.11.9"
}
},
"nbformat": 4,
@@ -220,7 +220,7 @@
"source": [
"!!! note Note\n",
"\n",
" If you're using LangGraph Platform or LangGraph Studio, you __don't need__ to pass checkpointer to the entrypoint decorator, since it's done automatically."
" If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass checkpointer to the entrypoint decorator, since it's done automatically."
]
},
{
+4 -3
View File
@@ -31,6 +31,7 @@
"outputs": [],
"source": [
"# hide-cell\n",
"%%capture --no-stderr\n",
"%pip install --quiet -U langgraph \"langchain[anthropic]\""
]
},
@@ -1603,9 +1604,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "langgraph",
"language": "python",
"name": "python3"
"name": "langgraph"
},
"language_info": {
"codemirror_mode": {
@@ -1617,7 +1618,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
"version": "3.11.9"
}
},
"nbformat": 4,
@@ -0,0 +1,604 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
"metadata": {},
"source": [
"# How to use Postgres checkpointer for persistence\n",
"\n",
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Prerequisites</p>\n",
" <p>\n",
" This guide assumes familiarity with the following:\n",
" <ul>\n",
" <li>\n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/persistence/\">\n",
" Persistence\n",
" </a>\n",
" </li> \n",
" <li>\n",
" <a href=\"https://www.postgresql.org/about/\">\n",
" Postgresql\n",
" </a>\n",
" </li> \n",
" </ul>\n",
" </p>\n",
"</div> \n",
"\n",
"!!! info \"Not needed for LangGraph API users\"\n",
"\n",
" If you're using the LangGraph API, you needn't manually implement a checkpointer. The API automatically handles checkpointing for you. This guide is relevant when implementing LangGraph in your own custom server.\n",
"\n",
"When creating LangGraph agents, you can also set them up so that they persist their state. This allows you to do things like interact with an agent multiple times and have it remember previous interactions.\n",
"\n",
"This how-to guide shows how to use `Postgres` as the backend for persisting checkpoint state using the [`langgraph-checkpoint-postgres`](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres) library.\n",
"\n",
"For demonstration purposes we add persistence to the [pre-built create react agent](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent). \n",
"\n",
"In general, you can add a checkpointer to any custom graph that you build like this:\n",
"\n",
"```python\n",
"from langgraph.graph import StateGraph\n",
"\n",
"builder = StateGraph(....)\n",
"# ... define the graph\n",
"checkpointer = # postgres checkpointer (see examples below)\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
"...\n",
"```\n",
"\n",
"!!! info \"Setup\"\n",
"\n",
" You need to run `.setup()` once on your checkpointer to initialize the database before you can use it."
]
},
{
"cell_type": "markdown",
"id": "456fa19c-93a5-4750-a410-f2d810b964ad",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"You will need access to a postgres instance. There are many resources online that can help\n",
"you set up a postgres instance.\n",
"\n",
"Next, let's install the required packages and set our API keys"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "faadfb1b-cebe-4dcf-82fd-34044c380bc4",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U psycopg psycopg-pool langgraph langgraph-checkpoint-postgres"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "eca9aafb-a155-407a-8036-682a2f1297d7",
"metadata": {},
"outputs": [],
"source": [
"import getpass\n",
"import os\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"OPENAI_API_KEY\")"
]
},
{
"cell_type": "markdown",
"id": "b394e26c",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "e26b3204-cca2-414c-800e-7e09032445ae",
"metadata": {},
"source": [
"## Define model and tools for the graph"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e5213193-5a7d-43e7-aeba-fe732bb1cd7a",
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"\n",
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"from langgraph.prebuilt import create_react_agent\n",
"from langgraph.checkpoint.postgres import PostgresSaver\n",
"from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver\n",
"\n",
"\n",
"@tool\n",
"def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
" \"\"\"Use this to get weather information.\"\"\"\n",
" if city == \"nyc\":\n",
" return \"It might be cloudy in nyc\"\n",
" elif city == \"sf\":\n",
" return \"It's always sunny in sf\"\n",
" else:\n",
" raise AssertionError(\"Unknown city\")\n",
"\n",
"\n",
"tools = [get_weather]\n",
"model = ChatOpenAI(model_name=\"gpt-4o-mini\", temperature=0)"
]
},
{
"cell_type": "markdown",
"id": "e9342c62-dbb4-40f6-9271-7393f1ca48c4",
"metadata": {},
"source": [
"## Use sync connection\n",
"\n",
"This sets up a synchronous connection to the database. \n",
"\n",
"Synchronous connections execute operations in a blocking manner, meaning each operation waits for completion before moving to the next one. The `DB_URI` is the database connection URI, with the protocol used for connecting to a PostgreSQL database, authentication, and host where database is running. The connection_kwargs dictionary defines additional parameters for the database connection."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2b9d13b1-9d72-48a0-b63a-adc062c06c29",
"metadata": {},
"outputs": [],
"source": [
"DB_URI = \"postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable\""
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "3fe36f67-073a-4fd7-a8f8-da196dd46a0d",
"metadata": {},
"outputs": [],
"source": [
"connection_kwargs = {\n",
" \"autocommit\": True,\n",
" \"prepare_threshold\": 0,\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "e39fc712-9e1c-4831-9077-dd07b0c13594",
"metadata": {},
"source": [
"### With a connection pool\n",
"\n",
"This manages a pool of reusable database connections: \n",
"- Advantages: Efficient resource utilization, improved performance for frequent connections\n",
"- Best for: Applications with many short-lived database operations\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "bd235fc7-1e5c-4db6-a90b-ea75462ccf7d",
"metadata": {},
"outputs": [],
"source": [
"from psycopg_pool import ConnectionPool\n",
"\n",
"with ConnectionPool(\n",
" # Example configuration\n",
" conninfo=DB_URI,\n",
" max_size=20,\n",
" kwargs=connection_kwargs,\n",
") as pool:\n",
" checkpointer = PostgresSaver(pool)\n",
"\n",
" # NOTE: you need to call .setup() the first time you're using your checkpointer\n",
" checkpointer.setup()\n",
"\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
" res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)\n",
" checkpoint = checkpointer.get(config)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "a7e0e7ec-a675-470b-9270-e4bdc59d4a4d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content=\"what's the weather in sf\", id='735b7deb-b0fe-4ad5-8920-2a3c69bbe9f7'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_lJHMDYgfgRdiEAGfFsEhqqKV', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-c56b3e04-08a9-4a59-b3f5-ee52d0ef0656-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_lJHMDYgfgRdiEAGfFsEhqqKV', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
" ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='0644bf7b-4d1b-4ebe-afa1-d2169ccce582', tool_call_id='call_lJHMDYgfgRdiEAGfFsEhqqKV'),\n",
" AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-1ed9b8d0-9b50-4b87-b3a2-9860f51e9fd1-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"res"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "96efd8b2-97c9-4207-83b2-00131723a75a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'v': 1,\n",
" 'id': '1ef559b7-3b19-6ce8-8003-18d0f60634be',\n",
" 'ts': '2024-08-08T15:32:42.108605+00:00',\n",
" 'current_tasks': {},\n",
" 'pending_sends': [],\n",
" 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8',\n",
" 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'},\n",
" 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'},\n",
" '__input__': {},\n",
" '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}},\n",
" 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af',\n",
" 'tools': '00000000000000000000000000000005.',\n",
" 'messages': '00000000000000000000000000000005.b9adc75836c78af94af1d6811340dd13',\n",
" '__start__': '00000000000000000000000000000002.',\n",
" 'start:agent': '00000000000000000000000000000003.',\n",
" 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'},\n",
" 'channel_values': {'agent': 'agent',\n",
" 'messages': [HumanMessage(content=\"what's the weather in sf\", id='735b7deb-b0fe-4ad5-8920-2a3c69bbe9f7'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_lJHMDYgfgRdiEAGfFsEhqqKV', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-c56b3e04-08a9-4a59-b3f5-ee52d0ef0656-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_lJHMDYgfgRdiEAGfFsEhqqKV', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
" ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='0644bf7b-4d1b-4ebe-afa1-d2169ccce582', tool_call_id='call_lJHMDYgfgRdiEAGfFsEhqqKV'),\n",
" AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-1ed9b8d0-9b50-4b87-b3a2-9860f51e9fd1-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint"
]
},
{
"cell_type": "markdown",
"id": "967c95c7-e392-4819-bd71-f29e91c68df3",
"metadata": {},
"source": [
"### With a connection\n",
"\n",
"This creates a single, dedicated connection to the database:\n",
"- Advantages: Simple to use, suitable for longer transactions\n",
"- Best for: Applications with fewer, longer-lived database operations"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "180d6daf-8fa7-4608-bd2e-bfbf44ed5836",
"metadata": {},
"outputs": [],
"source": [
"from psycopg import Connection\n",
"\n",
"\n",
"with Connection.connect(DB_URI, **connection_kwargs) as conn:\n",
" checkpointer = PostgresSaver(conn)\n",
" # NOTE: you need to call .setup() the first time you're using your checkpointer\n",
" # checkpointer.setup()\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
" res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)\n",
"\n",
" checkpoint_tuple = checkpointer.get_tuple(config)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "613d0bbc-0e38-45c4-aace-1f6f7ae27c7b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4650-6bfc-8003-1c5488f19318'}}, checkpoint={'v': 1, 'id': '1ef559b7-4650-6bfc-8003-1c5488f19318', 'ts': '2024-08-08T15:32:43.284551+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}}, 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af', 'tools': '00000000000000000000000000000005.', 'messages': '00000000000000000000000000000005.af9f229d2c4e14f4866eb37f72ec39f6', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in sf\", id='7a14f96c-2d88-454f-9520-0e0287a4abbb'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_NcL4dBTYu4kSPGMKdxztdpjN', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-39adbf2c-36ef-40f6-9cad-8e1f8167fc19-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_NcL4dBTYu4kSPGMKdxztdpjN', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='c9f82354-3225-40a8-bf54-81f3e199043b', tool_call_id='call_NcL4dBTYu4kSPGMKdxztdpjN'), AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-83888be3-d681-42ca-ad67-e2f5ee8550de-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}, metadata={'step': 3, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 94, 'prompt_tokens': 84, 'completion_tokens': 10}, 'finish_reason': 'stop', 'system_fingerprint': 'fp_48196bc67a'}, id='run-83888be3-d681-42ca-ad67-e2f5ee8550de-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4087-681a-8002-88a5738f76f1'}}, pending_writes=[])"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint_tuple"
]
},
{
"cell_type": "markdown",
"id": "49fb52fd-af31-4603-889d-66d783244bce",
"metadata": {},
"source": [
"### With a connection string\n",
"\n",
"This creates a connection based on a connection string:\n",
"- Advantages: Simplicity, encapsulates connection details\n",
"- Best for: Quick setup or when connection details are provided as a string"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "5fe54e79-9eaf-44e2-b2d9-1e0284b984d0",
"metadata": {},
"outputs": [],
"source": [
"with PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
" res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)\n",
"\n",
" checkpoint_tuples = list(checkpointer.list(config))"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "b2ce743b-5896-443b-9ec0-a655b065895c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[CheckpointTuple(config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-5024-6476-8003-cf0a750e6b37'}}, checkpoint={'v': 1, 'id': '1ef559b7-5024-6476-8003-cf0a750e6b37', 'ts': '2024-08-08T15:32:44.314900+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}}, 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af', 'tools': '00000000000000000000000000000005.', 'messages': '00000000000000000000000000000005.3f8b8d9923575b911e17157008ab75ac', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in sf\", id='5bf79d15-6332-4bf5-89bd-ee192b31ed84'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2958adc7-f6a4-415d-ade1-5ee77e0b9276-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='cac4f90a-dc3e-4bfa-940f-1c630289a583', tool_call_id='call_9y3q1BiwW7zGh2gk2faInTRk'), AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-97d3fb7a-3d2e-4090-84f4-dafdfe44553f-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}, metadata={'step': 3, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 94, 'prompt_tokens': 84, 'completion_tokens': 10}, 'finish_reason': 'stop', 'system_fingerprint': 'fp_48196bc67a'}, id='run-97d3fb7a-3d2e-4090-84f4-dafdfe44553f-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}}, parent_config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4b3d-6430-8002-b5c99d2eb4db'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4b3d-6430-8002-b5c99d2eb4db'}}, checkpoint={'v': 1, 'id': '1ef559b7-4b3d-6430-8002-b5c99d2eb4db', 'ts': '2024-08-08T15:32:43.800857+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}}, 'channel_versions': {'agent': '00000000000000000000000000000004.', 'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'messages': '00000000000000000000000000000004.1195f50946feaedb0bae1fdbfadc806b', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'tools': 'tools', 'messages': [HumanMessage(content=\"what's the weather in sf\", id='5bf79d15-6332-4bf5-89bd-ee192b31ed84'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2958adc7-f6a4-415d-ade1-5ee77e0b9276-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='cac4f90a-dc3e-4bfa-940f-1c630289a583', tool_call_id='call_9y3q1BiwW7zGh2gk2faInTRk')]}}, metadata={'step': 2, 'source': 'loop', 'writes': {'tools': {'messages': [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='cac4f90a-dc3e-4bfa-940f-1c630289a583', tool_call_id='call_9y3q1BiwW7zGh2gk2faInTRk')]}}}, parent_config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4b30-6078-8001-eaf8c9bd8844'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-4b30-6078-8001-eaf8c9bd8844'}}, checkpoint={'v': 1, 'id': '1ef559b7-4b30-6078-8001-eaf8c9bd8844', 'ts': '2024-08-08T15:32:43.795440+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}}, 'channel_versions': {'agent': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af', 'messages': '00000000000000000000000000000003.bab5fb3a70876f600f5f2fd46945ce5f', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in sf\", id='5bf79d15-6332-4bf5-89bd-ee192b31ed84'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-2958adc7-f6a4-415d-ade1-5ee77e0b9276-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})], 'branch:agent:should_continue:tools': 'agent'}}, metadata={'step': 1, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': '{\"city\":\"sf\"}'}}]}, response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 71, 'prompt_tokens': 57, 'completion_tokens': 14}, 'finish_reason': 'tool_calls', 'system_fingerprint': 'fp_507c9469a1'}, id='run-2958adc7-f6a4-415d-ade1-5ee77e0b9276-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_9y3q1BiwW7zGh2gk2faInTRk', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})]}}}, parent_config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-46d7-6116-8000-8976b7c89a2f'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-46d7-6116-8000-8976b7c89a2f'}}, checkpoint={'v': 1, 'id': '1ef559b7-46d7-6116-8000-8976b7c89a2f', 'ts': '2024-08-08T15:32:43.339573+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}}, 'channel_versions': {'messages': '00000000000000000000000000000002.ba0c90d32863686481f7fe5eab9ecdf0', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='5bf79d15-6332-4bf5-89bd-ee192b31ed84')], 'start:agent': '__start__'}}, metadata={'step': 0, 'source': 'loop', 'writes': None}, parent_config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-46ce-6c64-bfff-ef7fe2663573'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '3', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-46ce-6c64-bfff-ef7fe2663573'}}, checkpoint={'v': 1, 'id': '1ef559b7-46ce-6c64-bfff-ef7fe2663573', 'ts': '2024-08-08T15:32:43.336188+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'__input__': {}}, 'channel_versions': {'__start__': '00000000000000000000000000000001.ab89befb52cc0e91e106ef7f500ea033'}, 'channel_values': {'__start__': {'messages': [['human', \"what's the weather in sf\"]]}}}, metadata={'step': -1, 'source': 'input', 'writes': {'messages': [['human', \"what's the weather in sf\"]]}}, parent_config=None, pending_writes=None)]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint_tuples"
]
},
{
"cell_type": "markdown",
"id": "c0a47d3e-e588-48fc-a5d4-2145dff17e77",
"metadata": {},
"source": [
"## Use async connection\n",
"\n",
"This sets up an asynchronous connection to the database. \n",
"\n",
"Async connections allow non-blocking database operations. This means other parts of your application can continue running while waiting for database operations to complete. It's particularly useful in high-concurrency scenarios or when dealing with I/O-bound operations."
]
},
{
"cell_type": "markdown",
"id": "ee6b6cf7-d8f7-4777-a48d-93b5855fe681",
"metadata": {},
"source": [
"### With a connection pool"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4faf6087-73cc-4957-9a4f-f3509a32a740",
"metadata": {},
"outputs": [],
"source": [
"from psycopg_pool import AsyncConnectionPool\n",
"\n",
"async with AsyncConnectionPool(\n",
" # Example configuration\n",
" conninfo=DB_URI,\n",
" max_size=20,\n",
" kwargs=connection_kwargs,\n",
") as pool:\n",
" checkpointer = AsyncPostgresSaver(pool)\n",
"\n",
" # NOTE: you need to call .setup() the first time you're using your checkpointer\n",
" await checkpointer.setup()\n",
"\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"4\"}}\n",
" res = await graph.ainvoke(\n",
" {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
" )\n",
"\n",
" checkpoint = await checkpointer.aget(config)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "e0c42044-4de6-4742-8e00-fe295d50c95a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'v': 1,\n",
" 'id': '1ef559b7-5cc9-6460-8003-8655824c0944',\n",
" 'ts': '2024-08-08T15:32:45.640793+00:00',\n",
" 'current_tasks': {},\n",
" 'pending_sends': [],\n",
" 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8',\n",
" 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'},\n",
" 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'},\n",
" '__input__': {},\n",
" '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}},\n",
" 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af',\n",
" 'tools': '00000000000000000000000000000005.',\n",
" 'messages': '00000000000000000000000000000005.d869fc7231619df0db74feed624efe41',\n",
" '__start__': '00000000000000000000000000000002.',\n",
" 'start:agent': '00000000000000000000000000000003.',\n",
" 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'},\n",
" 'channel_values': {'agent': 'agent',\n",
" 'messages': [HumanMessage(content=\"what's the weather in nyc\", id='d883b8a0-99de-486d-91a2-bcfa7f25dc05'),\n",
" AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_H6TAYfyd6AnaCrkQGs6Q2fVp', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6f542f84-ad73-444c-8ef7-b5ea75a2e09b-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_H6TAYfyd6AnaCrkQGs6Q2fVp', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}),\n",
" ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='c0e52254-77a4-4ea9-a2b7-61dd2d65ec68', tool_call_id='call_H6TAYfyd6AnaCrkQGs6Q2fVp'),\n",
" AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-977140d4-7582-40c3-b2b6-31b542c430a3-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint"
]
},
{
"cell_type": "markdown",
"id": "56552584-9eb8-40df-a6a0-44151018b509",
"metadata": {},
"source": [
"### With a connection"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "386b78bc-2f73-49ba-a2a4-47bce6fc49b7",
"metadata": {},
"outputs": [],
"source": [
"from psycopg import AsyncConnection\n",
"\n",
"async with await AsyncConnection.connect(DB_URI, **connection_kwargs) as conn:\n",
" checkpointer = AsyncPostgresSaver(conn)\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"5\"}}\n",
" res = await graph.ainvoke(\n",
" {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
" )\n",
" checkpoint_tuple = await checkpointer.aget_tuple(config)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "d1ed1344-c923-4a46-b04e-cc3646737d48",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"CheckpointTuple(config={'configurable': {'thread_id': '5', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-65b4-60ca-8003-1ef4b620559a'}}, checkpoint={'v': 1, 'id': '1ef559b7-65b4-60ca-8003-1ef4b620559a', 'ts': '2024-08-08T15:32:46.575814+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}}, 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af', 'tools': '00000000000000000000000000000005.', 'messages': '00000000000000000000000000000005.1557a6006d58f736d5cb2dd5c5f10111', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in nyc\", id='935e7732-b288-49bd-9ec2-1f7610cc38cb'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_94KtjtPmsiaj7T8yXvL7Ef31', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-790c929a-7982-49e7-af67-2cbe4a86373b-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_94KtjtPmsiaj7T8yXvL7Ef31', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='b2dc1073-abc4-4492-8982-434a7e32e445', tool_call_id='call_94KtjtPmsiaj7T8yXvL7Ef31'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-7e8a7f16-d8e1-457a-89f3-192102396449-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}, metadata={'step': 3, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 97, 'prompt_tokens': 88, 'completion_tokens': 9}, 'finish_reason': 'stop', 'system_fingerprint': 'fp_48196bc67a'}, id='run-7e8a7f16-d8e1-457a-89f3-192102396449-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}}, parent_config={'configurable': {'thread_id': '5', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-62ae-6128-8002-c04af82bcd41'}}, pending_writes=[])"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint_tuple"
]
},
{
"cell_type": "markdown",
"id": "2f7e486a-3e63-41d7-b84b-6743f0a5764c",
"metadata": {},
"source": [
"### With a connection string"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "6a39d1ff-ca37-4457-8b52-07d33b59c36e",
"metadata": {},
"outputs": [],
"source": [
"async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer:\n",
" graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
" config = {\"configurable\": {\"thread_id\": \"6\"}}\n",
" res = await graph.ainvoke(\n",
" {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
" )\n",
" checkpoint_tuples = [c async for c in checkpointer.alist(config)]"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "2b6d73ca-519e-45f7-90c2-1b8596624505",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[CheckpointTuple(config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-723c-67de-8003-63bd4eab35af'}}, checkpoint={'v': 1, 'id': '1ef559b7-723c-67de-8003-63bd4eab35af', 'ts': '2024-08-08T15:32:47.890003+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}}, 'channel_versions': {'agent': '00000000000000000000000000000005.065d90dd7f7cd091f0233855210bb2af', 'tools': '00000000000000000000000000000005.', 'messages': '00000000000000000000000000000005.b6fe2a26011590cfe8fd6a39151a9e92', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in nyc\", id='977ddb90-9991-44cb-9f73-361c6dd21396'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-47b10c48-4db3-46d8-b4fa-e021818e01c5-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='798c520f-4f9a-4f6d-a389-da721eb4d4ce', tool_call_id='call_QIFCuh4zfP9owpjToycJiZf7'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-4a34e05d-8bcf-41ad-adc3-715919fde64c-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}, metadata={'step': 3, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 97, 'prompt_tokens': 88, 'completion_tokens': 9}, 'finish_reason': 'stop', 'system_fingerprint': 'fp_48196bc67a'}, id='run-4a34e05d-8bcf-41ad-adc3-715919fde64c-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}}, parent_config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6bf5-63c6-8002-ed990dbbc96e'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6bf5-63c6-8002-ed990dbbc96e'}}, checkpoint={'v': 1, 'id': '1ef559b7-6bf5-63c6-8002-ed990dbbc96e', 'ts': '2024-08-08T15:32:47.231667+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'tools': {'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}}, 'channel_versions': {'agent': '00000000000000000000000000000004.', 'tools': '00000000000000000000000000000004.022986cd20ae85c77ea298a383f69ba8', 'messages': '00000000000000000000000000000004.c9074f2a41f05486b5efb86353dc75c0', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000004.'}, 'channel_values': {'tools': 'tools', 'messages': [HumanMessage(content=\"what's the weather in nyc\", id='977ddb90-9991-44cb-9f73-361c6dd21396'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-47b10c48-4db3-46d8-b4fa-e021818e01c5-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='798c520f-4f9a-4f6d-a389-da721eb4d4ce', tool_call_id='call_QIFCuh4zfP9owpjToycJiZf7')]}}, metadata={'step': 2, 'source': 'loop', 'writes': {'tools': {'messages': [ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='798c520f-4f9a-4f6d-a389-da721eb4d4ce', tool_call_id='call_QIFCuh4zfP9owpjToycJiZf7')]}}}, parent_config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6be0-6926-8001-1a8ce73baf9e'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6be0-6926-8001-1a8ce73baf9e'}}, checkpoint={'v': 1, 'id': '1ef559b7-6be0-6926-8001-1a8ce73baf9e', 'ts': '2024-08-08T15:32:47.223198+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'agent': {'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, '__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}}, 'channel_versions': {'agent': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af', 'messages': '00000000000000000000000000000003.097b5407d709b297591f1ef5d50c8368', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000003.', 'branch:agent:should_continue:tools': '00000000000000000000000000000003.065d90dd7f7cd091f0233855210bb2af'}, 'channel_values': {'agent': 'agent', 'messages': [HumanMessage(content=\"what's the weather in nyc\", id='977ddb90-9991-44cb-9f73-361c6dd21396'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-47b10c48-4db3-46d8-b4fa-e021818e01c5-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})], 'branch:agent:should_continue:tools': 'agent'}}, metadata={'step': 1, 'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': '{\"city\":\"nyc\"}'}}]}, response_metadata={'logprobs': None, 'model_name': 'gpt-4o-mini-2024-07-18', 'token_usage': {'total_tokens': 73, 'prompt_tokens': 58, 'completion_tokens': 15}, 'finish_reason': 'tool_calls', 'system_fingerprint': 'fp_48196bc67a'}, id='run-47b10c48-4db3-46d8-b4fa-e021818e01c5-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_QIFCuh4zfP9owpjToycJiZf7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})]}}}, parent_config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-663d-60b4-8000-10a8922bffbf'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-663d-60b4-8000-10a8922bffbf'}}, checkpoint={'v': 1, 'id': '1ef559b7-663d-60b4-8000-10a8922bffbf', 'ts': '2024-08-08T15:32:46.631935+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'__input__': {}, '__start__': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}}, 'channel_versions': {'messages': '00000000000000000000000000000002.2a79db8da664e437bdb25ea804457ca7', '__start__': '00000000000000000000000000000002.', 'start:agent': '00000000000000000000000000000002.d6f25946c3108fc12f27abbcf9b4cedc'}, 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='977ddb90-9991-44cb-9f73-361c6dd21396')], 'start:agent': '__start__'}}, metadata={'step': 0, 'source': 'loop', 'writes': None}, parent_config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6637-6d4e-bfff-6cecf690c3cb'}}, pending_writes=None),\n",
" CheckpointTuple(config={'configurable': {'thread_id': '6', 'checkpoint_ns': '', 'checkpoint_id': '1ef559b7-6637-6d4e-bfff-6cecf690c3cb'}}, checkpoint={'v': 1, 'id': '1ef559b7-6637-6d4e-bfff-6cecf690c3cb', 'ts': '2024-08-08T15:32:46.629806+00:00', 'current_tasks': {}, 'pending_sends': [], 'versions_seen': {'__input__': {}}, 'channel_versions': {'__start__': '00000000000000000000000000000001.0e148ae3debe753278387e84f786e863'}, 'channel_values': {'__start__': {'messages': [['human', \"what's the weather in nyc\"]]}}}, metadata={'step': -1, 'source': 'input', 'writes': {'messages': [['human', \"what's the weather in nyc\"]]}}, parent_config=None, pending_writes=None)]"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"checkpoint_tuples"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+2 -2
View File
@@ -15,7 +15,7 @@ This guide assumes basic familiarity with the following concepts:
!!! note
Custom auth is only available for LangGraph Platform SaaS deployments or Enterprise Self-Hosted deployments.
Custom auth is only available for LangGraph Cloud SaaS deployments or Enterprise Self-Hosted deployments.
## 1. Create your app
@@ -42,7 +42,7 @@ The server will start and open the studio in your browser:
> - 📚 API Docs: http://127.0.0.1:2024/docs
>
> This in-memory server is designed for development and testing.
> For production use, please use LangGraph Platform.
> For production use, please use LangGraph Cloud.
```
If you were to self-host this on the public internet, anyone could access it!
@@ -114,10 +114,10 @@ Tool Calls:
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://blog.langchain.dev/langgraph-cloud/", "content": "We also have a new stable release of LangGraph. By LangChain 6 min read Jun 27, 2024 (Oct '24) Edit: Since the launch of LangGraph Platform, we now have multiple deployment options alongside LangGraph Studio - which now fall under LangGraph Platform. LangGraph Platform is synonymous with our Cloud SaaS deployment option."}, {"url": "https://changelog.langchain.com/announcements/langgraph-cloud-deploy-at-scale-monitor-carefully-iterate-boldly", "content": "LangChain - Changelog | ☁ 🚀 LangGraph Platform: Deploy at scale, monitor LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain Changelog Sign up for our newsletter to stay up to date DATE: The LangChain Team LangGraph LangGraph Platform ☁ 🚀 LangGraph Platform: Deploy at scale, monitor carefully, iterate boldly DATE: June 27, 2024 AUTHOR: The LangChain Team LangGraph Platform is now in closed beta, offering scalable, fault-tolerant deployment for LangGraph agents. LangGraph Platform also includes a new playground-like studio for debugging agent failure modes and quick iteration: Join the waitlist today for LangGraph Platform. And to learn more, read our blog post announcement or check out our docs. Subscribe By clicking subscribe, you accept our privacy policy and terms and conditions."}]
[{"url": "https://blog.langchain.dev/langgraph-cloud/", "content": "We also have a new stable release of LangGraph. By LangChain 6 min read Jun 27, 2024 (Oct '24) Edit: Since the launch of LangGraph Cloud, we now have multiple deployment options alongside LangGraph Studio - which now fall under LangGraph Platform. LangGraph Cloud is synonymous with our Cloud SaaS deployment option."}, {"url": "https://changelog.langchain.com/announcements/langgraph-cloud-deploy-at-scale-monitor-carefully-iterate-boldly", "content": "LangChain - Changelog | ☁ 🚀 LangGraph Cloud: Deploy at scale, monitor LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain LangSmith LangGraph LangChain Changelog Sign up for our newsletter to stay up to date DATE: The LangChain Team LangGraph LangGraph Cloud ☁ 🚀 LangGraph Cloud: Deploy at scale, monitor carefully, iterate boldly DATE: June 27, 2024 AUTHOR: The LangChain Team LangGraph Cloud is now in closed beta, offering scalable, fault-tolerant deployment for LangGraph agents. LangGraph Cloud also includes a new playground-like studio for debugging agent failure modes and quick iteration: Join the waitlist today for LangGraph Cloud. And to learn more, read our blog post announcement or check out our docs. Subscribe By clicking subscribe, you accept our privacy policy and terms and conditions."}]
================================== Ai Message ==================================
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Platform was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Cloud was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
Tool Calls:
human_assistance (toolu_01JDQAV7nPqMkHHhNs3j3XoN)
Call ID: toolu_01JDQAV7nPqMkHHhNs3j3XoN
@@ -149,7 +149,7 @@ for event in events:
```
================================== Ai Message ==================================
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Platform was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
[{'text': "Based on the search results, it appears that LangGraph was already in existence before June 27, 2024, when LangGraph Cloud was announced. However, the search results don't provide a specific release date for the original LangGraph. \n\nGiven this information, I'll use the human_assistance tool to review and potentially provide more accurate information about LangGraph's initial release date.", 'type': 'text'}, {'id': 'toolu_01JDQAV7nPqMkHHhNs3j3XoN', 'input': {'name': 'Assistant', 'birthday': '2023-01-01'}, 'name': 'human_assistance', 'type': 'tool_use'}]
Tool Calls:
human_assistance (toolu_01JDQAV7nPqMkHHhNs3j3XoN)
Call ID: toolu_01JDQAV7nPqMkHHhNs3j3XoN
@@ -168,9 +168,9 @@ LangGraph was initially released on January 17, 2024. This information comes fro
To summarize:
1. LangGraph's original release date: January 17, 2024
2. LangGraph Platform announcement: June 27, 2024
2. LangGraph Cloud announcement: June 27, 2024
It's worth noting that LangGraph had been in development and use for some time before the LangGraph Platform announcement, but the official initial release of LangGraph itself was on January 17, 2024.
It's worth noting that LangGraph had been in development and use for some time before the LangGraph Cloud announcement, but the official initial release of LangGraph itself was on January 17, 2024.
```
Note that these fields are now reflected in the state:
@@ -105,7 +105,7 @@ Tool Calls:
================================= Tool Message =================================
Name: tavily_search_results_json
[{"url": "https://blockchain.news/news/langchain-new-features-upcoming-events-update", "content": "LangChain, a leading platform in the AI development space, has released its latest updates, showcasing new use cases and enhancements across its ecosystem. According to the LangChain Blog, the updates cover advancements in LangGraph Platform, LangSmith's self-improving evaluators, and revamped documentation for LangGraph."}, {"url": "https://blog.langchain.dev/langgraph-platform-announce/", "content": "With these learnings under our belt, we decided to couple some of our latest offerings under LangGraph Platform. LangGraph Platform today includes LangGraph Server, LangGraph Studio, plus the CLI and SDK. ... we added features in LangGraph Server to deliver on a few key value areas. Below, we'll focus on these aspects of LangGraph Platform."}]
[{"url": "https://blockchain.news/news/langchain-new-features-upcoming-events-update", "content": "LangChain, a leading platform in the AI development space, has released its latest updates, showcasing new use cases and enhancements across its ecosystem. According to the LangChain Blog, the updates cover advancements in LangGraph Cloud, LangSmith's self-improving evaluators, and revamped documentation for LangGraph."}, {"url": "https://blog.langchain.dev/langgraph-platform-announce/", "content": "With these learnings under our belt, we decided to couple some of our latest offerings under LangGraph Platform. LangGraph Platform today includes LangGraph Server, LangGraph Studio, plus the CLI and SDK. ... we added features in LangGraph Server to deliver on a few key value areas. Below, we'll focus on these aspects of LangGraph Platform."}]
================================== Ai Message ==================================
Thank you for your patience. I've found some recent information about LangGraph for you. Let me summarize the key points:
@@ -114,9 +114,9 @@ Thank you for your patience. I've found some recent information about LangGraph
2. Recent updates and features of LangGraph include:
a. LangGraph Platform: This seems to be a cloud-based version of LangGraph, though specific details weren't provided in the search results.
a. LangGraph Cloud: This seems to be a cloud-based version of LangGraph, though specific details weren't provided in the search results.
...
3. Keep an eye on LangGraph Platform developments, as cloud-based solutions often provide an easier starting point for learners.
3. Keep an eye on LangGraph Cloud developments, as cloud-based solutions often provide an easier starting point for learners.
4. Consider how LangGraph fits into the broader LangChain ecosystem, especially its interaction with tools like LangSmith.
Is there any specific aspect of LangGraph you'd like to know more about? I'd be happy to do a more focused search on particular features or use cases.
@@ -284,5 +284,5 @@ The graph resumed execution from the `action` node. You can tell this is the cas
Take your LangGraph journey further by exploring deployment and advanced features:
- **[LangGraph Server quickstart](../../tutorials/langgraph-platform/local-server.md)**: Launch a LangGraph server locally and interact with it using the REST API and LangGraph Studio Web UI.
- **[LangGraph Platform quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Platform.
- **[LangGraph Cloud quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
- **[LangGraph Platform concepts](../../concepts/langgraph_platform.md)**: Understand the foundational concepts of the LangGraph Platform.
+1 -1
View File
@@ -14,7 +14,7 @@ New to LangGraph or LLM app development? Read this material to get up and runnin
- [Common Workflows](workflows/index.md): Overview of the most common workflows using LLMs implemented with LangGraph.
- [LangGraph Server Quickstart](langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using REST API and LangGraph Studio Web UI.
- [LangGraph Template Quickstart](../concepts/template_applications.md): Start building with LangGraph Platform using a template application.
- [Deploy with LangGraph Platform Quickstart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Platform.
- [Deploy with LangGraph Cloud Quickstart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud.
## Use cases 🛠️ {#use-cases}
+11 -5
View File
@@ -194,9 +194,11 @@ nav:
- Assistants:
- Overview: concepts/assistants.md
- cloud/how-tos/configuration_cloud.md
- cloud/how-tos/assistant_versioning.md
- Threads:
- Overview: cloud/concepts/threads.md
- cloud/how-tos/use_threads.md
- cloud/how-tos/copy_threads.md
- cloud/how-tos/check_thread_status.md
- Runs:
- Overview: cloud/concepts/runs.md
- cloud/how-tos/background_run.md
@@ -207,10 +209,14 @@ nav:
- Streaming:
- Overview: cloud/concepts/streaming.md
- cloud/how-tos/streaming.md
- Human-in-the-loop: cloud/how-tos/add-human-in-the-loop.md
- Breakpoints: cloud/how-tos/human_in_the_loop_breakpoint.md
- Time travel: cloud/how-tos/human_in_the_loop_time_travel.md
- MCP: concepts/server-mcp.md
- Human-in-the-loop:
- cloud/how-tos/human_in_the_loop_breakpoint.md
- cloud/how-tos/human_in_the_loop_user_input.md
- cloud/how-tos/human_in_the_loop_edit_state.md
- cloud/how-tos/human_in_the_loop_time_travel.md
- cloud/how-tos/human_in_the_loop_review_tool_calls.md
- MCP:
- Overview: concepts/server-mcp.md
- Double-texting:
- Overview: concepts/double_texting.md
- cloud/how-tos/interrupt_concurrent.md
@@ -558,6 +558,6 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
else:
async with (
self.lock,
conn.cursor(binary=True, row_factory=dict_row) as cur,
conn.cursor(binary=True) as cur,
):
yield cur
+2 -2
View File
@@ -206,7 +206,7 @@ def up(
):
click.secho("Starting LangGraph API server...", fg="green")
click.secho(
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangGraph Platform closed beta.
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangGraph Cloud closed beta.
For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KEY.""",
)
with Runner() as runner, Progress(message="Pulling...") as set:
@@ -529,7 +529,7 @@ def dockerfile(
"\n",
"# LANGSMITH_API_KEY=your-api-key",
"\n",
"# Or if you have a LangGraph Platform license key, "
"# Or if you have a LangGraph Cloud license key, "
"then uncomment the following line: ",
"\n",
"# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key",
+1 -1
View File
@@ -538,7 +538,7 @@ ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_graphs/src/agent.py:graph"}}'
assert additional_contexts == {}
# node.js build used for LangGraph Platform
# node.js build used for LangGraph Cloud
def test_config_to_docker_nodejs():
graphs = {"agent": "./graphs/agent.js:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
+1 -2
View File
@@ -12,7 +12,6 @@
[![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph)
[![Open Issues](https://img.shields.io/github/issues-raw/langchain-ai/langgraph)](https://github.com/langchain-ai/langgraph/issues)
[![Docs](https://img.shields.io/badge/docs-latest-blue)](https://langchain-ai.github.io/langgraph/)
[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/langchain-ai/langgraph)](https://gitmcp.io/langchain-ai/langgraph)
Trusted by companies shaping the future of agents including Klarna, Replit, Elastic, and more LangGraph is a powerful low-level orchestration framework for building, managing, and deploying long-running, stateful agents.
@@ -81,4 +80,4 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
## Acknowledgements
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
+1 -1
View File
@@ -1069,7 +1069,7 @@ def _proc_input(
return MISSING
else:
raise RuntimeError(
f"Invalid channels type, expected list or dict, got {proc.channels}"
"Invalid channels type, expected list or dict, got {proc.channels}"
)
# If the process has a mapper, apply it to the value
+1 -1
View File
@@ -89,7 +89,7 @@ class RemoteGraph(PregelProtocol):
APIs that implement the LangGraph Server API specification.
For example, the `RemoteGraph` class can be used to call APIs from deployments
on LangGraph Platform.
on LangGraph Cloud.
`RemoteGraph` behaves the same way as a `Graph` and can be used directly as
a node in another `Graph`.
+125 -101
View File
@@ -2515,126 +2515,134 @@ files = [
[[package]]
name = "pydantic"
version = "2.9.2"
version = "2.11.4"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
{file = "pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb"},
{file = "pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d"},
]
markers = {dev = "python_version < \"4.0\""}
[package.dependencies]
annotated-types = ">=0.6.0"
pydantic-core = "2.23.4"
typing-extensions = [
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
]
pydantic-core = "2.33.2"
typing-extensions = ">=4.12.2"
typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""]
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
version = "2.23.4"
version = "2.33.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
{file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
{file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
{file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
{file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
{file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
{file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
{file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
{file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
{file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
{file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
{file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
{file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
{file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
{file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
{file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
{file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
{file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
{file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
{file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
{file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
{file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
{file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
{file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
{file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
{file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
{file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
{file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
{file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
{file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
{file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
{file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
{file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
{file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
{file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
{file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
{file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
{file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
{file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
{file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
{file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
{file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
{file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
]
markers = {dev = "python_version < \"4.0\""}
@@ -3635,6 +3643,22 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "typing-inspection"
version = "0.4.0"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
{file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
]
markers = {dev = "python_version < \"4.0\""}
[package.dependencies]
typing-extensions = ">=4.12.0"
[[package]]
name = "tzdata"
version = "2024.2"
+1 -1
View File
@@ -863,7 +863,7 @@ async def test_ainvoke():
assert result == {"messages": [{"type": "human", "content": "world"}]}
@pytest.mark.skip("Unskip this test to manually test the LangGraph Platform integration")
@pytest.mark.skip("Unskip this test to manually test the LangGraph Cloud integration")
@pytest.mark.anyio
async def test_langgraph_cloud_integration():
from langgraph_sdk.client import get_client, get_sync_client
+1 -1
View File
@@ -1,6 +1,6 @@
# LangGraph Python SDK
This repository contains the Python SDK for interacting with the LangGraph Platform REST API.
This repository contains the Python SDK for interacting with the LangGraph Cloud REST API.
## Quick Start
+2 -16
View File
@@ -10,7 +10,6 @@ document Store.
from __future__ import annotations
import asyncio
import functools
import logging
import os
import sys
@@ -23,7 +22,6 @@ from typing import (
Literal,
Optional,
Sequence,
Type,
Union,
overload,
)
@@ -164,7 +162,7 @@ def get_client(
transport: Optional[httpx.AsyncBaseTransport] = None
if url is None:
if os.environ.get("__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT") == "true":
transport = get_asgi_transport()(app=None, root_path="/noauth")
transport = httpx.ASGITransport(app=None, root_path="/noauth")
_registered_transports.append(transport)
url = "http://api"
else:
@@ -172,8 +170,7 @@ def get_client(
from langgraph_api.server import app # type: ignore
url = "http://api"
transport = get_asgi_transport()(app, root_path="/noauth")
transport = httpx.ASGITransport(app, root_path="/noauth")
except Exception:
url = "http://localhost:8123"
@@ -5385,17 +5382,6 @@ def configure_loopback_transports(app: Any) -> None:
transport.app = app
@functools.lru_cache(maxsize=1)
def get_asgi_transport() -> Type[httpx.ASGITransport]:
try:
from langgraph_api import asgi_transport
return asgi_transport.ASGITransport
except ImportError:
# Older versions of the server
return httpx.ASGITransport
TimeoutTypes = Union[
Optional[float],
tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.68"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"