mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-01 04:39:01 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed648f87bf |
@@ -12,7 +12,6 @@
|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://langchain-ai.github.io/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.
|
||||
@@ -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,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
|
||||
|
||||
|
||||
|
||||
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
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
|
||||
@@ -1 +0,0 @@
|
||||
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
|
||||
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
eNqNVWtsFFUU5hUxVIkkUkJEHUZsVXp3d3Zm225BcPuwLaQP2w0UDMG7d+7uDp2dO8y9U7pFTKxIDAZ1fpg0hqjIdhc3DbSBhBBApMEoURKD4cei+PrjDzUaNMZ3vbPd7cOWxySTzNzz+s75zjm3P9ODLaoRY+6QZjBsQcT4D3X6MxbeaWPK9qYTmMWJmmpv6wwfti0t93CcMZPWeL3Q1DzQYHGLmBryIJLw9kjeBKYUxjBNRYiazP2wW0zA3u2MdGODijWSz69UiEUVsebp3aJFdCzWiDbFllghIsJBGIwfxLWVQnN5QoiQiLinYkIPUqpRxqNOU27ShFoSWSm0aggLjAgJjJmQJLZHaCK7BAQNoVmIY910z7hchcn1U53OCL4rDlk5FRJJwYAJzHW3cdRExTqXIR3aKgYyCABKDAMzoEPGCyXuycQxVHk1v5yzJBUnlDkjMyp0DCKETQawgYiqGTHneKxPMysEFUddLxVCH2VqFrl+80Q42W6MTQB1rQcf7wU8c83QefkA0xKY2Mx5t7UtvL2xeVNDa3rctTMMTVPXEHTNvTs4xKFCXoAlTTxTnHWzB5wPgzknQ0Ww3vYkZ90QfB4l6PENTw2tQ447beblp6cKTIi6uR9Q6CgnPW58dKoOoc5gC0RtndNcQgvFnUFoJSqVaVlatuEm6mTq2meGKwgnw8keSfIER6Y5pkkDOYNRqFM8MsHEhEnW7/PLwFcJfNLJaa4xs5IAER7BOeQ7Wiygjo0YizuH/XLlEQtTkw8KfiHNzZhN+1OcUfzJR5lCd7/TtnGyH5an6jm7ztmwzSmWZKEFJgUeOiD4fDUBqUYOCo0t4aG6QpjwrDyNhC1o0CjnqqHYPBkUt41urGbrZm2YXNlkxhaPr2sJjQHNMG3eC/mJBPkjJ6343Cf32C31LZzgNXJjF23KbsOGYuaccPMFvgCQ5HAx66258tmseV/PgDhYnY+2+tb6kxALNuW3Y3NjiOJs5oXlWESXUm5YignNSVwpORgM3sLvzRAJs1n+j9Dx1FfdRHMqlePawk21b4gnWwANNNU5w7+3+ySprl1BXQ0762NdgdoqTa3WukIbOk5RZmmIu3Qb2SQWAxQjfqOwpJOr4NeEu4Qel6WAXMnRrBE0A+l803bakXriQqVrBNPCOoHqqUmgxIpBQ+vLj4kLILdKDlbKAVWOAByJqkAJVleBYNAvgYjfX60q1VKVolYe7tGgk5U8khAjJKbjYygKEERxDMaH2cnUb2kNtTTXDXWBDhIhnJAw5MQZxMDpTmzx/eFkkU5sla9tC6frngQdoS3OiaCMsT/ir47igKJEq6pAw+aO4eJYT4xtyt35+fvx+bRbEiN24d8HX75zTv6Zz9+xMfWV0Mb50j0vjm0YoJu2ZsVTjy564MwjpUuc18rQtqo1x6NlkW2/nfu4tGR/ZuFPf/WeWXJ1weK+3J8fXFcuX3v9Wfvaj5eie/4xn0uOhb5fNvz26bmxkobR0vO136xdFkotvTv1zKHv9i5fcWHFjtHolYPKaP2RlsX7/audZufeLz7rHQj/ffDiuhVrLbCv9tX0Unr+vu7+gfs3/7Kp4YDVmv7woYE3wUvXvy65A85b2HSxGyxY/8TPZ8WGBY2BWIk99vmBTy+3X1m9q2meYUdX7vt9/7rSu676/zg3+u37Jft+fWvnla8uXe994725bpLz5ygnFnmf4t//AT+mUOg=
|
||||
eNqNVVtMHFUYXnoleKnGSL01nG4qJHVnd2Z2C+z6oLBQ2yIXYZtKUcnZmcPOwMyc6cxZ2uVmhPqk0M5TE/WpLLtmxQKxalP1SUiqVmyaNkqjyIsxhgRjfCs2eGbZ5VJu3aeZ8//f/33//51/tjfZjgxTxlrOsKwRZECB0BfT6k0a6FQUmeRsQkVEwmK8rrYhNBg15KnnJUJ0M+DxQF12Q41IBtZlwS1g1dPOeVRkmjCCzHgYi7E7ObmdThWeaSa4DWmmMwA4lve5gDObRU+aOp0GVhB9ckZNZDhpVMBUikbsI0neD44WqSCMw85uF1jOhaYpm4TS3wc4IoNyHF4EBRUYFZEb1MgCAgQDFSECYji6HxzBp4EANXAUSEjR7TMaF2HspdUk6wg6LUFSZAI1BjSoIpr/pt0OFpFiR4U0I+NlDjEm1jREGAUSOkVnd1JCUKSjPheXsEmssTXDG4GCgHTCIE3AoqxFrE8jHbLuAiJqsWukBLte2h0r1YaQzkBFbkeJRZQ1CnVdkQVoxz2tlHs4o5khMR2tDafs1hhqgUasL8uyOjx1Meq1Bli31+fmR88wdMKyplCzaB9UUkJPx79aGdCh0EbrMJl7ZCUWwZdW5mDTGqqGQm3DqpLQECRrCBpqse+zledGVCOyiqxksG4tXSa4TOd1c5zbP7aqsBnTBGuoBSomGlsa8hIkxbO8l2GLGZa7lJ2SgrQIkaxBvtj3sYFMne4A6kvQkiRq9sapI+j6tWTm1l6srcq6Oe3YG6+g7ljfnECiC/A8OEZvFa1/CPBcgPUHOA68Uh0aDmZoQuuaMRYyoGa2UEMqs+YnBSmqtSExFVzX9innclsG5VdkVSZMZmWpWfarFfexLDtVuGmmgVQ6NZsx7vX7/VvUpZNBxLps90eHx/B8KNMle3J9HlnTo/QKprc/oyphq6K6Dm6Zv6wtiyl8AMwGCrmTU0XroXGUrJE4VJpme2Hr/GWJGUzRg2A2lgjWg983vkWiA5tkrhzcYjbYNHtDPamM84wsWl/T52aWa2wMHe84Vl7devzVGkmr9tbprWYJGmyXoZXi3ByIYBxR0EjwMBOEgoSYhvQKWcmKxpqy6qPB4deZehzG9C6FIL1zGtZQogEZdDWtlKDgqEg/dgZKUHh9WaN12c8W86XFLB+GXlQi8j6m8kT9aHaZlpYlbn8p038479CVNejRRE5OwXu5jvRvu3iurGr85YffXfj+/L+hOyc95huP7ZmdftLiJ8d/u6TWFz09ytz7++bEzomGmfnpzvM7r+Y+usv/Bfr87dLrt7u6uj6Y++mvy1Xeu9/27Hgo0H9lqG684HHXgbmSmcrAa7V5+bsGfunruzH/3NgVZi4POm9ddM8mDs4+49pRWFmve36Ihv5p+6R5oC8y+d35ghsk3P5fS/9e76niP12tgZvj3O73n63qvTf/yPBIfkVMeaIDbtt9ZOz2R7k9Z2cih/NyB4zfm65d8Py6bV9/Y9Mfew7k/Hwrv3dPkRIsH9nXMF9S91Z3ecHCi5N3Pwz9eLVnu8OxsLDdIZY/lWrOcTj+B1mCCwM=
|
||||
+1
-1
@@ -1 +1 @@
|
||||
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
|
||||
+1
-1
@@ -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==
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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 graph’s 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,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,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
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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, we’ll 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.
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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
@@ -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**
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Imagine a general-purpose writing agent built on a common graph architecture. Wh
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 graph’s 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 graph’s 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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
[](https://pepy.tech/project/langgraph)
|
||||
[](https://github.com/langchain-ai/langgraph/issues)
|
||||
[](https://langchain-ai.github.io/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.
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Generated
+125
-101
@@ -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"
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
Reference in New Issue
Block a user