Compare commits

...
201 Commits
Author SHA1 Message Date
David DuongandGitHub 2a7d48582f release(cli): 0.2.2 (#4241) 2025-04-11 01:40:35 +02:00
Tat Dat Duong b7bd87a063 release(cli): 0.2.2 2025-04-11 01:34:08 +02:00
David DuongandGitHub 2a825cc0e0 feat(cli): add multiplatform support (#4239)
- Uses new `install-node.sh` script already used for Python Gen UI
- Add default `node_version` / `python_version` based on provided
`graphs`

Closes #4115
2025-04-11 01:32:27 +02:00
Tat Dat Duong 436902e5a3 Consolidate node_version and python_version fix 2025-04-11 01:04:26 +02:00
Tat Dat Duong 0ac6a96c6e Fix up 2025-04-11 00:41:16 +02:00
Tat Dat Duong d9856d92af Another 3.9 fix 2025-04-11 00:09:34 +02:00
Tat Dat Duong b3487cbc49 Fix Python 3.9 2025-04-11 00:06:31 +02:00
Tat Dat Duong d06075cbcf Fix new style config 2025-04-11 00:04:44 +02:00
Tat Dat Duong e0be9ae2ef feat(cli): add multiplatform support
Uses new `install-node.sh` script already used for Python Gen UI, add default `node_version` / `python_version` based on provided `graphs`
2025-04-10 23:52:49 +02:00
Vadym BardaandGitHub 19cfe3a0a9 docs: fix title (#4238) 2025-04-10 17:17:36 -04:00
David DuongandGitHub 99a87abaa5 feat(docs): update typedoc references for auth (#4236) 2025-04-10 21:29:19 +02:00
William FHandGitHub a03cb0b16d CLI: Ensure correct api version is used (#4237)
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-10 15:56:07 +00:00
Tat Dat Duong 4be86b2a51 feat(docs): update typedoc references for auth 2025-04-10 17:03:03 +02:00
David DuongandGitHub 06e660a845 chore(docs,cli): remove python-only label for custom auth, add test for custom auth in CLI (#4233) 2025-04-10 16:02:12 +02:00
Tat Dat Duong 794dc1ae92 docs(api): add custom auth docs 2025-04-10 15:06:55 +02:00
Tat Dat Duong 6e7bfecbbc chore(docs,cli): remove python-only label for custom auth, add test for custom auth in CLI 2025-04-10 14:34:52 +02:00
fa080ea689 updates prereq banner (#4220)
<img width="796" alt="Screenshot 2025-04-09 at 1 20 14 PM"
src="https://github.com/user-attachments/assets/fe7409ed-c4cb-42d1-9e34-5adf4ca237e7"
/>

---------

Co-authored-by: Vadym Barda <vadim.barda@gmail.com>
2025-04-09 20:22:36 -04:00
David DuongandGitHub 9fb06fc5af feat(sdk-js): use event key instead of action, prevent retrying on HTTP 409 (#4223) 2025-04-10 01:54:54 +02:00
Tat Dat Duong 5debbb23ca feat(sdk-js): use event key instead of action, clean up interfaces 2025-04-10 01:52:44 +02:00
Nuno CamposandGitHub 7dcc760fd8 Validate other types in model_construct (#4200)
Resolves:
https://github.com/langchain-ai/langgraph/issues/4184
https://github.com/langchain-ai/langgraph/issues/4198 <- tested on
python 3.9 and 3.10
2025-04-09 15:58:11 -07:00
Vadym BardaandGitHub fdb9b9b8e0 checkpoint-postgres: add deprecation warning for ShallowPostgresSaver (#4219) 2025-04-09 11:31:33 -04:00
William FHandGitHub 2f51a15064 Update CLI (#4213) 2025-04-08 19:23:59 -07:00
Nuno CamposandGitHub d2acacfc8f Merge branch 'main' into wfh/_validate_more 2025-04-08 18:03:19 -07:00
Nuno CamposandGitHub 622a15b89e Remove pip from image (#4208)
After user installs, removes pip, setuptools, and wheel from the
resulting image.
2025-04-08 18:01:24 -07:00
Nuno CamposandGitHub bf50938de5 Update poetry version used in ci (#4212) 2025-04-08 18:00:28 -07:00
Nuno Campos d67a500cd9 Fix 2025-04-08 17:57:09 -07:00
Nuno Campos c6f5e561ec Update poetry version used in ci 2025-04-08 17:52:24 -07:00
William FHandGitHub 288fe12933 docs: Fix link (#4211) 2025-04-08 17:31:00 -07:00
William Fu-Hinthorn 5b58efc8d7 Update tests 2025-04-08 17:08:24 -07:00
David DuongandGitHub 2e1e582991 feat(sdk-js): add support for registering multiple events at once (#4209) 2025-04-09 01:58:49 +02:00
Tat Dat Duong 72260e64d5 Prevent casting 2025-04-09 01:57:50 +02:00
Tat Dat Duong 3193f5d063 feat(sdk-js): add support for registering multiple events at once 2025-04-09 01:56:32 +02:00
William Fu-Hinthorn 1b9093459c Remove pip from image
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-08 15:52:58 -07:00
Nuno Campos 392805938e 0.3.27 2025-04-08 15:04:46 -07:00
Nuno CamposandGitHub 4fb2aeacc7 Add checkpoint_during arg (#4169)
- This provides a new mode of execution where only the last checkpoint
is saved
- We save the last checkpoint no matter how the agent run is terminated
(success, error, interrupt, etc)
- This cuts down on cpu time spent on checkpointing, while not losing
any resilience benefits, given individual task writes are still saved
- If an error occurs and the run is retried, any tasks that completed
successfully before will be skipped (as currently)
- checkpoint_during=True is useful when you want to time-travel to inner
steps of a run
- The default value will remain the current behavior, ie.
checkpoint_during=True
2025-04-08 15:03:06 -07:00
David DuongandGitHub 8252668bcc release(sdk-js): 0.0.64 (#4207) 2025-04-08 23:46:23 +02:00
Tat Dat Duong 27e4b0fcfe release(sdk-js): 0.0.64 2025-04-08 23:45:03 +02:00
David DuongandGitHub ba388e25b3 feat(sdk-js): add auth types (#4199) 2025-04-08 23:44:33 +02:00
Nuno Campos 947a233fc5 Fix 2025-04-08 14:20:29 -07:00
Nuno Campos b76dc8ae0a Fix 2025-04-08 14:15:13 -07:00
Nuno Campos cbbfaba1fd Add comments 2025-04-08 14:07:08 -07:00
Nuno Campos e9aec77893 Add more tests 2025-04-08 14:07:02 -07:00
William FH 44691b69a3 Merge branch 'main' into wfh/_validate_more
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-08 13:57:37 -07:00
Nuno CamposandGitHub 067c4dd246 Implement simpler filtering of config keys in RemoteGraph (#4205) 2025-04-08 13:51:11 -07:00
Nuno Campos ccc21974e0 Implement simpler filtering of config keys in RemoteGraph 2025-04-08 13:44:31 -07:00
Nuno CamposandGitHub a6e66746f7 Make compatible with langchain-core 0.1 by conditionally importing _StreamingCallbackHandler (#4203) 2025-04-08 13:28:01 -07:00
Vadym BardaandGitHub 3a17df6106 langgraph: release 0.3.26 (#4204) 2025-04-08 14:53:10 -04:00
William Fu-Hinthorn 52c2837e42 Lint & handle arb types
Test on pydantic < 2

Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-08 11:49:37 -07:00
Nuno Campos cee6a450dc Lint 2025-04-08 10:52:28 -07:00
Nuno Campos 0b3bf37a55 Fix the rest 2025-04-08 10:47:55 -07:00
Nuno CamposandGitHub 305a676675 langgraph: raise GraphInterrupt only if used as a subgraph (#4202) 2025-04-08 10:45:45 -07:00
Nuno Campos 5b73e38c38 Make compatible with langchain-core 0.1 by conditionally importing _StreamingCallbackHandler 2025-04-08 10:44:12 -07:00
vbarda 41fb5ec77c Revert "add warning"
This reverts commit cff349e22e.
2025-04-08 13:38:49 -04:00
vbarda cff349e22e add warning 2025-04-08 13:34:47 -04:00
vbarda 8f32fc4819 update tests 2025-04-08 13:28:35 -04:00
vbarda 5690555394 langgraph: raise GraphInterrupt only if used as a subgraph 2025-04-08 12:51:36 -04:00
William Fu-Hinthorn 933d6aa8f5 Validate types.
My be too slow though. V1 handling is ugly.
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-08 09:31:54 -07:00
Tat Dat Duong 7d5621a84f Default type for TExtra 2025-04-08 17:10:43 +02:00
Tat Dat Duong 5f1213a1c7 Remove extra 2025-04-08 17:07:04 +02:00
Tat Dat Duong a98f9542fa Add unused extra generic for future typing of metadata 2025-04-08 17:06:51 +02:00
William Fu-Hinthorn d2e854b04f Merge branch 'main' into wfh/_validate_more 2025-04-08 06:34:38 -07:00
William FHandGitHub c5b118a672 Add admonitions about managed checkpointers (#4197)
If you're deploying with langgraph API, you don't need to manually
define a checkpointer. For folks who already know they'll be developing
with the api server, I'd like to save everyone time by making this more
clear in the docs on checkpointing.
2025-04-08 12:16:24 +00:00
Tat Dat Duong aee39605e0 Add missing types 2025-04-08 14:13:59 +02:00
lc-arjunandGitHub 72bec9161a Release js sdk 0.0.63 (#4192) 2025-04-07 18:40:59 -07:00
Nuno CamposandGitHub ae17e77522 feat: add assistant description to js sdk (#4191) 2025-04-07 18:37:49 -07:00
Arjun Natarajan a96fc75c55 add assistant description to js sdk 2025-04-07 21:06:34 -04:00
Nuno Campos d541ed90d5 Save Sends unconditionally 2025-04-07 16:45:26 -07:00
William Fu-Hinthorn 7d7708fe42 Validate more 2025-04-07 11:29:32 -07:00
Tat Dat Duong c757247858 feat(sdk-js): add auth types 2025-04-07 20:29:14 +02:00
Nuno Campos 5a0228cb13 Add test 2025-04-04 16:00:28 -07:00
Nuno Campos 4abfc7702d Subgraphs inherit checkpoint mode 2025-04-04 16:00:22 -07:00
Nuno Campos a5495e84c8 Add another test 2025-04-04 15:42:11 -07:00
Nuno Campos 4f353dac31 Fix assignment of pending writes 2025-04-04 14:41:10 -07:00
Nuno CamposandGitHub 4c89bb39d4 Add benchmark script for typed dict version of existing wide state benchmark (#4174)
- to easily compare perf impact of using pydantic, data class, or typed
dict for same workload
2025-04-04 18:37:57 +00:00
Eugene YurtsevandGitHub 05a4fcc8bb cli: release 0.1.89 (#4173)
Release to pick up this: https://github.com/langchain-ai/langgraph/pull/4164
2025-04-04 13:46:47 -04:00
Nuno Campos 7ebd6f5e1f Better test 2025-04-04 10:01:35 -07:00
Eugene YurtsevandGitHub adac016e33 cli: support dict format for graph specification in langgraph.json (#4164)
Allow the CLI to work with dict format for the graph specification.

```json
{
  "dependencies": ["./my_agent"],
  "graphs": {
    "agent": {
      "path": "./my_agent/agent.py:graph",
      "description": "this is my agent description"
    }
  },
  "env": ".env"
}
```

And backwards compatible with:

```json
{
  "dependencies": ["./my_agent"],
  "graphs": {
    "agent": "./my_agent/agent.py:graph",
  },
  "env": ".env"
}
```
2025-04-04 10:16:20 -04:00
Nuno Campos 0a1dd7a01a Do same thing for writes 2025-04-03 17:31:34 -07:00
Nuno Campos 7e08339335 mypy is dumb 2025-04-03 16:55:23 -07:00
Nuno Campos e1d4b5552d Add checkpoint_during arg
- This provides a new mode of execution where only the last checkpoint is saved
- We save the last checkpoint no matter how the agent run is terminated (success, error, interrupt, etc)
- This cuts down on cpu time spent on checkpointing, while not losing any resilience benefits, given individual task writes are still saved
- If an error occurs and the run is retried, any tasks that completed successfully before will be skipped (as currently)
- checkpoint_during=True is useful when you want to time-travel to inner steps of a run
- The default value will remain the current behavior, ie. checkpoint_during=True
2025-04-03 16:51:53 -07:00
David DuongandGitHub 2d13904abf release(langgraph): 0.3.25 (#4167) 2025-04-03 22:20:03 +02:00
Tat Dat Duong dfeb9d3b46 release(langgraph): 0.3.25 2025-04-03 22:12:16 +02:00
David DuongandGitHub 81935a73d8 feat(langgraph): Add UI messages API (#4157)
Sample usage:

```python
from typing import Annotated, Sequence, TypedDict

from langchain_core.messages import BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages

from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message


class AgentState(TypedDict):  # noqa: D101
    messages: Annotated[Sequence[BaseMessage], add_messages]
    ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]


async def agent(state: AgentState):  # noqa: D103
    message = await ChatOpenAI(model="gpt-4o-mini", temperature=0).ainvoke(
        state["messages"]
    )

    # Also directly writes the message to `ui`
    push_ui_message("simple", {"hello": "123"}, message=message, state_key="ui")

    return {"messages": [message]}

```
2025-04-03 22:10:18 +02:00
Tat Dat Duong 615fc8b4ae Update naming 2025-04-03 21:58:16 +02:00
William FHandGitHub 13e6f6cbde Add algolia site verification (#4165) 2025-04-03 12:53:40 -07:00
Vadym BardaandGitHub e89633f30b prebuilt: release 0.1.8 (#4161) 2025-04-03 12:01:18 -04:00
Vadym BardaandGitHub 0bbf5829e8 docs: add a how-to guide for managing message history in create_react_agent (#4149) 2025-04-03 16:00:11 +00:00
David DuongandGitHub 7ed5288f8f release(cli): 0.1.84 (#4158) 2025-04-03 15:25:12 +02:00
Tat Dat Duong cba240e70e release(cli): 0.1.84 2025-04-03 15:15:52 +02:00
Tat Dat Duong e9b5046076 Update docs to include Python API 2025-04-03 14:39:45 +02:00
Tat Dat Duong af6552a17e Move to langgraph/graph 2025-04-03 14:13:26 +02:00
Tat Dat Duong e38c30a434 Other docstring changes 2025-04-03 14:13:26 +02:00
Tat Dat Duong e41dea4cf9 Remove unnecessary return value 2025-04-03 14:13:26 +02:00
Tat Dat Duong f9f8c19ec4 Update docstrings 2025-04-03 14:13:26 +02:00
Tat Dat Duong 64ab3217f6 Add UI messages API 2025-04-03 14:13:26 +02:00
David DuongandGitHub 9af243d138 feat(cli): pass ui and ui config to inmem server, handle Docker setup for UI (#4100) 2025-04-03 14:11:30 +02:00
David DuongandGitHub 3f1d440aee fix(sdk-js): send accepts any input (#4099) 2025-04-03 14:00:31 +02:00
Tat Dat Duong 78901599e6 Add test for UI config 2025-04-03 13:48:14 +02:00
Tat Dat Duong 958c0df2d7 Install Node.js runtime and run the build process to get the UI 2025-04-03 13:48:14 +02:00
Tat Dat Duong 6919de8b3e feat(cli): pass ui and ui config to inmem server 2025-04-03 13:48:14 +02:00
Nuno CamposandGitHub e9a66cef46 Update jinja2 dev dep (#4150) 2025-04-02 16:02:25 -07:00
Nuno CamposandGitHub 728679e48e Bump langchain-core from 0.3.0 to 0.3.15 in /libs/checkpoint-sqlite (#3978)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
0.3.0 to 0.3.15.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/25a103187137077d4331e7153fe119e1c0c3ffb6"><code>25a1031</code></a>
community: Fix a validation error for MoonshotChat (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27801">#27801</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/e4e2aa0b78e6662bb5cebb06b15c19ddbe96ae43"><code>e4e2aa0</code></a>
core[patch]: update image util err msg (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27803">#27803</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/181bcd05778ff08688ea22f8dab81a6bd27501fd"><code>181bcd0</code></a>
core[patch]: Release 0.3.15 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27802">#27802</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c1e742347f9701aadba8920e4d1f79a636e50b68"><code>c1e7423</code></a>
core[patch]: rm image loading (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27797">#27797</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ad0387ac97e0c8feee4272f4ed98f0d65bd616ba"><code>ad0387a</code></a>
Improvement [docs] Improve api docs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27787">#27787</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/d9163e7afa0e5e975d36b7482c6a101e5c5dc375"><code>d9163e7</code></a>
community[docs]: Add content for the Lora adapter in the VLLM page. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27788">#27788</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/0172d938b4bf1e9da9f3b796dbfce64c565ce565"><code>0172d93</code></a>
community: add AzureOpenAIWhisperParser (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27796">#27796</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b631b0a5964bcdd46cf30fa0e91925d724ec7ae8"><code>b631b0a</code></a>
community[patch]: cap SQLAlchemy and update deps (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27792">#27792</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/8ad7adad8784d01ad1cbbb8b4c5f8102dbf11a63"><code>8ad7ada</code></a>
infra: build api docs from package listing (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27774">#27774</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3952ee31b8fc95b1ed74b83429002a7b5da630a3"><code>3952ee3</code></a>
ollama: add pydocstyle linting for ollama (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27686">#27686</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==0.3.0...langchain-core==0.3.15">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=pip&previous-version=0.3.0&new-version=0.3.15)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>
2025-04-02 15:57:14 -07:00
Nuno CamposandGitHub f90c81f280 Bump langchain-core from 0.2.38 to 0.2.43 in /libs/checkpoint (#3979)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
0.2.38 to 0.2.43.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/9fdeb74d9965258ad077d535681d9bae84b58e08"><code>9fdeb74</code></a>
core[patch]: Release 0.2.43 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27808">#27808</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7d481f10102f43559cc57bcad7eba291067939ee"><code>7d481f1</code></a>
core[patch]: remove prompt img loading (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27807">#27807</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/33a53970e1eb9a515d0d38809dd0d7f2e556c4ae"><code>33a5397</code></a>
infra: turn off release attestations (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27766">#27766</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/283cb50ea69d03d082db5607b68f6d13aa4e65a1"><code>283cb50</code></a>
core[patch]: Release 0.2.42 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27763">#27763</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/5e3cee6c98a55d5303847c6f22256e0671caa83f"><code>5e3cee6</code></a>
core[patch]: make get_all_basemodel_annotations public (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27762">#27762</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/807314661dbd8933d18a0289df05bd381ffbbc4f"><code>8073146</code></a>
Added mapping to fix CI for #langchain-aws:227. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/27114">#27114</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/6cfd1e846a3f7369d33a77c3fb60314e3e60e202"><code>6cfd1e8</code></a>
core[patch]: Release 0.2.41 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26687">#26687</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/c5eca37262132b7b88d0f68835af5fea49d13494"><code>c5eca37</code></a>
core[patch]: Fixed bedrock chat model load. (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26643">#26643</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/70c992bd61c48ebb4f60bde0ff9bfd7b54393678"><code>70c992b</code></a>
community: poetry lock for cffi dep (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26674">#26674</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/51fd70be63afeaecf0eff78c2a6f6d3f34330203"><code>51fd70b</code></a>
infra: 0.2 release checkout ref for release note (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/26604">#26604</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==0.2.38...langchain-core==0.2.43">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=pip&previous-version=0.2.38&new-version=0.2.43)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>
2025-04-02 15:56:20 -07:00
Nuno Campos f118a61101 Update jinja2 dev dep 2025-04-02 15:56:00 -07:00
dependabot[bot]andNuno Campos 8963bb2b68 Bump langchain-core from 0.3.0 to 0.3.15 in /libs/checkpoint-sqlite
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 0.3.0 to 0.3.15.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==0.3.0...langchain-core==0.3.15)

---
updated-dependencies:
- dependency-name: langchain-core
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 15:50:49 -07:00
dependabot[bot]andNuno Campos 499a1812e8 Bump langchain-core from 0.2.38 to 0.2.43 in /libs/checkpoint
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from 0.2.38 to 0.2.43.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==0.2.38...langchain-core==0.2.43)

---
updated-dependencies:
- dependency-name: langchain-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 15:49:49 -07:00
Nuno Campos 2cb81201b2 0.3.24 2025-04-02 15:45:06 -07:00
Nuno Campos 494cd4f6ad checkpoint 2.0.24 2025-04-02 15:44:58 -07:00
Nuno CamposandGitHub 9226b42150 Add checkpoint migrations for state graph internal channels (#4125) 2025-04-02 15:42:28 -07:00
Eugene YurtsevandGitHub 5e0a843423 sdk-py: release 0.1.61 (#4148) 2025-04-02 15:34:36 -07:00
6e54f74fa9 sdk-py: Add option to set description via client sdk (#4147)
Co-authored-by: William FH <13333726+hinthornw@users.noreply.github.com>
2025-04-02 21:26:14 +00:00
Nuno Campos ffb8400b19 Disable tests in 3.9 2025-04-02 14:05:15 -07:00
Nuno Campos b6b40149a9 Fix 2025-04-02 13:56:29 -07:00
Nuno Campos 5eecd2ac3a Update 2025-04-02 13:31:36 -07:00
Nuno Campos c38f7be89c Migrate quadratic channels too 2025-04-02 13:24:20 -07:00
Nuno Campos 2d406f03a2 Lint 2025-04-02 12:56:09 -07:00
Nuno Campos 1c48ec0aba Lint 2025-04-02 12:48:10 -07:00
Nuno Campos 4dda404da3 Implement checkpoint migration
- Migrate start:{node} channels to branch:to:{node}
- Migrate {node} channels to branch:to:{node}
2025-04-02 12:44:59 -07:00
Mahmut CAVDARandGitHub cce9801a8b docs: fixed indentation (#4078)
"Copy the clipboard" doesn't return valid Python code.
2025-04-02 13:44:45 -04:00
Vadym BardaandGitHub 67e8f8fc11 prebuilt: add optional pre-model hook that runs before calling LLM in create_react_agent (#4059)
Example:

```python
from typing import Any
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import AnyMessage
from langchain_core.messages.utils import count_tokens_approximately

from langgraph.graph import MessagesState
from langgraph.prebuilt.chat_agent_executor import create_react_agent, AgentState
from langgraph.checkpoint.memory import InMemorySaver

from langmem.short_term import SummarizationNode, RunningSummary


class State(MessagesState):
    context: dict[str, Any]

search = TavilySearchResults(max_results=3)
tools = [search]

model = ChatOpenAI(model="gpt-4o")
summarization_model = model.bind(max_tokens=256)

summarization_node = SummarizationNode(
    token_counter=count_tokens_approximately,
    model=summarization_model,
    max_tokens=2048,
    max_summary_tokens=256,
    output_messages_key="messages"
    # output_messages_key="llm_input_messages"
)

checkpointer = InMemorySaver()


class State(AgentState):
    user_language: str

    # summarization-related keys
    context: dict[str, Any]


def prompt(state):
    language = state["user_language"]
    system_msg = f"Always respond in {language}"
    return [{"role": "system", "content": system_msg}] + state["messages"]


graph = create_react_agent(
    model,
    tools,
    prompt=prompt,
    pre_model_hook=summarization_node,
    state_schema=State,
    checkpointer=checkpointer
)
```
2025-04-02 15:41:31 +00:00
RohitandGitHub 3a9247728b Fix missing colon in function definition in langchain-ai.github docum… (#4142)
…entation.

This commit fixes a syntax error in the "langchain-ai.github"
documentation. The function called "some_node_inside_alice" was missing
a colon (:) after the function, which is required for valid Python
syntax.
2025-04-02 08:04:22 -07:00
33766eb2ff docs: add missing MemorySaver import for unexpanded example (#4133)
Import `MemorySaver` for the code to run without expanding the example

---------

Co-authored-by: Eugene Yurtsev <eugene@langchain.dev>
2025-04-02 14:37:51 +00:00
Nuno CamposandNuno Campos 6fe319ed1b WIP: Add migrate_checkpoint 2025-04-02 07:24:55 -07:00
Andrew NguonlyandGitHub 3878addbe0 docs: Refactor content for new LangGraph Platform deployment options (#4118)
### Summary
This is a large refactor of the content for the LangGraph Platform
deployment options. Although there are a lot of changes, I do feel
fairly confident that this is safe to merge and won't have any negative
impact related to confusion around deployment options. However, please
review thoroughly (i.e. run the docs locally).

### Goals and Non-Goals
Just wanted to explicitly state goals and non-goals so that we're clear
about what needs to be done now versus what can be done in a smaller
follow-up PR.

Goals
1. Add new content for the new deployment options (Self-Hosted Data
Plane, Self-Hosted Control Plane).
1. Hide old content for deprecated deployment options (BYOC).
1. Create a pair of "conceptual" and "how-to" pages for each deployment
option. As much as possible, the pages should have consistent headings.
1. Introduce the terms "control plane" and "data plane" and define them
plainly without hiding/abstracting information.

Non-Goals
1. Do not change the navigation of the existing deployment options. As
much as possible, update content in-place or add new pages. Changing the
navigation is a bigger task that can be done later.
1. Do not remove old content for deprecated deployment options. We may
need to refer to this later. There are only ~2 pages (I think).

### Next Steps
1. Update the architecture diagrams for each deployment option. Commit
Excalidraw file to source control.
1. Create a "how-to" page for the Control Plane UI. This page pertains
to 3/4 deployment options. Most of the content lives in the "how-to"
page for Cloud SaaS deployment.
1. Document required RBAC permissions for K8s for Self-Hosted Data Plane
and Self-Hosted Control Plane (and update links).
1. Figure out how to consolidate plan information.
1. Figure out where to document licensing, telemetry, custom
Postgres/Redis.
1. Update autoscaling content.
2025-04-02 06:51:14 -07:00
Vadym BardaandGitHub 55f922cf2f langgraph: release 0.3.23 (#4141) 2025-04-02 09:50:18 -04:00
William FHandGitHub 9a5dc5d8f2 Update link (#4131) 2025-04-01 23:18:54 -07:00
William FHandGitHub 90b3da5959 TTL How-to (#4129)
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-01 22:54:31 -07:00
William FHandGitHub d2275a6727 Update cli.md to mention ttl (#4128)
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-01 22:21:55 -07:00
Nuno CamposandGitHub 0c590afb27 Maintain checkpoint LATEST_VERSION constant in langgraph lib (#4126)
- This should be controlled by the langgraph version, not the version of
langgraph-checkpoint installed
2025-04-01 21:53:12 -07:00
Nuno Campos 6efeefe424 Maintain checkpoint LATEST_VERSION constant in langgraph lib
- This should be controlled by the langgraph version, not the version of langgraph-checkpoint installed
2025-04-01 21:46:38 -07:00
William FHandGitHub da8b8c606a Release CLI (#4124)
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-01 21:28:58 -07:00
William FHandGitHub dcda8c24d6 Add checkpointer configuration support in langgraph.json (#4122)
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-01 21:16:12 -07:00
Vadym BardaandGitHub e3d697620b langgraph: support removing all messages with RemoveMessage (#4117) 2025-04-01 17:51:54 -04:00
Nuno Campos 30883729f0 0.3.22 2025-04-01 07:54:24 -07:00
William FHandGitHub 1c403f34c8 Allow blocking in dev (#4109) 2025-04-01 05:52:25 -07:00
William Fu-Hinthorn 9bd78ed483 Allow blocking in dev
Signed-off-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2025-04-01 05:45:47 -07:00
Nuno CamposandGitHub a77db7d73d Avoid creating checkpoint unless we're saving it (#4106)
- When checkpointing is disabled don't call create_checkpoint in
PregelLoop
- In local_read apply writes directly to copies of updated channels
- Add BaseChannel.copy() method to create channel copies with less
overhead
2025-03-31 19:13:36 -07:00
Nuno Campos e85b7e6cd9 Lint 2025-03-31 18:46:51 -07:00
Nuno Campos 94fa46f9fa Avoid creating checkpoint unless we're saving it
- When checkpointing is disabled don't call create_checkpoint in PregelLoop
- In local_read apply writes directly to copies of updated channels
- Add BaseChannel.copy() method to create channel copies with less overhead
2025-03-31 18:37:13 -07:00
Nuno CamposandGitHub e50110ba91 Avoid raise-catch strategy in BaseChannel.checkpoint() (#4105)
- This mirrors the work done earlier on BaseChannel.get()
- Comparing to a sentinel value is significantly faster than raising and
catching an exception
2025-03-31 18:10:15 -07:00
Nuno Campos fd64ada9de Avoid raise-catch strategy in BaseChannel.checkpoint()
- This mirrors the work done earlier on BaseChannel.get()
- Comparing to a sentinel value is significantly faster than raising and catching an exception
2025-03-31 17:48:19 -07:00
Nuno CamposandGitHub b15ec09c3b Add fast path to serialize None values (#4103)
- If the value to serialize is None we can use encode it in the string
type, and skip msgpack encoding
- Use None value for edge/branch channels in StateGraph
2025-03-31 17:42:26 -07:00
Nuno Campos 5e9e7b79fe Lint 2025-03-31 17:36:00 -07:00
Nuno Campos c49a077789 Lint 2025-03-31 17:24:26 -07:00
Nuno Campos 881b07cf7f Lint 2025-03-31 16:36:16 -07:00
Nuno Campos 0425d4e65d Update 2025-03-31 16:29:07 -07:00
Nuno Campos 118016a21c Add fast path to serialize None values 2025-03-31 16:15:56 -07:00
Nuno CamposandGitHub bda3c3add9 Lazily create atomic counters in pregel scratchpad (#4101)
- many times these aren't actually used, so makes sense to delay
creation until needed
2025-03-31 15:44:00 -07:00
Nuno Campos 067b99c789 Lazily create atomic counters in pregel scratchpad
- many times these aren't actually used, so makes sense to delay creation until needed
2025-03-31 15:19:53 -07:00
Tat Dat Duong aef463c633 fix(sdk-js): send accepts any input 2025-04-01 00:15:32 +02:00
Nuno CamposandGitHub 673cc5ad1a Reduce perf impact of pregel scratchpad creation (#4098)
- make scratchpad class frozen now that its members are never reassigned
- replace next(gen expr) with for-loop to avoid allocating generator
objects
2025-03-31 15:13:38 -07:00
Nuno Campos e9e9a96a0d Reduce perf impact of pregel scratchpad creation
- make scratchpad class frozen now that its members are never reassigned
- replace next(gen expr) with for-loop to avoid allocating generator objects
2025-03-31 14:50:10 -07:00
Andrew NguonlyandGitHub cf7136297e docs: Add docs for more environment variables (#4080) 2025-03-28 15:40:16 -07:00
David DuongandGitHub e5aae80e3a feat(cli): add support for gen ui config (#4079) 2025-03-28 23:14:56 +01:00
Tat Dat Duong f629f68ec3 feat(cli): add support for gen ui config 2025-03-28 23:05:04 +01:00
Andrew NguonlyandGitHub 575de221fe docs: Update Cloud SaaS and CLI documentation pages (#4077)
### Summary
1. Update API spec.
2. Clarify how to specify `requirements.txt` in `dependencies` list.
3. Clarify deletion policy for database.
4. Clarify resource allocation for `Production` type deployments.
5. Update supported Python versions.
2025-03-28 14:21:24 -07:00
David DuongandGitHub d2fc5f0a0f feat(sdk-js): run optimistic values mutator before any network request (#4070) 2025-03-28 21:57:33 +01:00
Tat Dat Duong 8e4b8b11ff Add docs about optimistic updates 2025-03-28 21:52:37 +01:00
Tat Dat Duong 2808a7859a Bump to 0.0.62 2025-03-28 21:39:44 +01:00
Tat Dat Duong fb3c61ea4f feat(sdk-js): run optimistic values mutator before any network request 2025-03-28 21:39:35 +01:00
David DuongandGitHub 69cecd872c release(sdk-js): 0.0.61 (#4069) 2025-03-28 15:24:38 +01:00
Tat Dat Duong 9abc1c8174 release(sdk-js): 0.0.61 2025-03-28 15:23:25 +01:00
David DuongandGitHub de90ced29d fix(sdk-js): do not await for client.runs.stream, as it is already async generator (#4068) 2025-03-28 15:22:20 +01:00
Tat Dat Duong b8b973fc0c fix(sdk-js): do not await for client.runs.stream, as it is already async generator 2025-03-28 15:15:54 +01:00
David DuongandGitHub 89e3709a2a feat(docs): cloning traces locally (#4057) 2025-03-28 14:41:00 +01:00
Arjun Natarajan a7f012a19c fix link 2025-03-28 09:37:05 -04:00
Arjun Natarajan 9b05ab6453 mkdocs yaml 2025-03-28 09:27:30 -04:00
David DuongandGitHub 5401d2ea81 feat(sdk-js): add option to manually provide implementation for shared modules (#4042) 2025-03-28 14:04:09 +01:00
Nuno CamposandGitHub 4401612aa6 Reduce the number of channels created for each node by 50% (#4064)
- Used to be 2 channels per node, it is now one per node, which is the
minimum
- Now both hard edges, conditional edges, entrypoint and conditional
entrypoint all use the same channel to trigger a node
2025-03-27 18:05:04 -07:00
Nuno Campos 49bb08a3f9 Update prebuilt test 2025-03-27 17:58:29 -07:00
Nuno Campos 4f2e9b838f Reduce the number of channels created for each node by 50%
- Used to be 2 channels per node, it is now one per node, which is the minimum
- Now both hard edges, conditional edges, entrypoint and conditional entrypoint all use the same channel to trigger a node
2025-03-27 17:47:34 -07:00
Nuno Campos d30da72f6e Reduce the number of channels created for each node by 50%
- Used to be 2 channels per node, it is now one per node, which is the minimum
- Now both hard edges, conditional edges, entrypoint and conditional entrypoint all use the same channel to trigger a node
2025-03-27 17:47:11 -07:00
William FHandGitHub 7f079adfee Update auth user type (#4062) 2025-03-27 16:09:54 -07:00
William Fu-Hinthorn 4c74af606f Update auth user type 2025-03-27 16:03:11 -07:00
Eugene YurtsevandGitHub 900824089b docs: disable link checking on push and workflow dispatch (#4061) 2025-03-27 17:33:36 -04:00
Nuno CamposandGitHub 522caa643f In Python 3.12 or above, use asyncio eager task factory (#4055)
- This is a performance improvement when calling async functions that do
not use await, as they are run immediately and never scheduled in the
loop
2025-03-27 13:36:04 -07:00
Nuno Campos ef71656f05 Fix 2025-03-27 13:14:39 -07:00
Tat Dat Duong 3ba7c7fbed Do not throw error if window is undefined due to Next 2025-03-27 21:03:54 +01:00
Tat Dat Duong 5cdab86d48 Fix object assignment 2025-03-27 20:58:39 +01:00
Nuno CamposandGitHub f5fe7e5195 Remove internal frames from stack traces (#4054)
- For exceptions raised in user code (ie. nodes or edges) remove
internal frames from the stack trace
2025-03-27 12:41:54 -07:00
Arjun Natarajan 2b728410e9 spell check 2025-03-27 14:47:27 -04:00
Arjun Natarajan 30221da4a8 docs for cloning traces locally 2025-03-27 14:38:20 -04:00
Nuno Campos 1c5a354a7d In Python 3.12 or above, use asyncio eager task factory
- This is a performance improvement when calling async functions that do not use await, as they are run immediately and never scheduled in the loop
2025-03-27 11:00:37 -07:00
Nuno Campos bfd271e00e Remove internal frames from stack traces
- For exceptions raised in user code (ie. nodes or edges) remove internal frames from the stack trace
2025-03-27 10:58:13 -07:00
Vadym BardaandGitHub 9647b1e55f langgraph: use correct type for node destination annotations (#4053)
Fixes https://github.com/langchain-ai/langgraph/issues/4051
2025-03-27 16:57:39 +00:00
Eugene YurtsevandGitHub e4aa204110 docs: add langchain llms.txt to the overview (#4047) 2025-03-27 12:33:42 -04:00
Vadym BardaandGitHub 2c29edadec langgraph: release 0.3.21 (#4050) 2025-03-27 11:38:37 -04:00
Vadym BardaandGitHub 96847e644b langgraph: add tests for remote graph interrupts (#4048) 2025-03-27 15:17:29 +00:00
Tat Dat Duong 4b102638c2 feat(sdk-js): add option to manually provide implementation for shared modules 2025-03-26 22:46:59 +01:00
7021ce3742 patch: fix return type of Topic.update (#4029)
This PR fixes the return type annotation of the `update` method from
`None` to `bool`, as the method returns a boolean value indicating
whether self.values has changed

Co-authored-by: kakaogames <kakaogames@Justin-MacBook-Pro.local>
2025-03-26 16:57:35 -04:00
Nuno CamposandGitHub d0c0aa9697 benchmark: remove some benchmarks (#4039)
Remove some benchmarks temporarily so we can fit more stuff into the
annotation
2025-03-26 13:50:53 -07:00
Vadym BardaandGitHub 520de30350 langgraph: fix interrupt deserialization in RemoteGraph (#4040) 2025-03-26 16:42:21 -04:00
Eugene Yurtsev 81c0d47363 x 2025-03-26 15:16:44 -04:00
87603d8a00 docs: add version admonitions for Interrupt and RetryPolicy (#3988)
This pull request includes changes to add version admonitions to the
documentation and update the styling for these admonitions. The most
important changes include the addition of version information to the
documentation, updates to the CSS for version admonitions, and
modifications to the `mkdocs.yml` configuration file to include the new
stylesheets.
this should solve this #3991

---------

Co-authored-by: Eugene Yurtsev <eugene@langchain.dev>
2025-03-26 13:48:54 -04:00
Vadym BardaandGitHub 7ad7329c7d docs: add codeact prebuilt (#4036) 2025-03-26 17:25:17 +00:00
Vadym BardaandGitHub e981d27f84 prebuilt: release 0.1.7 (#4034) 2025-03-26 09:22:09 -04:00
Vadym BardaandGitHub 71db4f2ad5 prebuilt: ignore updates when combining parent commands with Send (#4033) 2025-03-26 09:20:37 -04:00
Nuno CamposandGitHub 4ced277e2d Update adopters.md (#4025) 2025-03-25 18:40:02 -07:00
jessicaouandGitHub 34738fa566 Update adopters.md 2025-03-25 17:27:53 -07:00
Nuno CamposandGitHub 0286c38784 fix(sdk-js): mark schema as nullable to match python (#3928) 2025-03-25 16:11:35 -07:00
Tat Dat Duong dd733a3389 fix(sdk-js): mark schema as nullable to match python 2025-03-19 22:40:55 +01:00
145 changed files with 8691 additions and 2253 deletions
+1 -2
View File
@@ -4,7 +4,7 @@ on:
workflow_call:
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
build:
@@ -71,4 +71,3 @@ jobs:
working-directory: libs/cli/js-examples
run: |
langgraph build -t langgraph-test-e
+1 -7
View File
@@ -9,7 +9,7 @@ on:
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
# This env var allows us to get inline annotations when ruff has complaints.
RUFF_OUTPUT_FORMAT: github
@@ -50,12 +50,6 @@ jobs:
working-directory: ${{ inputs.working-directory }}
run: poetry check
- name: Check lock file
if: steps.changed-files.outputs.all
shell: bash
working-directory: ${{ inputs.working-directory }}
run: poetry check --lock
- name: Install dependencies
if: steps.changed-files.outputs.all
# Also installs dev/lint/test/typing dependencies, to ensure we have
+1 -7
View File
@@ -9,7 +9,7 @@ on:
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
build:
@@ -39,12 +39,6 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_RO_TOKEN }}
- name: Check Lock
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
poetry check --lock
- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
+1 -1
View File
@@ -4,7 +4,7 @@ on:
workflow_call:
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
build:
+1 -1
View File
@@ -9,7 +9,7 @@ on:
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
PYTHON_VERSION: "3.10"
jobs:
+1 -1
View File
@@ -4,7 +4,7 @@ on:
workflow_call:
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
build:
+1 -1
View File
@@ -8,7 +8,7 @@ on:
- "libs/**"
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
benchmark:
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- "libs/**"
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
benchmark:
+1 -1
View File
@@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
changes:
+2 -2
View File
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
permissions:
contents: read
@@ -99,7 +99,7 @@ jobs:
env:
LANGCHAIN_API_KEY: test
run: |
if [ "${{ github.event_name }}" == "schedule" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ] || ([ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]); then
if [ "${{ github.event_name }}" == "schedule" ]; then
echo "Running link check on all HTML files matching notebooks in docs directory..."
poetry run pytest -v \
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
+6 -6
View File
@@ -12,7 +12,7 @@ on:
workflow_dispatch:
env:
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
markdown-link-check:
@@ -42,8 +42,8 @@ jobs:
- name: Check README.md is in sync
run: |
if ! diff -q README.md libs/langgraph/README.md >/dev/null; then
echo "README.md is out of sync with libs/langgraph/README.md"
diff -C 3 README.md libs/langgraph/README.md
exit 1
fi
if ! diff -q README.md libs/langgraph/README.md >/dev/null; then
echo "README.md is out of sync with libs/langgraph/README.md"
diff -C 3 README.md libs/langgraph/README.md
exit 1
fi
+1 -1
View File
@@ -10,7 +10,7 @@ on:
env:
PYTHON_VERSION: "3.11"
POETRY_VERSION: "1.7.1"
POETRY_VERSION: "2.1.2"
jobs:
build:
+3 -3
View File
@@ -9,7 +9,7 @@ on:
type: string
description: "JSON string of changed files"
schedule:
- cron: '0 13 * * *'
- cron: "0 13 * * *"
defaults:
run:
@@ -30,12 +30,12 @@ jobs:
uses: "./.github/actions/poetry_setup"
with:
python-version: 3.11
poetry-version: 1.7.1
poetry-version: 2.1.2
cache-key: test-langgraph-notebooks
- name: Install dependencies
run: |
poetry install --with test
poetry install --with test --no-root
poetry run pip install jupyter
- name: Start services
@@ -36,3 +36,6 @@ packages:
- name: "langgraph-reflection"
repo: "langchain-ai/langgraph-reflection"
description: "LangGraph agent that runs a reflection step."
- name: "langgraph-codeact"
repo: "langchain-ai/langgraph-codeact"
description: "LangGraph implementation of CodeAct agent that generates and executes code instead of tool calling."
@@ -0,0 +1 @@
eNptV3tcVHUWB3zAbr7yo1mmdZt8JM4dZphhGEAyXibKADI8xCD3zr2/YS5zX9wHMBiZtNaapo2htqW1IY9E8rGwaiVpa1k+UjNw1Tay1NxtSU1LS9d1z29meGX3j/nM3HPO97y+5/x+U9NUjmSFFYXQFlZQkUzRKvxQfDVNMirTkKL+sZFHqltk6rOzHLkbNZk9FelWVUmJj4qiJNYgSkigWAMt8lHlpijaTalR8F3ikB+m3iky3tND0xbpeKQoVAlSdPFPLNLRIrgSVF28rgAMpioEqxIeQawQCJcoz9TpdbLIIZBqCpJ11fr+BpmogigUZQ+RwqpeglX67AiKKKdkFsFr0UWwYMLSBEcJDE/JHkVP0BqnajLFEaygqKyq+QPUE6BAlLNOmRJUQkBsidspym5RZBQD4RB5hLFUNyJ4UQG5qFJODhEuRAEUgrgFmtMYFF8kFAkmAxEZ6VBB4jfKYJ1IVr2RkfFEEqF4eafI4dcuGSFG5P1uGcSLNFTcqyc4kaZUxBCi0GNIpCs4eEOREI2Bc1moIOEo0ygZYcy5vWmzqkI4ZQhcJTj8CTklyyLFVFBeHDnAysE0nZqicqxQQlAqpCO5kYwA3ozhU6C6uDbZUKtAyBwllyBC0pwclFGC1xC9C8l+c0KgAqWUkSqDB6hDoEgsA1UKVoyGBgG8BcOn8RIrIwJXBxHJGssxgOP3I/R0SvF4FSiGhOQ+RxIliDLFg7ScRRXKr5BjMHJPqhhsFsWLmtJblApR5hiS5ihF6SkEIckiowUoDghWjFBAcRxEBn1RMUguOHCxAiXQLCTo1pw9XvMEFnfInwM2jsXGdqCoxivYMN3PBRw21rZDZURJ5FiVEoiAFgZKklV9v592kUGygN8Sj9hFe9K0QKMwQBIPNaAHGGcGqz6bVVRRxiWw4RhSWTzCiEjRWIUVUKB9POJUfwlFf0MC5AcGycjFIRonAj3DVWKC1i7gPKHQSMCciPNzomdgAg7YHjJDWG5ChU4pquwdAI5jh2UB7WRVFuGZg7mVWafmD0UV/Q57ho2S1YBF72D2eDcZsftZlOKGPvWRnQJ1ooQTnaDqCggJGnaQSnF6wg3ziZ2gcmCyApPgQUTvrghCEQUIeQx4VqHJkDKMElEKMwGwLlB1w/QEJggnVQGbieApD6Q1cOdQhCawsBoDI+wV/PyUOIpGhr7NBZyDJkGav1pfFXjoCNxWFrMs0GonVJJwwnbk7th9xXodDxzh4EWJpJIWERQUPHO8Lt5FcQrS61TEw9D4F5Iu3mgwwhtR5AKLVvVKGMulCX7Og3Hv1/hFOggdS0uQurACxgNCAwUGwRiyUkBHlwdlUt2wZqF5oEcE9YA8MGU8hbVw1rAfAAtvGowLQwYRYQbgX/7VFvQYjAcygF7pqqshWzhlYDUwEG6fJmQd1BSdpcBW0Kwurm6CGYZxUbpCRtXjdvu2DDyAtlI0jaBISKBFPIe+1pIqVtLDmnVxMLN6okpRmWbohYD8JfA1exCSSIoDejcGbH3bKEmChecPI6pUEYWWYO9IHNCd4mbcJhJaJ6i+tiwIJSk9KtsL56VAmAxWk8G0rZIEHrACB+cfCQu9xNco+eXv9RdIFO0BEDJ4FvsaA8Zb+uuIiq/BTtFZjgGQlEy7fQ2UzFstrf3fy5qgwpHha0rJvtNdUNjnzmwwmQxx2wcAK16B9jX4SbZzgDFsNi9Ji4Dhe9PYSIuih0W+06HhCxfSroVOPhF5vIUZGcnuea4CQXE71NlpqRWSwxltijGUVEbH5NtjMwvtVZ6qMoNImmItZmtcdLTJQpoMRgPkTKZVpMzKzWBSCyjbLDlZMxjVGHfWbFMZnSVl5EkyW2bNnpM52wrEWZiLshz21LLHo/lcj1ZitzgdhpxsG+91zFYEj6kqlVroTqtikc0cOze7yhqX5S5bkF8xl9ZMOV5kTeMtCQSErJWzTOKCOUnZjmw7w+ZHxzoK57vS5glqXqrCV6LZBelahn1hrGzJUKPz7LGWfjGbjWbSGAzbarTYjPjZ0sMYDgklqttXb7IaY96C9SjBoYOebVTwFUGpqQeSosOfNAUvRnVZc/v4fU99KhDW157r1vSE0UwkSTIRbYyOIUyW+JjYeGMs8bg9tyUl6Cf3N5m5PRe2rAIHKZnWMw9NtFsTPIhpTvnNGWjHMwAN9i9tkSNRpSQqiAxG5WuZT+YEroRkemprYOxIUS6hBLbK79a3CfMbroCs0BYUwybAkOCc5BVfvcUaY94SFPVwrxkSM5ImI2k0vVtJwh5DHMuzUD3/Z/ASCtSPwbXddaeGKnoQ3Fc3mfwK8LzfX0dGPISD/fchWeLg2f3bWr1ofiVr3MCYoImoX0wbTbyy6055EKPOqLRU9iiTLOM7NQlz1khZY6xWmwXog8wus8tkNTEMhWKNCFmQzci8g5cjDSi4fZIoq6SCaLh2q17fKT1PVeJlk2g2xZitkGtCzw3UoTlTRZyEkgB3HMTBpWgr7SJpinYjMsA4X1NqYWaSPT1lx3yyP3XILClw5W8SREVgXa5GB5KhO75mmhM1BnanjBpTZpE5SYW+trhoKs5K2azIxthiUCxDphXkbOtB6yVaPV68TRQHsZfTvla3OVEXb7GYdQlwoCbarBaj0f/HYElj4CD4aNCxB5dHhPifQSvyvlh5f+yw6usFo66+9PxSvrvq0s219elW8nSj/cqojiVJHtvDCM2bLncPqxly4/5b7U+nP3Sq64drYT9+RA9rSLly+fLhnXc/dv6774ZG0vM+jbxxYPlOsevI5SPXftgodn986WZF09yr/1u3v/2DL6I6bV+dOT8u+t09MTG7X3i9o+zkc3Zd7sG3pq4raRr3j9ZTXH7Y9C0HLnj4jgP3Zbk+bm098EPnuZ+dFZ/91Typc/h/rWEhP+c/rSx7s3Fr5/AJq66L+ZNqyLzYVc+HNH/46J5N2R8ucx4rnTF4eHhDdvPT3yd/n3xX6e++7VydemGsfvvkkYa9WW1LstZfPNPw+QPDhs4fPbugqtwzftDvbUOE2rCOMdM6wiYQOaNH/CtpXi0xpfSRecZJjl3nh7897cWkK4NuGV6bvzLslYhXU69PHPJTYssjjGHTkWW1D3PTt0478OmMw/sOjFm58sllGdIV3cYJE7/cO3JJbOj4zDO5e+lSdtj+441fuUbvzR709/2dgz0ZK9vChcecuhYUP7J1xQ3P1LCjZxK6F2yunZSyZ//Yiit/qg658Mt/7u2IDy2x2RynQ5eHza0Liai7N3Fs5sx2fcTuxsERz9WUb3rjyD42c9HN7nvmTJ/8tWJ8NOvY3vyR9K5FxzdMaSl2Rdw62zKlrr2puOxzr+3Qtpnl06jbFy4t/iT0xlF7Svq13IhfZuSN+POKZ0/U0uqba0zc+gWPuU+q6OD1iTdalj/VnD7J+tK5FRfizl5t2HapdsSV9rMnvRmrOukm68iT3czqshFDj1cWKQefGXff5UFbv9lwV9ilS+/pXw69/u8PDx18J2bNzM0zvKNPPzv8byMGF7t+rBm6s5s9tOjlVQkNYZvXLv0D7W06N+Po2Ko38ooco+Y80caeOBf+2tV05suDhcOac+rGd0y5uODQhrWaOfOmKSLlaPGpt/ePpXa8s4QKNz9cqC0p8qxbr1dIR+XS/a61bfXLHxiefnfRt8TVz4ZUfHU315l92jCmdkUmdX7BZu+G2PUdNY2PfhGSnHJ/dteJ48q654q7HNGJbatT7VNf2aBXux4cbBycf7gqcso17zhvZRt5lM/YXbun+O3x49afTNONGXGqLjUuf/INY9jPRd3bF31TmZex+4V3s9/fuPqiuqP141LjuYfiE/acTFsz9tYo255/hr8eYijNUZ8cMWFHtFvfua/zg5XbN098cPCscP526/rF+7p2XEy+ufjM88+Yxqz5Li0jN+Fq2/mv59dxXbe1bt+3OdLk2hNHjiVPbJjxwqvju/977Ymc9Kg5m9POJs+dNmXnrb9Yysq06qWu8mqvXPziA7MWh4aE3L49KOSpnxa3F4aHhPwftFJrYA==
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
+4
View File
@@ -10,14 +10,17 @@ This list of companies using LangGraph and their success stories is compiled fro
| [Athena Intelligence](https://www.athenaintel.com/) | Software & Technology (GenAI Native) | Research & summarization | [Case study, 2024](https://blog.langchain.dev/customers-athena-intelligence/) |
| [Captide](https://www.captide.co/) | Software & Technology (GenAI Native) | Data extraction | [Case study, 2025](https://blog.langchain.dev/how-captide-is-redefining-equity-research-with-agentic-workflows-built-on-langgraph-and-langsmith/) |
| [Cisco Outshift](https://outshift.cisco.com/) | Software & Technology | DevOps | [Blog post, 2025](https://outshift.cisco.com/blog/build-react-agent-application-for-devops-tasks-using-rest-apis) |
| [C.H. Robinson](https://www.chrobinson.com/en-us/) | Logistics | Automation | [Case study, 2025](https://blog.langchain.dev/customers-chrobinson/) |
| [Elastic](https://www.elastic.co/) | Software & Technology | Copilot for domain-specific task | [Blog post, 2025](https://www.elastic.co/blog/elastic-security-generative-ai-features) |
| [GitLab](https://about.gitlab.com/) | Software & Technology | Code generation | [Duo workflow docs](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/duo_workflow/) |
| [Inconvo](https://inconvo.ai/?ref=blog.langchain.dev) | Software & Technology | Code generation | [Case study, 2025](https://blog.langchain.dev/customers-inconvo/) |
| [Infor](https://infor.com/) | Software & Technology | GenAI embedded product experiences; customer support; copilot | [Case study, 2025](https://blog.langchain.dev/customers-infor/) |
| [Klarna](https://www.klarna.com/) | Fintech | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/customers-klarna/) |
| [Komodo Health](https://www.komodohealth.com/) | Healthcare | Copilot for domain-specific task | [Blog post](https://www.komodohealth.com/perspectives/new-gen-ai-assistant-empowers-the-enterprise/) |
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
| [Minimal](https://gominimal.ai/) | E-commerce | Customer support | [Case study, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
| [OpenRecovery](https://www.openrecovery.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-openrecovery/) |
| [Qodo](https://www.qodo.ai/) | Software & Technology (GenAI Native) | Code generation | [Blog post, 2025](https://www.qodo.ai/blog/why-we-chose-langgraph-to-build-our-coding-agent/) |
| [Rakuten](https://www.rakuten.com/) | E-commerce / Fintech | Copilot for domain-specific task | [Blog post, 2025](https://rakuten.today/blog/from-ai-hype-to-real-world-tools-rakuten-teams-up-with-langchain.html) |
| [Replit](https://replit.com/) | Software & Technology | Code generation | [Blog post, 2024](https://blog.langchain.dev/customers-replit/); [Breakout agent story, 2024](https://www.langchain.com/breakoutagents/replit); [Fireside chat video, 2024](https://www.youtube.com/watch?v=ViykMqljjxU) |
| [Rexera](https://www.rexera.com/) | Real Estate (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-rexera/) |
@@ -25,3 +28,4 @@ This list of companies using LangGraph and their success stories is compiled fro
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
| [Vodafone](https://www.vodafone.com/) | Telecommunications | Code generation; internal search | [Case study, 2025](https://blog.langchain.dev/customers-vodafone/) |
+2 -2
View File
@@ -1,6 +1,6 @@
# How to Deploy to LangGraph Cloud
# How to Deploy to Cloud SaaS
LangGraph Cloud is available within <a href="https://www.langchain.com/langsmith" target="_blank">LangSmith</a>. To deploy a LangGraph Cloud API, navigate to the <a href="https://smith.langchain.com/" target="_blank">LangSmith UI</a>.
Before deploying, review the [conceptual guide for the Cloud SaaS](../../concepts/langgraph_cloud.md) deployment option.
## Prerequisites
@@ -0,0 +1,56 @@
# How to Deploy Self-Hosted Control Plane
Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option.
## Prerequisites
1. You are using Kubernetes.
1. You have self-hosted LangSmith deployed.
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](./test_locally.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`) and push it to a registry your Kubernetes cluster has access to.
1. `KEDA` is installed on your cluster.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
1. Ingress Configuration (recommended)
1. Install `Ingress Nginx` to serve as a reverse proxy for your deployment.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx
1. Provision a root domain that will suffix all domains for your workloads (e.g. `us.langgraph.app`).
1. Provision wildcard certificates to terminate TLS for your deployments.
1. Note: If this step is skipped, you will need to provision domains/certs for each of your deployments.
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
## Setup
1. As part of configuring your Self-Hosted LangSmith instance, you enable the `langgraphPlatform` option. This will provision a few key resources.
1. `listener`: This is a service that listens to the [control plane](../../concepts/langgraph_control_plane.md) for changes to your deployments and creates/updates downstream CRDs.
1. `LangGraphPlatform CRD`: A CRD for LangGraph Platform deployments. This contains the spec for managing an instance of a LangGraph platform deployment.
1. `operator`: This operator handles changes to your LangGraph Platform CRDs.
1. `host-backend`: This is the [control plane](../../concepts/langgraph_control_plane.md).
1. Two additional images will be used by the chart.
hostBackendImage:
repository: "docker.io/langchain/hosted-langserve-backend"
pullPolicy: IfNotPresent
tag: "0.9.80"
operatorImage:
repository: "docker.io/langchain/langgraph-operator"
pullPolicy: IfNotPresent
tag: "aa9dff4"
1. In your `values.yaml` file, enable the `langgraphPlatform` option.
config:
langgraphPlatform:
enabled: true
langgraphPlatformLicenseKey: "YOUR_LANGGRAPH_PLATFORM_LICENSE_KEY"
rootDomain: "YOUR_ROOT_DOMAIN"
1. You can also configure base templates for your agents by overriding the base templates [here](https://github.com/langchain-ai/helm/blob/main/charts/langsmith/values.yaml#L898).
1. You create a deployment from the [Control Plane UI](../../concepts/langgraph_control_plane.md#control-plane-ui).
@@ -0,0 +1,53 @@
# How to Deploy Self-Hosted Data Plane
Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option.
## Prerequisites
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](./test_locally.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`) and push it to a registry your Kubernetes cluster or Amazon ECS cluster has access to.
## Kubernetes
### Prerequisites
1. `KEDA` is installed on your cluster.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
1. A valid `Ingress` controller is install on your cluster.
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
### Setup
1. You give us your LangSmith organization ID. We will enable the Self-Hosted Data Plane for your organization.
1. We provide you a [Helm chart](https://github.com/langchain-ai/helm/tree/main/charts/langgraph-dataplane) which you run to setup your Kubernetes cluster. This chart contains a few important components.
1. `langgraph-listener`: This is a service that listens to LangChain's [control plane](../../concepts/langgraph_control_plane.md) for changes to your deployments and creates/updates downstream CRDs.
1. `LangGraphPlatform CRD`: A CRD for LangGraph Platform deployments. This contains the spec for managing an instance of a LangGraph Platform deployment.
1. `langgraph-platform-operator`: This operator handles changes to your LangGraph Platform CRDs.
1. Configure your `langgraph-dataplane-values.yaml` file.
config:
langgraphPlatformLicenseKey: "" # Your LangGraph Platform license key
langsmithApiKey: "" # API Key of your Workspace
langsmithWorkspaceId: "" # Workspace ID
hostBackendUrl: "https://api.host.langchain.com" # Only override this if on EU
smithBackendUrl: "https://api.smith.langchain.com" # Only override this if on EU
1. Deploy `langgraph-dataplane` Helm chart.
helm repo add langchain https://langchain-ai.github.io/helm/
helm repo update
helm upgrade -i langgraph-dataplane langchain/langgraph-dataplane --values langgraph-dataplane-values.yaml
1. If successful, you will see two services start up in your namespace.
NAME READY STATUS RESTARTS AGE
langgraph-dataplane-listener-7fccd788-wn2dx 0/1 Running 0 9s
langgraph-dataplane-redis-0 0/1 ContainerCreating 0 9s
1. You create a deployment from the [Control Plane UI](../../concepts/langgraph_control_plane.md#control-plane-ui).
## Amazon ECS
Coming soon!
@@ -0,0 +1,110 @@
# How to Deploy a Standalone Container
Before deploying, review the [conceptual guide for the Standalone Container](../../concepts/langgraph_standalone_container.md) deployment option.
## Prerequisites
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to [test your application locally](./test_locally.md).
1. Use the [LangGraph CLI](../../concepts/langgraph_cli.md) to build a Docker image (i.e. `langgraph build`).
1. The following environment variables are needed for a standalone container deployment.
1. `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs. The value of `REDIS_URI` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
!!! Note "Shared Redis Instance"
Multiple self-hosted deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI` can be set to `redis://<hostname_1>:<port>/1` and for `Deployment B`, `REDIS_URI` can be set to `redis://<hostname_1>:<port>/2`.
`1` and `2` are different database numbers within the same instance, but `<hostname_1>` is shared. **The same database number cannot be used for separate deployments**.
1. `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. The value of `DATABASE_URI` must be a valid [Postgres connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).
!!! Note "Shared Postgres Instance"
Multiple self-hosted deployments can share the same Postgres instance. For example, for `Deployment A`, `DATABASE_URI` can be set to `postgres://<user>:<password>@/<database_name_1>?host=<hostname_1>` and for `Deployment B`, `DATABASE_URI` can be set to `postgres://<user>:<password>@/<database_name_2>?host=<hostname_1>`.
`<database_name_1>` and `database_name_2` are different databases within the same instance, but `<hostname_1>` is shared. **The same database cannot be used for separate deployments**.
1. `LANGSMITH_API_KEY`: (if using [Lite](../../concepts/langgraph_data_plane.md#lite-vs-enterprise)) LangSmith API key. This will be used to authenticate ONCE at server start up.
1. `LANGGRAPH_CLOUD_LICENSE_KEY`: (if using [Enterprise](../../concepts/langgraph_data_plane.md#lite-vs-enterprise)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
1. `LANGSMITH_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
## Kubernetes (Helm)
Use this [Helm chart](https://github.com/langchain-ai/helm/blob/main/charts/langgraph-cloud/README.md) to deploy a LangGraph Server to a Kubernetes cluster.
## Docker
Run the following `docker` command:
```shell
docker run \
--env-file .env \
-p 8123:8000 \
-e REDIS_URI="foo" \
-e DATABASE_URI="bar" \
-e LANGSMITH_API_KEY="baz" \
my-image
```
!!! note
* You need to replace `my-image` with the name of the image you built in the prerequisite steps (from `langgraph build`)
and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`.
* If your application requires additional environment variables, you can pass them in a similar way.
## Docker Compose
Docker Compose YAML file:
```yml
volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: postgres:16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
image: ${IMAGE_NAME}
ports:
- "8123:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
env_file:
- .env
environment:
REDIS_URI: redis://langgraph-redis:6379
LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
```
You can run the command `docker compose up` with this Docker Compose file in the same folder.
This will launch a LangGraph Server on port `8123` (if you want to change this, you can change this by changing the ports in the `langgraph-api` volume). You can test if the application is healthy by running:
```shell
curl --request GET --url 0.0.0.0:8123/ok
```
Assuming everything is running correctly, you should see a response like:
```shell
{"ok":true}
```
@@ -0,0 +1,31 @@
# Testing local agents with remote traces
## Overview
A common workflow when debugging production-deployed agents is to test the same thread against a local version of the same agent, which may have modifications.
To support this, LangGraph Studio, in combination with LangSmith, allows you to clone remote threads traced in LangSmith into your locally running agent. This cloned thread can then be used to re-run specific nodes within Studio.
## Requirements
!!! info "Prerequisites"
- langgraph>=0.3.18
- langgraph-api>=0.0.32
- A thread traced in LangSmith.
- A locally running agent. See [here](../../how-tos/local-studio.md) for setup instructions.
- Note that your local agent must be using the above specified `langgraph` and `langgraph-api` versions.
- The nodes present in the remote trace must exist in at least one of the graphs in your local agent.
## Cloning Thread
First navigate to the LangSmith trace. Here you should see a button to "Run in Studio".
![Run in Studio](../img/run_in_studio.png){width=1200}
This will prompt you to enter the url that your locally running agent is accessible at. Once provided, select "Clone thread locally". If you have multiple graphs in your agent, you will also be prompted to select a graph to clone this thread under.
Once selected, a will a new thread in your local agent will be created and the thread history will be reconstruced to reflect the original trace.
Alternatively, if your trace originates from an agent deployed on LangGraph Platform, you can "View original thread" to open Studio with the actual deployed thread.
+109 -55
View File
@@ -12,10 +12,6 @@ Generative user interfaces (Generative UI) allows agents to go beyond text and g
LangGraph Platform supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as [Agent Chat](https://agentchat.vercel.app) and loading the code only when actually needed.
!!! warning "LangGraph.js only"
Currently only LangGraph.js supports Generative UI. Support for Python is coming soon.
## Tutorial
### 1. Define and configure UI components
@@ -74,58 +70,105 @@ CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tai
### 2. Send the UI components in your graph
Use the `typedUi` utility to emit UI elements from your agent nodes:
=== "Python"
```typescript title="src/agent/index.ts"
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
```python title="src/agent.py"
import uuid
from typing import Annotated, Sequence, TypedDict
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
from langchain_core.messages import AIMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, ui_message_reducer, push_ui_message
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
async def weather(state: AgentState):
class WeatherOutput(TypedDict):
city: str
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["langsmith:nostream"] })
.invoke(state.messages);
weather: WeatherOutput = (
await ChatOpenAI(model="gpt-4o-mini")
.with_structured_output(WeatherOutput)
.with_config({"tags": ["nostream"]})
.ainvoke(state["messages"])
)
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
message = AIMessage(
id=str(uuid.uuid4()),
content=f"Here's the weather for {weather['city']}",
)
// Emit UI elements with associated AI message
ui.push({ name: "weather", props: weather }, { message: response });
# Emit UI elements associated with the message
push_ui_message("weather", weather, message=message)
return {"messages": [message]}
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
workflow = StateGraph(AgentState)
workflow.add_node(weather)
workflow.add_edge("__start__", "weather")
graph = workflow.compile()
```
=== "JS"
Use the `typedUi` utility to emit UI elements from your agent nodes:
```typescript title="src/agent/index.ts"
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import { ChatOpenAI } from "@langchain/openai";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import type ComponentMap from "./ui.js";
import {
Annotation,
MessagesAnnotation,
StateGraph,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
export const graph = new StateGraph(AgentState)
.addNode("weather", async (state, config) => {
// Provide the type of the component map to ensure
// type safety of `ui.push()` calls as well as
// pushing the messages to the `ui` and sending a custom event as well.
const ui = typedUi<typeof ComponentMap>(config);
const weather = await new ChatOpenAI({ model: "gpt-4o-mini" })
.withStructuredOutput(z.object({ city: z.string() }))
.withConfig({ tags: ["nostream"] })
.invoke(state.messages);
const response = {
id: uuidv4(),
type: "ai",
content: `Here's the weather for ${weather.city}`,
};
// Emit UI elements associated with the AI message
ui.push({ name: "weather", props: weather }, { message: response });
return { messages: [response] };
})
.addEdge("__start__", "weather")
.compile();
```
### 3. Handle UI elements in your React application
@@ -294,18 +337,29 @@ const { thread, submit } = useStream({
### Remove UI messages from state
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `ui.delete` with the ID of the UI message.
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
```tsx
// pushed message
const message = ui.push({ name: "weather", props: { city: "London" } });
=== "Python"
// remove said message
ui.delete(message.id);
```python
from langgraph.graph.ui import push_ui_message, delete_ui_message
// return new state to persist changes
return { ui: ui.items };
```
# push message
message = push_ui_message("weather", {"city": "London"})
# remove said message
delete_ui_message(message["id"])
```
=== "JS"
```tsx
// push message
const message = ui.push({ name: "weather", props: { city: "London" } });
// remove said message
ui.delete(message.id);
```
## Learn more
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+49 -23
View File
@@ -1,7 +1,8 @@
# How to integrate LangGraph into your React application
!!! info "Prerequisites"
- [LangGraph Platform](../../concepts/langgraph_platform.md)
!!! info "Prerequisites"
- [LangGraph Platform](../../concepts/langgraph_platform.md)
- [LangGraph Server](../../concepts/langgraph_server.md)
The `useStream()` React hook provides a seamless way to integrate LangGraph into your React applications. It handles all the complexities of streaming, state management, and branching logic, letting you focus on building great chat experiences.
@@ -169,10 +170,7 @@ The `useStream()` hook exposes the `interrupt` property, which will be filled wi
Learn more about interrupts in the [How to handle interrupts](../../how-tos/human_in_the_loop/wait-user-input.ipynb) guide.
```tsx
const thread = useStream<
{ messages: Message[] },
{ InterruptType: string }
>({
const thread = useStream<{ messages: Message[] }, { InterruptType: string }>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
@@ -182,7 +180,6 @@ if (thread.interrupt) {
return (
<div>
Interrupted! {thread.interrupt.value}
<button
type="button"
onClick={() => {
@@ -313,7 +310,7 @@ export default function App() {
onEdit={(message) =>
thread.submit(
{ messages: [message] },
{ checkpoint: parentCheckpoint },
{ checkpoint: parentCheckpoint }
)
}
/>
@@ -370,6 +367,33 @@ export default function App() {
For advanced use cases you can use the `experimental_branchTree` property to get the tree representation of the thread, which can be used to render branching controls for non-message based graphs.
### Optimistic Updates
You can optimistically update the client state before performing a network request to the agent, allowing you to provide immediate feedback to the user, such as showing the user message immediately before the agent has seen the request.
```tsx
const stream = useStream({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
});
const handleSubmit = (text: string) => {
const newMessage = { type: "human" as const, content: text };
stream.submit(
{ messages: [newMessage] },
{
optimisticValues(prev) {
const prevMessages = prev.messages ?? [];
const newMessages = [...prevMessages, newMessage];
return { ...prev, messages: newMessages };
},
}
);
};
```
### TypeScript
The `useStream()` hook is friendly for apps written in TypeScript and you can specify types for the state to get better type safety and IDE support.
@@ -397,21 +421,23 @@ You can also optionally specify types for different scenarios, such as:
- `UpdateType`: Type for the submit function (default: `Partial<State>`)
```tsx
const thread = useStream<State, {
UpdateType: {
messages: Message[] | Message;
context?: Record<string, unknown>;
};
InterruptType: string;
CustomEventType: {
type: "progress" | "debug";
payload: unknown;
};
ConfigurableType: {
model: string;
};
}>({
const thread = useStream<
State,
{
UpdateType: {
messages: Message[] | Message;
context?: Record<string, unknown>;
};
InterruptType: string;
CustomEventType: {
type: "progress" | "debug";
payload: unknown;
};
ConfigurableType: {
model: string;
};
}
>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
+294 -11
View File
@@ -22,7 +22,7 @@
"description": "A run is an invocation of a graph / assistant, with no state or memory persistence."
},
{
"name": "Crons (Enterprise-only)",
"name": "Crons (Plus tier)",
"description": "A cron is a periodic run that recurs on a given schedule. The repeats can be isolated, or share state in a thread"
},
{
@@ -805,6 +805,58 @@
}
}
},
"/threads/state/bulk": {
"post": {
"tags": [
"Threads"
],
"summary": "Bulk Update Thread State",
"description": "Create a new thread from a batch of state updates.",
"operationId": "bulk_update_thread_state_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ThreadStateBulkUpdate"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Thread"
}
}
}
},
"409": {
"description": "Conflict",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/threads/{thread_id}/state": {
"get": {
"tags": [
@@ -1342,6 +1394,21 @@
},
"name": "offset",
"in": "query"
},
{
"required": false,
"schema": {
"type": "string",
"enum": [
"pending",
"error",
"success",
"timeout",
"interrupted"
]
},
"name": "status",
"in": "query"
}
],
"responses": {
@@ -1458,7 +1525,7 @@
"/threads/{thread_id}/runs/crons": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Create Thread Cron",
"description": "Create a cron to schedule runs on a thread.",
@@ -1836,6 +1903,17 @@
},
"name": "run_id",
"in": "path"
},
{
"required": false,
"schema": {
"type": "boolean",
"title": "Cancel on Disconnect",
"description": "If true, the run will be cancelled if the client disconnects.",
"default": false
},
"name": "cancel_on_disconnect",
"in": "query"
}
],
"responses": {
@@ -2032,7 +2110,7 @@
"/runs/crons": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Create Cron",
"description": "Create a cron to schedule runs on new threads.",
@@ -2084,7 +2162,7 @@
"/runs/crons/search": {
"post": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Search Crons",
"description": "Search all active crons",
@@ -2190,6 +2268,68 @@
}
}
},
"/runs/cancel": {
"post": {
"tags": [
"Thread Runs"
],
"summary": "Cancel Runs",
"description": "Cancel one or more runs. Can cancel runs by thread ID and run IDs, or by status filter.",
"operationId": "cancel_runs_post",
"parameters": [
{
"description": "Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. `interrupt` will simply cancel the run. `rollback` will cancel the run and delete the run and associated checkpoints afterwards.",
"required": false,
"schema": {
"type": "string",
"enum": [
"interrupt",
"rollback"
],
"title": "Action",
"default": "interrupt"
},
"name": "action",
"in": "query"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunsCancel"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "Success - Runs cancelled"
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/runs/wait": {
"post": {
"tags": [
@@ -2373,7 +2513,7 @@
"/runs/crons/{cron_id}": {
"delete": {
"tags": [
"Crons (Enterprise-only)"
"Crons (Plus tier)"
],
"summary": "Delete Cron",
"description": "Delete a cron by ID.",
@@ -2936,7 +3076,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3216,7 +3356,11 @@
"description": "The command to run.",
"properties": {
"update": {
"type": "object",
"type": [
"object",
"array",
"null"
],
"title": "Update",
"description": "An update to the state."
},
@@ -3226,12 +3370,13 @@
"array",
"number",
"string",
"boolean",
"null"
],
"title": "Resume",
"description": "A value to pass to an interrupted node."
},
"send": {
"goto": {
"anyOf": [
{
"$ref": "#/components/schemas/Send"
@@ -3242,10 +3387,21 @@
"$ref": "#/components/schemas/Send"
}
},
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
],
"title": "Goto",
"description": "Name of the node(s) to navigate to next or node(s) to be executed with a provided input."
}
}
},
@@ -3276,6 +3432,18 @@
{
"type": "object"
},
{
"type": "array"
},
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
@@ -3326,7 +3494,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3491,6 +3659,18 @@
{
"type": "object"
},
{
"type": "array"
},
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
@@ -3541,7 +3721,7 @@
"type": "string",
"maxLength": 65536,
"minLength": 1,
"format": "uri",
"format": "uri-reference",
"title": "Webhook",
"description": "Webhook to call after LangGraph API call is done."
},
@@ -3840,6 +4020,36 @@
"title": "If Exists",
"description": "How to handle duplicate creation. Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing thread).",
"default": "raise"
},
"ttl": {
"type": "object",
"title": "TTL",
"description": "The time-to-live for the thread.",
"properties": {
"strategy": {
"type": "string",
"enum": ["delete"],
"description": "The TTL strategy. 'delete' removes the entire thread.",
"default": "delete"
},
"ttl": {
"type": "number",
"description": "The time-to-live in minutes from now until thread should be swept."
}
}
},
"supersteps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"updates": {
"type": "array",
"items": { "$ref": "#/components/schemas/ThreadSuperstepUpdate" }
}
},
"required": ["updates"]
}
}
},
"type": "object",
@@ -4028,6 +4238,43 @@
"title": "ThreadStateUpdate",
"description": "Payload for updating the state of a thread."
},
"ThreadSuperstepUpdate": {
"properties": {
"values": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object"
}
},
{
"type": "object"
},
{
"type": "null"
}
]
},
"command": {
"anyOf": [
{
"$ref": "#/components/schemas/Command"
},
{
"type": "null"
}
],
"description": "The command associated with the update."
},
"as_node": {
"type": "string",
"description": "Update the state as if this node had just executed."
}
},
"required": ["as_node"],
"type": "object"
},
"ThreadStateUpdateResponse": {
"properties": {
"checkpoint": {
@@ -4230,6 +4477,42 @@
},
"description": "Represents a single document or data entry in the graph's Store. Items are used to store cross-thread memories."
},
"RunsCancel": {
"type": "object",
"title": "RunsCancel",
"description": "Payload for cancelling runs.",
"properties": {
"status": {
"type": "string",
"enum": ["pending", "running", "all"],
"title": "Status",
"description": "Filter runs by status to cancel. Must be one of 'pending', 'running', or 'all'."
},
"thread_id": {
"type": "string",
"format": "uuid",
"title": "Thread Id",
"description": "The ID of the thread containing runs to cancel."
},
"run_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Run Ids",
"description": "List of run IDs to cancel."
}
},
"oneOf": [
{
"required": ["status"]
},
{
"required": ["thread_id", "run_ids"]
}
]
},
"SearchItemsResponse": {
"type": "object",
"required": [
+64 -6
View File
@@ -29,7 +29,7 @@ The LangGraph command line interface includes commands to build and run a LangGr
## Configuration File {#configuration-file}
The LangGraph CLI requires a JSON configuration file with the following keys:
The LangGraph CLI requires a JSON configuration file that follows this [schema](https://raw.githubusercontent.com/langchain-ai/langgraph/refs/heads/main/libs/cli/schemas/schema.json). It contains the following properties:
<div class="admonition tip">
<p class="admonition-title">Note</p>
@@ -42,15 +42,16 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. |
| <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;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search to the BaseStore. Contains the following fields: <ul><li>`index`: Configuration for semantic search indexing with fields:<ul><li>`embed`: Embedding provider (e.g., "openai:text-embedding-3-small") or path to custom embedding function</li><li>`dims`: Dimension size of the embedding model. Used to initialize the vector table.</li><li>`fields` (optional): List of fields to index. Defaults to `["$"]`, which means to index entire documents. Can be specific fields like `["text", "summary", "some.value"]`</li></ul></li></ul> |
| <span style="white-space: nowrap;">`python_version`</span> | `3.11` or `3.12`. Defaults to `3.11`. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields: <ul><li>`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.</li><li>`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**, defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).</li></ul> |
| <span style="white-space: nowrap;">`python_version`</span> | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li></ul> |
=== "JS"
@@ -59,9 +60,10 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <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>`./src/graph.ts:variable`, where `variable` is an instance of `CompiledStateGraph`</li><li>`./src/graph.ts:makeGraph`, where `makeGraph` is a function that takes a config dictionary (`LangGraphRunnableConfig`) and creates an instance of `StateGraph` / `CompiledStateGraph`.</li></ul> |
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search to the BaseStore. Contains the following fields: <ul><li>`index`: Configuration for semantic search indexing with fields:<ul><li>`embed`: Embedding provider (e.g., "openai:text-embedding-3-small") or path to custom embedding function</li><li>`dims`: Dimension size of the embedding model. Used to initialize the vector table.</li><li>`fields` (optional): List of fields to index. Defaults to `["$"]`, which means to index entire documents. Can be specific fields like `["text", "summary", "some.value"]`</li></ul></li></ul> |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields: <ul><li>`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.</li><li>`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**, defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).</li></ul> |
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
### Examples
@@ -82,7 +84,7 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](../deployment/semantic_search.md) within the BaseStore of your deployment.
The `fields` configuration determines which parts of your documents to embed:
The `index.fields` configuration determines which parts of your documents to embed:
- If omitted or set to `["$"]`, the entire document will be embedded
- To embed specific fields, use JSON path notation: `["metadata.title", "content.text"]`
@@ -171,6 +173,62 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
See the [authentication conceptual guide](../../concepts/auth.md) for details, and the [setting up custom authentication](../../tutorials/auth/getting_started.md) guide for a practical walk through of the process.
#### Configuring Store Item Time-to-Live (TTL)
You can configure default data expiration for items/memories in the BaseStore using the `store.ttl` key. This determines how long items are retained after they are last accessed (with reads potentially refreshing the timer based on `refresh_on_read`). Note that these defaults can be overwritten on a per-call basis by modifying the corresponding arguments in `get`, `search`, etc.
The `ttl` configuration is an object containing optional fields:
- `refresh_on_read`: If `true` (the default), accessing an item via `get` or `search` resets its expiration timer. Set to `false` to only refresh TTL on writes (`put`).
- `default_ttl`: The default lifespan of an item in **minutes**. If not set, items do not expire by default.
- `sweep_interval_minutes`: How frequently (in minutes) the system should run a background process to delete expired items. If not set, sweeping does not occur automatically.
Here is an example enabling a 7-day TTL (10080 minutes), refreshing on reads, and sweeping every hour:
```json
{
"dependencies": ["."],
"graphs": {
"memory_agent": "./agent/graph.py:graph"
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 60,
"default_ttl": 10080
}
}
}
```
#### Configuring Checkpoint Time-to-Live (TTL)
You can configure the time-to-live (TTL) for checkpoints using the `checkpointer` key. This determines how long checkpoint data is retained before being automatically handled according to the specified strategy (e.g., deletion). The `ttl` configuration is an object containing:
- `strategy`: The action to take on expired checkpoints (currently `"delete"` is the only accepted option).
- `sweep_interval_minutes`: How frequently (in minutes) the system checks for expired checkpoints.
- `default_ttl`: The default lifespan of a checkpoint in **minutes**.
Here's an example setting a default TTL of 30 days (43200 minutes):
```json
{
"dependencies": ["."],
"graphs": {
"chat": "./chat/graph.py:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 10,
"default_ttl": 43200
}
}
}
```
In this example, checkpoints older than 30 days will be deleted, and the check runs every 10 minutes.
=== "JS"
+26
View File
@@ -2,6 +2,22 @@
The LangGraph Cloud Server supports specific environment variables for configuring a deployment.
## `BG_JOB_ISOLATED_LOOPS`
Set `BG_JOB_ISOLATED_LOOPS` to `True` to execute background runs in an isolated event loop separate from the serving API event loop.
This environment variable should be set to `True` if the implementation of a graph/node contains synchronous code. In this situation, the synchronous code will block the serving API event loop, which may cause the API to be unavailable. A symptom of an unavailable API is continuous application restarts due to failing health checks.
Defaults to `False`.
## `BG_JOB_TIMEOUT_SECS`
The timeout of a background run can be increased. However, the infrastructure for a Cloud SaaS deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
A background run can execute for longer than 1 hour, but a client must reconnect to the server (e.g. join stream via `POST /threads/{thread_id}/runs/{run_id}/stream`) to retrieve output from the run if the run is taking longer than 1 hour.
Defaults to `3600`.
## `DD_API_KEY`
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
@@ -28,6 +44,10 @@ Set this environment variable to have a BYOC deployment send traces to a self-ho
`SELF_HOSTED_LANGSMITH_HOSTNAME` is the hostname of the self-hosted LangSmith instance. It must be accessible to the BYOC deployment. `LANGSMITH_API_KEY` is a LangSmith API generated from the self-hosted LangSmith instance.
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
## `N_JOBS_PER_WORKER`
Number of jobs per worker for the LangGraph Cloud task queue. Defaults to `10`.
@@ -55,3 +75,9 @@ Database Connectivity:
- The externally managed Postgres instance must be accessible by the LangGraph Server service in the ECS cluster. The BYOC user is responsible for ensuring connectivity.
- For example, if an AWS RDS Postgres instance is provisioned, it can be provisioned in the same VPC (`langgraph-cloud-vpc`) as the ECS cluster with the `langgraph-cloud-service-sg` security group to ensure connectivity.
## `REDIS_URI_CUSTOM`
For [Bring Your Own Cloud (BYOC)](../../concepts/bring_your_own_cloud.md) deployments only.
Specify `REDIS_URI_CUSTOM` to use an externally managed Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
+3 -6
View File
@@ -2,10 +2,6 @@
LangGraph Platform provides a flexible authentication and authorization system that can integrate with most authentication schemes.
!!! note "Python only"
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon.
## Core Concepts
### Authentication vs Authorization
@@ -146,7 +142,7 @@ The returned user information is available:
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](##supported-actions) for the list of types the value can take for each action.
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
3. Raise an HTTP exception if access is denied.
@@ -289,7 +285,7 @@ async def on_assistant_create(
)
```
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action. Requests to create, update,
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action.
### Filter Operations {#filter-operations}
@@ -423,6 +419,7 @@ Here are all the supported action handlers:
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) |
???+ note "About Runs"
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
+35 -60
View File
@@ -10,90 +10,65 @@
There are 4 main options for deploying with the LangGraph Platform:
1. **[Self-Hosted Lite](#self-hosted-lite)**: Available for all plans.
1. **[Cloud SaaS](#cloud-saas)**: Available for **Plus** and **Enterprise** plans.
2. **[Self-Hosted Enterprise](#self-hosted-enterprise)**: Available for the **Enterprise** plan.
1. **[Self-Hosted Data Plane](#self-hosted-data-plane)**: Available for the **Enterprise** plan.
3. **[Cloud SaaS](#cloud-saas)**: Available for **Plus** and **Enterprise** plans.
1. **[Self-Hosted Control Plane](#self-hosted-control-plane)**: Available for the **Enterprise** plan.
4. **[Bring Your Own Cloud](#bring-your-own-cloud)**: Available only for **Enterprise** plans and **only on AWS**.
1. **[Standalone Container](#standalone-container)**: Available for all plans.
Please see the [LangGraph Platform Plans](./plans.md) for more information on the different plans.
The guide below will explain the differences between the deployment options.
## Self-Hosted Enterprise
!!! important
The Self-Hosted Enterprise version is only available for the **Enterprise** plan.
!!! warning "Note"
The LangGraph Platform Deployments view is optionally available for Self-Hosted Enterprise LangGraph deployments. With one click, self-hosted LangGraph deployments can be deployed in the same Kubernetes cluster where a self-hosted LangSmith instance is deployed.
With a Self-Hosted Enterprise deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
Youll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure.
For more information, please see:
* [Self-Hosted conceptual guide](./self_hosted.md)
* [Self-Hosted Deployment how-to guide](../how-tos/deploy-self-hosted.md)
## Self-Hosted Lite
!!! important
The Self-Hosted Lite version is available for all plans.
!!! warning "Note"
The LangGraph Platform Deployments view is optionally available for Self-Hosted Lite LangGraph deployments. With one click, self-hosted LangGraph deployments can be deployed in the same Kubernetes cluster where a self-hosted LangSmith instance is deployed.
The Self-Hosted Lite deployment option is a free (up to 1 million nodes executed per year), limited version of LangGraph Platform that you can run locally or in a self-hosted manner.
With a Self-Hosted Lite deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances.
Youll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure.
[Cron jobs](../cloud/how-tos/cron_jobs.md) are not available for Self-Hosted Lite deployments.
For more information, please see:
* [Self-Hosted conceptual guide](./self_hosted.md)
* [Self-Hosted deployment how-to guide](../how-tos/deploy-self-hosted.md)
## Cloud SaaS
!!! important
The [Cloud SaaS](./langgraph_cloud.md) deployment option is a fully managed model for deployment where we manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in our cloud. This option provides a simple way to deploy and manage your LangGraph Servers.
The Cloud SaaS version of LangGraph Platform is only available for **Plus** and **Enterprise** plans.
The [Cloud SaaS](./langgraph_cloud.md) version of LangGraph Platform is hosted as part of [LangSmith](https://smith.langchain.com/).
The Cloud SaaS version of LangGraph Platform provides a simple way to deploy and manage your LangGraph applications.
This deployment option provides access to the LangGraph Platform UI (within LangSmith) and an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub.
Connect your GitHub repositories to the platform and deploy your LangGraph Servers from the [Control Plane UI](./langgraph_control_plane.md#control-plane-ui). The build process (i.e. CI/CD) is managed internally by the platform.
For more information, please see:
* [Cloud SaaS Conceptual Guide](./langgraph_cloud.md)
* [How to deploy to Cloud SaaS](../cloud/deployment/cloud.md)
## Self-Hosted Data Plane
## Bring Your Own Cloud
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployemnt where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
!!! important
Build a Docker image using the [LangGraph CLI](./langgraph_cli.md) and deploy your LangGraph Server from the [Control Plane UI](./langgraph_control_plane.md#control-plane-ui).
The Bring Your Own Cloud version of LangGraph Platform is only available for **Enterprise** plans.
Supported Compute Platforms: [Kubernetes](https://kubernetes.io/), [Amazon ECS](https://aws.amazon.com/ecs/) (coming soon!)
For more information, please see:
This combines the best of both worlds for Cloud and Self-Hosted. Create your deployments through the LangGraph Platform UI (within LangSmith) and we manage the infrastructure so you don't have to. The infrastructure all runs within your cloud. This is currently only available on AWS.
* [Self-Hosted Data Plane Conceptual Guide](./langgraph_self_hosted_data_plane.md)
* [How to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md)
For more information please see:
## Self-Hosted Control Plane
* [Bring Your Own Cloud Conceptual Guide](./bring_your_own_cloud.md)
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option give you full control and responsibility of the control plane and data plane infrastructure.
Build a Docker image using the [LangGraph CLI](./langgraph_cli.md) and deploy your LangGraph Server from the [Control Plane UI](./langgraph_control_plane.md#control-plane-ui).
Supported Compute Platforms: [Kubernetes](https://kubernetes.io/)
For more information, please see:
* [Self-Hosted Control Plane Conceptual Guide](./langgraph_self_hosted_control_plane.md)
* [How to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md)
## Standalone Container
The [Standalone Container](./langgraph_standalone_container.md) deployment option is the least restrictive model for deployment. Deploy standalone instances of a LangGraph Server in your cloud.
Build a Docker image using the [LangGraph CLI](./langgraph_cli.md) and deploy your LangGraph Server using the container deployment tooling of your choice. Images can be deployed to any compute platform.
For more information, please see:
* [Sandalone Container Conceptual Guide](./langgraph_standalone_container.md)
* [How to deploy a Standalone Container](../cloud/deployment/standalone_container.md)
## Related
+2
View File
@@ -23,9 +23,11 @@ This provides a minimal abstraction for building workflows with state management
Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review.
```python
from langgraph.checkpoint.memory import MemorySaver
from langgraph.func import entrypoint, task
from langgraph.types import interrupt
@task
def write_essay(topic: str) -> str:
"""Write an essay about the given topic."""
Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 KiB

+7 -5
View File
@@ -49,7 +49,7 @@ The LangGraph Platform offers a few different deployment options described in th
- [Why LangGraph Platform?](./langgraph_platform.md): The LangGraph platform is an opinionated way to deploy and manage LangGraph applications. This guide provides an overview of the key features and concepts behind LangGraph Platform.
- [Platform Architecture](./platform_architecture.md): A high-level overview of the architecture of the LangGraph Platform.
- [Scalability and Resilience](./scalability_and_resilience.md): LangGraph Platform is designed to be scalable and resilient. This document explains how the platform achieves this.
- [Deployment Options](./deployment_options.md): LangGraph Platform offers four deployment options: [Self-Hosted Lite](./self_hosted.md#self-hosted-lite), [Self-Hosted Enterprise](./self_hosted.md#self-hosted-enterprise), [bring your own cloud (BYOC)](./bring_your_own_cloud.md), and [Cloud SaaS](./langgraph_cloud.md). This guide explains the differences between these options, and which Plans they are available on.
- [Deployment Options](./deployment_options.md): LangGraph Platform offers four deployment options: [Cloud SaaS](./langgraph_cloud.md), [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md), [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md), and [Standalone Container](./langgraph_standalone_container.md). This guide explains the differences between these options, and which Plans they are available on.
- [Plans](./plans.md): LangGraph Platforms offer three different plans: Developer, Plus, Enterprise. This guide explains the differences between these options, what deployment options are available for each, and how to sign up for each one.
- [Template Applications](./template_applications.md): Reference applications designed to help you get started quickly when building with LangGraph.
@@ -62,6 +62,8 @@ The LangGraph Platform comprises several components that work together to suppor
- [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph
- [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications.
- [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally.
- [LangGraph Control Plane](./langgraph_control_plane.md): The LangGraph Control Plane refers to the Control Plane UI where users create and update LangGraph Servers and the Control Plane APIs that support the UI experience.
- [LangGraph Data Plane](./langgraph_data_plane.md): The LangGraph Data Plane refers to LangGraph Servers, the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the LangGraph Control Plane.
### LangGraph Server
@@ -74,7 +76,7 @@ The LangGraph Platform comprises several components that work together to suppor
### Deployment Options
- [Self-Hosted Lite](./self_hosted.md): A free (up to 1 million nodes executed per year), limited version of LangGraph Platform that you can run locally or in a self-hosted manner
- [Cloud SaaS](./langgraph_cloud.md): Hosted as part of LangSmith.
- [Bring Your Own Cloud](./bring_your_own_cloud.md): We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud.
- [Self-Hosted Enterprise](./self_hosted.md): Completely managed by you.
- [Cloud SaaS](../concepts/langgraph_cloud.md): Connect to your GitHub repositories and deploy LangGraph Servers to LangChain's cloud. We manage everything.
- [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. We manage the [control plane](../concepts/langgraph_control_plane.md), you manage the deployments.
- [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md#control-plane-ui): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md) and deploy LangGraph Servers to your cloud. You manage everything.
- [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like.
+8 -92
View File
@@ -1,101 +1,17 @@
# Cloud SaaS
!!! info "Prerequisites"
- [LangGraph Platform](./langgraph_platform.md)
- [LangGraph Server](./langgraph_server.md)
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy to Cloud SaaS](../cloud/deployment/cloud.md).
## Overview
LangGraph's Cloud SaaS is a managed service for deploying LangGraph Servers, regardless of its definition or dependencies. The service offers managed implementations of checkpointers and stores, allowing you to focus on building the right cognitive architecture for your use case. By handling scalable & secure infrastructure, LangGraph Cloud SaaS offers the fastest path to getting your LangGraph Server deployed to production.
The Cloud SaaS deployment option is a fully managed model for deployment where we manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in our cloud.
## Deployment
A **deployment** is an instance of a LangGraph Server. A single deployment can have many [revisions](#revision). When a deployment is created, all the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details.
Resource Allocation:
| **Deployment Type** | **CPU** | **Memory** | **Scaling** |
|---------------------|---------|------------|---------------------|
| Development | 1 CPU | 1 GB | Up to 1 container |
| Production | 2 CPU | 2 GB | Up to 10 containers |
See the [how-to guide](../cloud/deployment/cloud.md#create-new-deployment) for creating a new deployment.
## Revision
A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically.
See the [how-to guide](../cloud/deployment/cloud.md#create-new-revision) for creating a new revision.
## Persistence
A dedicated database is automatically created for each deployment. The database serves as the [persistence layer](../concepts/persistence.md) for the deployment.
When defining a graph to be deployed to LangGraph Cloud SaaS, a [checkpointer](../concepts/persistence.md#checkpointer-libraries) should not be configured by the user. Instead, a checkpointer is automatically configured for the graph.
There is no direct access to the database. All access to the database occurs through the LangGraph Server APIs.
## Autoscaling
`Production` type deployments automatically scale up to 10 containers. Scaling is based on the current request load for a single container. Specifically, the autoscaling implementation scales the deployment so that each container is processing about 10 concurrent requests. For example...
- If the deployment is processing 20 concurrent requests, the deployment will scale up from 1 container to 2 containers (20 requests / 2 containers = 10 requests per container).
- If a deployment of 2 containers is processing 10 requests, the deployment will scale down from 2 containers to 1 container (10 requests / 1 container = 10 requests per container).
10 concurrent requests per container is the target threshold. However, 10 concurrent requests per container is not a hard limit. The number of concurrent requests can exceed 10 if there is a sudden burst of requests.
Scale down actions are delayed for 30 minutes before any action is taken. In other words, if the autoscaling implementation decides to scale down a deployment, it will first wait for 30 minutes before scaling down. After 30 minutes, the concurrency metric is recomputed and the deployment will scale down if the concurrency metric has met the target threshold. Otherwise, the deployment remains scaled up. This "cool down" period ensures that deployments do not scale up and down too frequently.
In the future, the autoscaling implementation may evolve to accommodate other metrics such as background run queue size.
## Asynchronous Deployment
Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
- When a new deployment is created, a new database is created for the deployment. Database creation is a one-time step. This step contributes to a longer deployment time for the initial revision of the deployment.
- When a subsequent revision is created for a deployment, there is no database creation step. The deployment time for a subsequent revision is significantly faster compared to the deployment time of the initial revision.
- The deployment process for each revision contains a build step, which can take up to a few minutes.
## LangSmith Integration
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployemnt. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING_V2` and `LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set internally, automatically. Traces are created for each run and are emitted to the tracing project automatically.
When a deployment is deleted, the traces and the tracing project are not deleted.
## Automatic Deletion
Deployments are automatically deleted after 28 consecutive days of non-use (it is in an unused state). A deployment is in an unused state if there are no traces emitted to LangSmith from the deployment after 28 consecutive days. On any given day, if a deployment emits a trace to LangSmith, the counter for consecutive days of non-use is reset.
- An email notification is sent after 7 consecutive days of non-use.
- A deployment is deleted after 28 consecutive days of non-use.
!!! danger "Data Cannot Be Recovered"
After a deployment is deleted, the data (i.e. [persistence](#persistence)) from the deployment cannot be recovered.
| | [Control Plane](../concepts/langgraph_control_plane.md) | [Data Plane](../concepts/langgraph_data_plane.md) |
|-------------------|-------------------|------------|
| **What is it?** | <ul><li>Control Plane UI for creating deployments and revisions</li><li>Control Plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
| **Who provisions and manages it?** | LangChain | LangChain |
## Architecture
!!! warning "Subject to Change"
The Cloud SaaS deployment architecture may change in the future.
A high-level diagram of a Cloud SaaS deployment.
![diagram](img/langgraph_cloud_architecture.png)
## Whitelisting IP Addresses
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
| US | EU |
|----------------|----------------|
| 35.197.29.146 | 34.13.192.67 |
| 34.145.102.123 | 34.147.105.64 |
| 34.169.45.153 | 34.90.22.166 |
| 34.82.222.17 | 34.147.36.213 |
| 35.227.171.135 | 34.32.137.113 |
| 34.169.88.30 | 34.91.238.184 |
| 34.19.93.202 | 35.204.101.241 |
| 34.19.34.50 | 35.204.48.32 |
## Related
- [Deployment Options](./deployment_options.md)
![Cloud SaaS](./img/self_hosted_control_plane_architecture.png)
@@ -0,0 +1,98 @@
# LangGraph Control Plane
The term "control plane" is used broadly to refer to the Control Plane UI where users create and update [LangGraph Servers](./langgraph_server.md) (deployments) and the Control Plane APIs that support the UI experience.
When a user makes an update through the Control Plane UI, the update is stored in the control plane state. The [LangGraph Data Plane](./langgraph_data_plane.md) "listener" application polls for these updates by calling the Control Plane APIs.
## Control Plane UI
From the Control Plane UI, you can:
- View a list of outstanding deployments.
- View details of an individual deployment.
- Create a new deployment.
- Update a deployment.
- Update environment variables for a deployment.
- View build and server logs of a deployment.
- Delete a deployment.
The Control Plane UI is embedded in [LangSmith](https://docs.smith.langchain.com/langgraph_cloud).
## Control Plane API
This section describes data model of the LangGraph Control Plane API. Control Plane API is used to create, update, and delete deployments. However, they are not publicly accessible.
### Deployment
A deployment is an instance of a LangGraph Server. A single deployment can have many revisions.
### Revision
A revision is an iteration of a deployment. When a new deployment is created, an initial revision is automatically created. To deploy code changes or update environment variables for a deployment, a new revision must be created.
### Environment Variable
Environment variables are set for a deployment. All environment variables are stored as secrets (i.e. saved in a secrets store).
## Control Plane Features
This section describes various features of the control plane.
### Deployment Types
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
| **Deployment Type** | **CPU** | **Memory** | **Scaling** |
|---------------------|---------|------------|---------------------|
| Development | 1 CPU | 1 GB | Up to 1 container |
| Production | 2 CPU | 2 GB | Up to 10 containers |
CPU and memory resources are per container.
!!! info "For [Cloud SaaS](../concepts/langgraph_cloud.md)"
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
!!! info "For [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md)"
Resources for [Self-Hosted Data Plane](../concepts/langgraph_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_control_plane.md) deployments can be fully customized.
### Database Provisioning
The control plane and [LangGraph Data Plane](./langgraph_data_plane.md) "listener" application coordinate to automatically create a Postgres database for each deployment. The database serves as the [persistence layer](../concepts/persistence.md) for the deployment.
When implementing a LangGraph application, a [checkpointer](../concepts/persistence.md#checkpointer-libraries) does not need to be configured by the developer. Instead, a checkpointer is automatically configured for the graph. Any checkpointer configured for a graph will be replaced by the one that is automatically configured.
There is no direct access to the database. All access to the database occurs through the [LangGraph Server](../concepts/langgraph_server.md).
The database is never deleted until the deployment itself is deleted. See [Automatic Deletion](#automatic-deletion) for additional details.
!!! info "For [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md)"
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_control_plane.md) deployments.
### Asynchronous Deployment
Infrastructure for deployments and revisions are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
- When a new deployment is created, a new database is created for the deployment. Database creation is a one-time step. This step contributes to a longer deployment time for the initial revision of the deployment.
- When a subsequent revision is created for a deployment, there is no database creation step. The deployment time for a subsequent revision is significantly faster compared to the deployment time of the initial revision.
- The deployment process for each revision contains a build step, which can take up to a few minutes.
The control plane and [LangGraph Data Plane](./langgraph_data_plane.md) "listener" application coordinate to achieve asynchronous deployments.
### Automatic Deletion
!!! info "Only for [Cloud SaaS](../concepts/langgraph_cloud.md)"
Automatic deletion of deployments is only available for [Cloud SaaS](../concepts/langgraph_cloud.md).
The control plane automatically deletes deployments after 28 consecutive days of non-use (it is in an unused state). A deployment is in an unused state if there are no traces emitted to LangSmith from the deployment after 28 consecutive days. On any given day, if a deployment emits a trace to LangSmith, the counter for consecutive days of non-use is reset.
- An email notification is sent after 7 consecutive days of non-use.
- A deployment is deleted after 28 consecutive days of non-use.
!!! danger "Data Cannot Be Recovered"
After a deployment is deleted, the data (e.g. Postgres) from the deployment cannot be recovered.
### LangSmith Integration
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
When a deployment is deleted, the traces and the tracing project are not deleted.
@@ -0,0 +1,74 @@
# LangGraph Data Plane
The term "data plane" is used broadly to refer to [LangGraph Servers](./langgraph_server.md) (deployments), the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the [LangGraph Control Plane](./langgraph_control_plane.md).
## Server Infrastructure
In addition to the [LangGraph Server](./langgraph_server.md) itself, the following infrastructure for each server are also included in the broad definition of "data plane":
- [Postgres](../concepts/platform_architecture.md#how-we-use-postgres)
- [Redis](../concepts/platform_architecture.md#how-we-use-redis)
- Secrets store
- Autoscalers
See [LangGraph Platform Architecture](../concepts/platform_architecture.md) for more details.
## "Listener" Application
The data plane "listener" application periodically calls [Control Plane APIs](../concepts/langgraph_control_plane.md#control-plane-api) to:
- Determine if new deployments should be created.
- Determine if existing deployments should be updated (i.e. new revisions).
- Determine if existing deployments should be deleted.
In other words, the data plane "listener" reads the latest state of the control plane (desired state) and takes action to reconcile outstanding deployments (current state) to match the latest state.
## Data Plane Features
This section describes various features of the data plane.
### Lite vs Enterprise
There are two versions of the LangGraph Server: `Lite` and `Enterprise`.
The `Lite` version is a limited version of the LangGraph Server that you can run locally or in a self-hosted manner (up to 1 million nodes executed per year). `Lite` is only available for the [Standalone Container](../concepts/langgraph_standalone_container.md) deployment option.
The `Enterprise` version is the full version of the LangGraph Server. To use the `Enterprise` version, you must acquire a license key that you will need to specify when running the Docker image. To acquire a license key, please email sales@langchain.dev. `Enterprise` is available for [Cloud SaaS](../concepts/langgraph_cloud.md), [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md), and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployment options.
Feature Differences:
| | Lite | Enterprise |
|-------|------------|------------|
| [Cron Jobs](../concepts/langgraph_server.md#cron-jobs) |❌|✅|
| [Custom Authentication](../concepts/auth.md) |❌|✅|
### Autoscaling
[`Production` type](../concepts/langgraph_control_plane.md#deployment-types) deployments automatically scale up to 10 containers. Scaling is based on the current request load for a single container. Specifically, the autoscaling implementation scales the deployment so that each container is processing about 10 concurrent requests. For example...
- If the deployment is processing 20 concurrent requests, the deployment will scale up from 1 container to 2 containers (20 requests / 2 containers = 10 requests per container).
- If a deployment of 2 containers is processing 10 requests, the deployment will scale down from 2 containers to 1 container (10 requests / 1 container = 10 requests per container).
10 concurrent requests per container is the target threshold. However, 10 concurrent requests per container is not a hard limit. The number of concurrent requests can exceed 10 if there is a sudden burst of requests.
Scale down actions are delayed for 30 minutes before any action is taken. In other words, if the autoscaling implementation decides to scale down a deployment, it will first wait for 30 minutes before scaling down. After 30 minutes, the concurrency metric is recomputed and the deployment will scale down if the concurrency metric has met the target threshold. Otherwise, the deployment remains scaled up. This "cool down" period ensures that deployments do not scale up and down too frequently.
In the future, the autoscaling implementation may evolve to accommodate other metrics such as background run queue size.
### Static IP Addresses
!!! info "Only for Cloud SaaS"
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md).
All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. Refer to the table below for the list of static IP addresses:
| US | EU |
|----------------|----------------|
| 35.197.29.146 | 34.13.192.67 |
| 34.145.102.123 | 34.147.105.64 |
| 34.169.45.153 | 34.90.22.166 |
| 34.82.222.17 | 34.147.36.213 |
| 35.227.171.135 | 34.32.137.113 |
| 34.169.88.30 | 34.91.238.184 |
| 34.19.93.202 | 35.204.101.241 |
| 34.19.34.50 | 35.204.48.32 |
+2
View File
@@ -16,6 +16,8 @@ The LangGraph Platform consists of several components that work together to supp
- [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph
- [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications.
- [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally.
- [LangGraph Control Plane](./langgraph_control_plane.md): The LangGraph Control Plane refers to the Control Plane UI where users create and update LangGraph Servers and the Control Plane APIs that support the UI experience.
- [LangGraph Data Plane](./langgraph_data_plane.md): The LangGraph Data Plane refers to LangGraph Servers, the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the LangGraph Control Plane.
![](img/lg_platform.png)
@@ -0,0 +1,23 @@
# Self-Hosted Control Plane
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md).
## Overview
The Self-Hosted Control Plane deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud (this option implies that the data plane is self-hosted).
| | [Control Plane](../concepts/langgraph_control_plane.md) | [Data Plane](../concepts/langgraph_data_plane.md) |
|-------------------|-------------------|------------|
| **What is it?** | <ul><li>Control Plane UI for creating deployments and revisions</li><li>Control Plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
| **Where is it hosted?** | Your cloud | Your cloud |
| **Who provisions and manages it?** | You | You |
## Architecture
![Self-Hosted Control Plane Architecture](./img/self_hosted_control_plane_architecture.png)
## Compute Platforms
### Kubernetes
The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
@@ -0,0 +1,27 @@
# Self-Hosted Data Plane
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md).
## Overview
LangGraph Platform's Self-Hosted Data Plane deployment option is a "hybrid" model for deployemnt where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud.
| | [Control Plane](../concepts/langgraph_control_plane.md) | [Data Plane](../concepts/langgraph_data_plane.md) |
|-------------------|-------------------|------------|
| **What is it?** | <ul><li>Control Plane UI for creating deployments and revisions</li><li>Control Plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
| **Where is it hosted?** | LangChain's cloud | Your cloud |
| **Who provisions and manages it?** | LangChain | You |
## Architecture
![Self-Hosted Data Plane Architecture](./img/self_hosted_data_plane_architecture.png)
## Compute Platforms
### Kubernetes
The Self-Hosted Data Plane deployment option supports deploying data plane infrastructure to any Kubernetes cluster.
### Amazon ECS
Coming soon...
@@ -0,0 +1,27 @@
# Standalone Container
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy a Standalone Container](../cloud/deployment/standalone_container.md).
## Overview
The Standalone Container deployment option is the least restrictive model for deployment. There is no [control plane](./langgraph_control_plane.md). [Data plane](./langgraph_data_plane.md) infrastructure is managed by you.
| | [Control Plane](../concepts/langgraph_control_plane.md) | [Data Plane](../concepts/langgraph_data_plane.md) |
|-------------------|-------------------|------------|
| **What is it?** | n/a | <ul><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
| **Where is it hosted?** | n/a | Your cloud |
| **Who provisions and manages it?** | n/a | You |
## Architecture
![Standalone Container](./img/langgraph_platform_deployment_architecture.png)
## Compute Platforms
### Kubernetes
The Standalone Container deployment option supports deploying data plane infrastructure to a Kubernetes cluster.
### Docker
The Standalone Container deployment option supports deploying data plane infrastructure to any Docker-supported compute platform.
+1 -1
View File
@@ -50,7 +50,7 @@ def agent(state) -> Command[Literal["agent", "another_agent"]]:
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./low_level.md#subgraphs)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph=Command.PARENT` in the `Command` object:
```python
def some_node_inside_alice(state)
def some_node_inside_alice(state):
return Command(
goto="bob",
update={"my_state_key": "my_state_value"},
+14 -5
View File
@@ -4,6 +4,10 @@ LangGraph has a built-in persistence layer, implemented through checkpointers. W
![Checkpoints](img/persistence/checkpoints.jpg)
!!! info "LangGraph API handles checkpointing automatically"
When using the LangGraph API, you don't need to implement or configure checkpointers manually. The API handles all persistence infrastructure for you behind the scenes.
## Threads
A thread is a unique ID or [thread identifier](#threads) assigned to each checkpoint saved by a checkpointer. When invoking graph with a checkpointer, you **must** specify a `thread_id` as part of the `configurable` portion of the config:
@@ -26,7 +30,7 @@ Let's see what checkpoints are saved when a simple graph is invoked as follows:
```python
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.memory import InMemorySaver
from typing import Annotated
from typing_extensions import TypedDict
from operator import add
@@ -49,7 +53,7 @@ workflow.add_edge(START, "node_a")
workflow.add_edge("node_a", "node_b")
workflow.add_edge("node_b", END)
checkpointer = MemorySaver()
checkpointer = InMemorySaver()
graph = workflow.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
@@ -223,6 +227,10 @@ But, what if we want to retain some information *across threads*? Consider the c
With checkpointers alone, we cannot share information across threads. This motivates the need for the [`Store`](../reference/store.md#langgraph.store.base.BaseStore) interface. As an illustration, we can define an `InMemoryStore` to store information about a user across threads. We simply compile our graph with a checkpointer, as before, and with our new `in_memory_store` variable.
!!! info "LangGraph API handles stores automatically"
When using the LangGraph API, you don't need to implement or configure stores manually. The API handles all storage infrastructure for you behind the scenes.
### Basic Usage
First, let's showcase this in isolation without using LangGraph.
@@ -324,10 +332,10 @@ store.put(
With this all in place, we use the `in_memory_store` in LangGraph. The `in_memory_store` works hand-in-hand with the checkpointer: the checkpointer saves state to threads, as discussed above, and the `in_memory_store` allows us to store arbitrary information for access *across* threads. We compile the graph with both the checkpointer and the `in_memory_store` as follows.
```python
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.memory import InMemorySaver
# We need this because we want to enable threads (conversations)
checkpointer = MemorySaver()
checkpointer = InMemorySaver()
# ... Define the graph ...
@@ -440,6 +448,7 @@ Under the hood, checkpointing is powered by checkpointer objects that conform to
* `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 Cloud. Ideal for using in production. Needs to be installed separately.
### Checkpointer interface
Each checkpointer conforms to [BaseCheckpointSaver][langgraph.checkpoint.base.BaseCheckpointSaver] interface and implements the following methods:
@@ -452,7 +461,7 @@ Each checkpointer conforms to [BaseCheckpointSaver][langgraph.checkpoint.base.Ba
If the checkpointer is used with asynchronous graph execution (i.e. executing the graph via `.ainvoke`, `.astream`, `.abatch`), asynchronous versions of the above methods will be used (`.aput`, `.aput_writes`, `.aget_tuple`, `.alist`).
!!! note Note
For running your graph asynchronously, you can use `MemorySaver`, or async versions of Sqlite/Postgres checkpointers -- `AsyncSqliteSaver` / `AsyncPostgresSaver` checkpointers.
For running your graph asynchronously, you can use `InMemorySaver`, or async versions of Sqlite/Postgres checkpointers -- `AsyncSqliteSaver` / `AsyncPostgresSaver` checkpointers.
### Serializer
-4
View File
@@ -9,10 +9,6 @@
For a more guided walkthrough, see [**setting up custom authentication**](../../tutorials/auth/getting_started.md) tutorial.
???+ note "Python only"
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.11`. Support for LangGraph.JS will be added soon.
???+ note "Support by deployment type"
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.
File diff suppressed because one or more lines are too long
+8 -3
View File
@@ -163,6 +163,7 @@ These guides show how to use the prebuilt ReAct agent:
- [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb)
- [How to return structured output from a ReAct agent](create-react-agent-structured-output.ipynb)
- [How to add semantic search for long-term memory to a ReAct agent](memory/semantic-search.ipynb#using-in-create-react-agent)
- [How to manage message history in a ReAct agent](create-react-agent-manage-message-history.ipynb)
Interested in further customizing the ReAct agent? This guide provides an
overview of its underlying implementation to help you customize for your own needs:
@@ -201,11 +202,14 @@ Learn how to set up your app for deployment to LangGraph Platform:
### Deployment
LangGraph applications can be deployed using LangGraph Cloud, which provides a range of services to help you deploy, manage, and scale your applications.
LangGraph applications can be deployed using LangGraph Platform, which provides a range of services to help you deploy, manage, and scale your applications.
- [How to deploy to LangGraph cloud](../cloud/deployment/cloud.md)
- [How to deploy to a self-hosted environment](./deploy-self-hosted.md)
- [How to deploy to Cloud SaaS](../cloud/deployment/cloud.md)
- [How to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md)
- [How to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md)
- [How to deploy a Standalone Container](../cloud/deployment/standalone_container.md)
- [How to interact with the deployment using RemoteGraph](./use-remote-graph.md)
- [How to add TTLs to your LangGraph application](./ttl/configure_ttl.md)
### Authentication & Access Control
@@ -300,6 +304,7 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a
- [How to interact with threads in LangGraph Studio](../cloud/how-tos/threads_studio.md)
- [How to add nodes as dataset examples in LangGraph Studio](../cloud/how-tos/datasets_studio.md)
- [How to engineer prompts in LangGraph Studio](../cloud/how-tos/iterate_graph_studio.md)
- [How to test your agent against remote traces](../cloud/how-tos/clone_traces_studio.md)
## Troubleshooting
@@ -16,6 +16,10 @@
" - [Memory](../../concepts/memory/)\n",
" - [Chat Models](https://python.langchain.com/docs/concepts/chat_models/)\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",
"Many AI applications need memory to share context across multiple interactions on the same [thread](../../concepts/persistence#threads) (e.g., multiple turns of a conversation). In LangGraph functional API, this kind of memory can be added to any [entrypoint()][langgraph.func.entrypoint] workflow using [thread-level persistence](https://langchain-ai.github.io/langgraph/concepts/persistence).\n",
"\n",
"When creating a LangGraph workflow, you can set it up to persist its results by using a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver):\n",
+4
View File
@@ -31,6 +31,10 @@
" </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",
"Many AI applications need memory to share context across multiple interactions. In LangGraph, this kind of memory can be added to any [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) using [thread-level persistence](https://langchain-ai.github.io/langgraph/concepts/persistence) .\n",
"\n",
"When creating any LangGraph graph, you can set it up to persist its state by adding a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver) when compiling the graph:\n",
+5 -1
View File
@@ -26,6 +26,10 @@
" </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",
@@ -44,7 +48,7 @@
"...\n",
"```\n",
"\n",
"!!! info \"Setup\"",
"!!! info \"Setup\"\n",
"\n",
" You need to run `.setup()` once on your checkpointer to initialize the database before you can use it."
]
+102
View File
@@ -0,0 +1,102 @@
# How to add TTLs to your LangGraph application
!!! tip "Prerequisites"
This guide assumes familiarity with the [LangGraph Platform](../../concepts/index.md#langgraph-platform), [Persistence](../../concepts/persistence.md), and [Cross-thread persistence](../../concepts/persistence.md#memory-store) concepts.
???+ note "LangGraph platform only"
TTLs are only supported for LangGraph platform deployments. This guide does not apply to LangGraph OSS.
The LangGraph Platform persists both [checkpoints](../../concepts/persistence.md#checkpoints) (thread state) and [cross-thread memories](../../concepts/persistence.md#memory-store) (store items). Configure Time-to-Live (TTL) policies in `langgraph.json` to automatically manage the lifecycle of this data, preventing indefinite accumulation.
## Configuring Checkpoint TTL
Checkpoints capture the state of conversation threads. Setting a TTL ensures old checkpoints and threads are automatically deleted.
Add a `checkpointer.ttl` configuration to your `langgraph.json` file:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 60,
"default_ttl": 43200
}
}
}
```
* `strategy`: Specifies the action taken on expiration. Currently, only `"delete"` is supported, which deletes all checkpoints in the thread upon expiration.
* `sweep_interval_minutes`: Defines how often, in minutes, the system checks for expired checkpoints.
* `default_ttl`: Sets the default lifespan of checkpoints in minutes (e.g., 43200 minutes = 30 days).
## Configuring Store Item TTL
Store items allow cross-thread data persistence. Configuring TTL for store items helps manage memory by removing stale data.
Add a `store.ttl` configuration to your `langgraph.json` file:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 120,
"default_ttl": 10080
}
}
}
```
* `refresh_on_read`: (Optional, default `true`) If `true`, accessing an item via `get` or `search` resets its expiration timer. If `false`, TTL only refreshes on `put`.
* `sweep_interval_minutes`: (Optional) Defines how often, in minutes, the system checks for expired items. If omitted, no sweeping occurs.
* `default_ttl`: (Optional) Sets the default lifespan of store items in minutes (e.g., 10080 minutes = 7 days). If omitted, items do not expire by default.
## Combining TTL Configurations
You can configure TTLs for both checkpoints and store items in the same `langgraph.json` file to set different policies for each data type. Here is an example:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./agent.py:graph"
},
"checkpointer": {
"ttl": {
"strategy": "delete",
"sweep_interval_minutes": 60,
"default_ttl": 43200
}
},
"store": {
"ttl": {
"refresh_on_read": true,
"sweep_interval_minutes": 120,
"default_ttl": 10080
}
}
}
```
## Runtime Overrides
The default `store.ttl` settings from `langgraph.json` can be overridden at runtime by providing specific TTL values in SDK method calls like `get`, `put`, and `search`.
## Deployment Process
After configuring TTLs in `langgraph.json`, deploy or restart your LangGraph application for the changes to take effect. Use `langgraph dev` for local development or `langgraph up` for Docker deployment.
See the [langgraph.json CLI reference][configuration-file] for more details on the other configurable options.
+4 -1
View File
@@ -1,6 +1,6 @@
---
hide_comments: true
title: Home
title: LangGraph
---
<script>
@@ -23,6 +23,9 @@ title: Home
.md-content h1 {
display: none;
}
.md-header__topic {
display: none;
}
</style>
{!../README.md!}
+19 -4
View File
@@ -1,13 +1,20 @@
# LLMs-txt for LangGraph
# LLMs-txt Overview
## Overview
LangGraph provides documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
Below you can find a list of documentation files in the [`llms.txt`](https://llmstxt.org/) format, specifically `llms.txt` and `llms-full.txt`. These files allow large language models (LLMs) and agents to access programming documentation and APIs, particularly useful within integrated development environments (IDEs).
| Language Version | llms.txt | llms-full.txt |
|------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
| LangGraph Python | [https://langchain-ai.github.io/langgraph/llms.txt](https://langchain-ai.github.io/langgraph/llms.txt) | [https://langchain-ai.github.io/langgraph/llms-full.txt](https://langchain-ai.github.io/langgraph/llms-full.txt) |
| LangGraph JS | [https://langchain-ai.github.io/langgraphjs/llms.txt](https://langchain-ai.github.io/langgraphjs/llms.txt) | [https://langchain-ai.github.io/langgraphjs/llms-full.txt](https://langchain-ai.github.io/langgraphjs/llms-full.txt) |
| LangChain Python | [https://python.langchain.com/llms.txt](https://python.langchain.com/llms.txt) | N/A |
| LangChain JS | [https://js.langchain.com/llms.txt](https://js.langchain.com/llms.txt) | N/A |
!!! info "Review the output"
Even with access to up-to-date documentation, current state-of-the-art models may not always generate correct code. Treat the generated code as a starting point, and always review it before shipping
code to production.
## Differences Between `llms.txt` and `llms-full.txt`
@@ -19,9 +26,17 @@ A key consideration when using `llms-full.txt` is its size. For extensive docume
## Using `llms.txt` via an MCP Server
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can utilize `llms.txt` effectively through an MCP server.
As of March 9, 2025, IDEs [do not yet have robust native support for `llms.txt`](https://x.com/jeremyphoward/status/1902109312216129905?t=1eHFv2vdNdAckajnug0_Vw&s=19). However, you can still use `llms.txt` effectively through an MCP server.
We provide an MCP server specifically designed to serve documentation, called [`mcpdoc`](https://github.com/langchain-ai/mcpdoc). This setup is compatible with IDEs and platforms such as Cursor, Windsurf, Claude, and Claude Code. Instructions for using `mcpdoc` with these tools are available in the repository.
### 🚀 Use the `mcpdoc` Server
We provide an **MCP server** that was designed to serve documentation for LLMs and IDEs:
👉 **[langchain-ai/mcpdoc GitHub Repository](https://github.com/langchain-ai/mcpdoc)**
This MCP server allows integrating `llms.txt` into tools like **Cursor**, **Windsurf**, **Claude**, and **Claude Code**.
📘 **Setup instructions and usage examples** are available in the repository.
## Using `llms-full.txt`
@@ -0,0 +1,38 @@
:root {
--md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 16h-4.2l-.8.8-2 2-2-2-.8-.8H5V4h14z"/><path d="M11 15h2v2h-2v-2m0-10h2v8h-2V5"/></svg>');
--md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 16h-4.2l-.8.8-2 2-2-2-.8-.8H5V4h14z"/><path d="M15 11h-2V9h-2v2H9v2h2v2h2v-2h2v-2Z"/></svg>');
}
.md-typeset .admonition.version-added,
.md-typeset details.version-added {
border-color: rgb(0, 191, 165);
}
.md-typeset .version-added > .admonition-title,
.md-typeset .version-added > summary {
background-color: rgba(0, 191, 165, 0.1);
}
.md-typeset .version-added > .admonition-title::before,
.md-typeset .version-added > summary::before {
background-color: rgb(0, 191, 165);
-webkit-mask-image: var(--md-admonition-icon--version-added);
mask-image: var(--md-admonition-icon--version-added);
}
.md-typeset .admonition.version-changed,
.md-typeset details.version-changed {
border-color: rgb(100, 221, 23);
}
.md-typeset .version-changed > .admonition-title,
.md-typeset .version-changed > summary {
background-color: rgba(100, 221, 23, 0.1);
}
.md-typeset .version-changed > .admonition-title::before,
.md-typeset .version-changed > summary::before {
background-color: rgb(100, 221, 23);
-webkit-mask-image: var(--md-admonition-icon--version-changed);
mask-image: var(--md-admonition-icon--version-changed);
}
+15 -4
View File
@@ -17,7 +17,18 @@ Get started deploying your LangGraph applications locally or on the cloud with
## Deployment Options
- [Self-Hosted Lite](../concepts/self_hosted.md): A free (up to 1 million nodes executed per year), limited version of LangGraph Platform that you can run locally or in a self-hosted manner
- [Cloud SaaS](../concepts/langgraph_cloud.md): Hosted as part of LangSmith.
- [Bring Your Own Cloud](../concepts/bring_your_own_cloud.md): We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud.
- [Self-Hosted Enterprise](../concepts/self_hosted.md): Completely managed by you.
- [Cloud SaaS](../concepts/langgraph_cloud.md): Connect to your GitHub repositories and deploy LangGraph Servers to LangChain's cloud. We manage everything.
- [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. We manage the [control plane](../concepts/langgraph_control_plane.md), you manage the deployments.
- [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md#control-plane-ui): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md) and deploy LangGraph Servers to your cloud. You manage everything.
- [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like.
A quick comparison...
| | **Cloud SaaS** | **Self-Hosted [Data Plane](../concepts/langgraph_data_plane.md)** | **Self-Hosted [Control Plane](../concepts/langgraph_control_plane.md)** | **Standalone Container** |
|----------------------|----------------|----------------------------|-------------------------------|--------------------------|
| **[Control Plane UI/API](../concepts/langgraph_control_plane.md)** | Yes | Yes | Yes | No |
| **CI/CD** | Managed internally by platform | Managed externally by you | Managed externally by you | Managed externally by you |
| **Data/Compute Residency** | LangChains cloud | Your cloud | Your cloud | Your cloud |
| **Required Permissions** | None | See details [here](). | See details [here](). | None |
| **LangSmith Compatibility** | Trace to LangSmith SaaS | Trace to LangSmith SaaS | Trace to Self-Hosted LangSmith | Optional tracing |
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Developer |
-1
View File
@@ -279,7 +279,6 @@
" if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
" print(\"Goodbye!\")\n",
" break\n",
"\n",
" stream_graph_updates(user_input)\n",
" except:\n",
" # fallback if input() is not available\n",
+16 -2
View File
@@ -185,6 +185,7 @@ nav:
- how-tos/create-react-agent-system-prompt.ipynb
- how-tos/create-react-agent-hitl.ipynb
- how-tos/create-react-agent-structured-output.ipynb
- how-tos/create-react-agent-manage-message-history.ipynb
- how-tos/react-agent-from-scratch.ipynb
- how-tos/react-agent-from-scratch-functional.ipynb
- LangGraph Platform:
@@ -202,8 +203,14 @@ nav:
- Deployment:
- Deployment: how-tos#deployment
- cloud/deployment/cloud.md
- cloud/deployment/self_hosted_data_plane.md
- cloud/deployment/self_hosted_control_plane.md
- cloud/deployment/standalone_container.md
- how-tos/deploy-self-hosted.md
- how-tos/use-remote-graph.md
- how-tos/ttl/configure_ttl.md
- Data Management:
- how-tos/ttl/configure_ttl.md
- Authentication & Access Control:
- Authentication & Access Control: how-tos#authentication-access-control
- cloud/how-tos/auth/custom_auth_new.md
@@ -256,6 +263,8 @@ nav:
- cloud/how-tos/invoke_studio.md
- cloud/how-tos/threads_studio.md
- cloud/how-tos/datasets_studio.md
- cloud/how-tos/iterate_graph_studio.md
- cloud/how-tos/clone_traces_studio.md
- Concepts:
- concepts/index.md
- LangGraph:
@@ -283,6 +292,8 @@ nav:
- concepts/template_applications.md
- Components:
- Components: concepts#components
- concepts/langgraph_control_plane.md
- concepts/langgraph_data_plane.md
- concepts/langgraph_server.md
- concepts/langgraph_studio.md
- concepts/langgraph_cli.md
@@ -296,9 +307,10 @@ nav:
- concepts/auth.md
- Deployment Options:
- Deployment Options: concepts#deployment-options
- concepts/self_hosted.md
- concepts/langgraph_cloud.md
- concepts/bring_your_own_cloud.md
- concepts/langgraph_self_hosted_data_plane.md
- concepts/langgraph_self_hosted_control_plane.md
- concepts/langgraph_standalone_container.md
- Tutorials:
- tutorials/index.md
- Quick Start:
@@ -503,3 +515,5 @@ validation:
not_found: info
copyright: >
Copyright &copy; 2025 LangChain, Inc | <a href="#__consent">Consent Preferences</a>
extra_css:
- stylesheets/version_admonitions.css
+1
View File
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% block extrahead %}
<meta name="algolia-site-verification" content="165B7E7C89E49946" />
<style>
@import url("https://fonts.googleapis.com/css2?family=Public+Sans&display=swap");
:root {
+240 -695
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -50,7 +50,8 @@ langchain-community = "^0.3.0"
langchain-experimental = "^0.3.2"
langchain-mistralai = "^0.2.6"
langgraph-checkpoint-mongodb = "^0.1.0"
langsmith = "^0.2.0"
langmem = "^0.0.19"
langsmith = "^0.3.0"
chromadb = "^0.5.5"
gpt4all = "^2.8.2"
scikit-learn = "^1.5.2"
@@ -1,5 +1,6 @@
import asyncio
import threading
import warnings
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager, contextmanager
from typing import Any, Optional
@@ -150,7 +151,7 @@ def _dump_blobs(
checkpoint_ns: str,
values: dict[str, Any],
versions: ChannelVersions,
) -> list[tuple[str, str, str, str, str, Optional[bytes]]]:
) -> list[tuple[str, str, str, str, Optional[bytes]]]:
if not versions:
return []
@@ -188,6 +189,12 @@ class ShallowPostgresSaver(BasePostgresSaver):
pipe: Optional[Pipeline] = None,
serde: Optional[SerializerProtocol] = None,
) -> None:
warnings.warn(
"ShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
"Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., checkpoint_during=False)`.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(serde=serde)
if isinstance(conn, ConnectionPool) and pipe is not None:
raise ValueError(
@@ -528,6 +535,12 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
pipe: Optional[AsyncPipeline] = None,
serde: Optional[SerializerProtocol] = None,
) -> None:
warnings.warn(
"AsyncShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
"Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., checkpoint_during=False)`.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(serde=serde)
if isinstance(conn, AsyncConnectionPool) and pipe is not None:
raise ValueError(
+34 -14
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "aiosqlite"
@@ -51,7 +51,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
trio = ["trio (>=0.23)"]
[[package]]
@@ -181,7 +181,7 @@ files = [
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli"]
toml = ["tomli ; python_version < \"3.11\""]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
@@ -267,7 +267,7 @@ idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
@@ -326,26 +326,26 @@ files = [
[[package]]
name = "langchain-core"
version = "0.3.0"
version = "0.3.15"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main", "dev"]
files = [
{file = "langchain_core-0.3.0-py3-none-any.whl", hash = "sha256:bee6dae2366d037ef0c5b87401fed14b5497cad26f97724e8c9ca7bc9239e847"},
{file = "langchain_core-0.3.0.tar.gz", hash = "sha256:1249149ea3ba24c9c761011483c14091573a5eb1a773aa0db9c8ad155dd4a69d"},
{file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"},
{file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.117,<0.2.0"
langsmith = ">=0.1.125,<0.2.0"
packaging = ">=23.2,<25"
pydantic = [
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
PyYAML = ">=5.3"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0"
typing-extensions = ">=4.7"
[[package]]
@@ -368,24 +368,28 @@ url = "../checkpoint"
[[package]]
name = "langsmith"
version = "0.1.120"
version = "0.1.147"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main", "dev"]
files = [
{file = "langsmith-0.1.120-py3-none-any.whl", hash = "sha256:54d2785e301646c0988e0a69ebe4d976488c87b41928b358cb153b6ddd8db62b"},
{file = "langsmith-0.1.120.tar.gz", hash = "sha256:25499ca187b41bd89d784b272b97a8d76f60e0e21bdf20336e8a2aa6a9b23ac9"},
{file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"},
{file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"},
]
[package.dependencies]
httpx = ">=0.23.0,<1"
orjson = ">=3.9.14,<4.0.0"
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
requests = ">=2,<3"
requests-toolbelt = ">=1.0.0,<2.0.0"
[package.extras]
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
[[package]]
name = "mypy"
@@ -454,6 +458,7 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
@@ -858,6 +863,21 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main", "dev"]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]]
name = "ruff"
version = "0.6.2"
@@ -952,7 +972,7 @@ files = [
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
@@ -30,6 +30,7 @@ from langgraph.checkpoint.serde.types import (
V = TypeVar("V", int, float, str)
PendingWrite = Tuple[str, str, Any]
# Kept for backwards compat, newer versions of LangGraph no longer use this.
LATEST_VERSION = 2
@@ -100,6 +101,7 @@ class Checkpoint(TypedDict):
Cleared by the next checkpoint."""
# Kept for backwards compat, newer versions of LangGraph no longer use this.
def empty_checkpoint() -> Checkpoint:
return Checkpoint(
v=LATEST_VERSION,
@@ -124,6 +126,7 @@ def copy_checkpoint(checkpoint: Checkpoint) -> Checkpoint:
)
# Kept for backwards compat, newer versions of LangGraph no longer use this.
def create_checkpoint(
checkpoint: Checkpoint,
channels: Optional[Mapping[str, ChannelProtocol]],
@@ -38,6 +38,8 @@ class InMemorySaver(
Only use `InMemorySaver` for debugging or testing purposes.
For production use cases we recommend installing [langgraph-checkpoint-postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) and using `PostgresSaver` / `AsyncPostgresSaver`.
If you are using the LangGraph Platform, no checkpointer needs to be specified. The correct managed checkpointer will be used automatically.
Args:
serde (Optional[SerializerProtocol]): The serializer to use for serializing and deserializing checkpoints. Defaults to None.
@@ -30,6 +30,7 @@ from langgraph.checkpoint.serde.types import SendProtocol
from langgraph.store.base import Item
LC_REVIVER = Reviver()
EMPTY_BYTES = b""
class JsonPlusSerializer(SerializerProtocol):
@@ -194,7 +195,9 @@ class JsonPlusSerializer(SerializerProtocol):
)
def dumps_typed(self, obj: Any) -> tuple[str, bytes]:
if isinstance(obj, bytes):
if obj is None:
return "null", EMPTY_BYTES
elif isinstance(obj, bytes):
return "bytes", obj
elif isinstance(obj, bytearray):
return "bytearray", obj
@@ -211,7 +214,9 @@ class JsonPlusSerializer(SerializerProtocol):
def loads_typed(self, data: tuple[str, bytes]) -> Any:
type_, data_ = data
if type_ == "bytes":
if type_ == "null":
return None
elif type_ == "bytes":
return data_
elif type_ == "bytearray":
return bytearray(data_)
+124 -9
View File
@@ -12,6 +12,29 @@ files = [
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "anyio"
version = "4.9.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
version = "2024.7.4"
@@ -177,7 +200,7 @@ version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
@@ -187,6 +210,65 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.7"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "idna"
version = "3.7"
@@ -240,19 +322,19 @@ files = [
[[package]]
name = "langchain-core"
version = "0.2.38"
version = "0.2.43"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main"]
files = [
{file = "langchain_core-0.2.38-py3-none-any.whl", hash = "sha256:8a5729bc7e68b4af089af20eff44fe4e7ca21d0e0c87ec21cef7621981fd1a4a"},
{file = "langchain_core-0.2.38.tar.gz", hash = "sha256:eb69dbedd344f2ee1f15bcea6c71a05884b867588fadc42d04632e727c1238f3"},
{file = "langchain_core-0.2.43-py3-none-any.whl", hash = "sha256:619601235113298ebf8252a349754b7c28d3cf7166c7c922da24944b78a9363a"},
{file = "langchain_core-0.2.43.tar.gz", hash = "sha256:42c2ef6adedb911f4254068b6adc9eb4c4075f6c8cb3d83590d3539a815695f5"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.75,<0.2.0"
langsmith = ">=0.1.112,<0.2.0"
packaging = ">=23.2,<25"
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
@@ -264,23 +346,28 @@ typing-extensions = ">=4.7"
[[package]]
name = "langsmith"
version = "0.1.93"
version = "0.1.147"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
groups = ["main"]
files = [
{file = "langsmith-0.1.93-py3-none-any.whl", hash = "sha256:811210b9d5f108f36431bd7b997eb9476a9ecf5a2abd7ddbb606c1cdcf0f43ce"},
{file = "langsmith-0.1.93.tar.gz", hash = "sha256:285b6ad3a54f50fa8eb97b5f600acc57d0e37e139dd8cf2111a117d0435ba9b4"},
{file = "langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15"},
{file = "langsmith-0.1.147.tar.gz", hash = "sha256:2e933220318a4e73034657103b3b1a3a6109cc5db3566a7e8e03be8d6d7def7a"},
]
[package.dependencies]
orjson = ">=3.9.14,<4.0.0"
httpx = ">=0.23.0,<1"
orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
requests = ">=2,<3"
requests-toolbelt = ">=1.0.0,<2.0.0"
[package.extras]
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
[[package]]
name = "marshmallow"
@@ -369,6 +456,7 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
@@ -773,6 +861,21 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]]
name = "ruff"
version = "0.6.2"
@@ -801,6 +904,18 @@ files = [
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "tenacity"
version = "8.5.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-checkpoint"
version = "2.0.23"
version = "2.0.24"
description = "Library with base interfaces for LangGraph checkpoint savers."
authors = []
license = "MIT"
+6
View File
@@ -15,12 +15,15 @@ import msgspec
from langgraph_cli.config import (
AuthConfig,
CheckpointerConfig,
Config,
CorsConfig,
HttpConfig,
IndexConfig,
SecurityConfig,
StoreConfig,
ThreadTTLConfig,
TTLConfig,
)
@@ -106,6 +109,9 @@ def add_descriptions_to_schema(schema, cls):
SecurityConfig,
HttpConfig,
CorsConfig,
ThreadTTLConfig,
CheckpointerConfig,
TTLConfig,
]:
if potential_cls.__name__ == def_name:
add_descriptions_to_schema(def_schema, potential_cls)
+14 -26
View File
@@ -274,23 +274,13 @@ def _build(
tag: str,
passthrough: Sequence[str] = (),
):
base_image = base_image or (
"langchain/langgraphjs-api"
if config_json.get("node_version")
else "langchain/langgraph-api"
)
# pull latest images
if pull:
runner.run(
subp_exec(
"docker",
"pull",
(
f"{base_image}:{config_json['node_version']}"
if config_json.get("node_version")
else f"{base_image}:{config_json['python_version']}"
),
langgraph_cli.config.docker_tag(config_json, base_image),
verbose=True,
)
)
@@ -450,11 +440,7 @@ def dockerfile(save_path: str, config: pathlib.Path, add_docker_compose: bool) -
dockerfile, additional_contexts = langgraph_cli.config.config_to_docker(
config,
config_json,
(
"langchain/langgraphjs-api"
if config_json.get("node_version")
else "langchain/langgraph-api"
),
None,
)
with open(str(save_path), "w", encoding="utf-8") as f:
f.write(dockerfile)
@@ -580,6 +566,12 @@ def dockerfile(save_path: str, config: pathlib.Path, add_docker_compose: bool) -
default=None,
help="URL of the LangGraph Studio instance to connect to. Defaults to https://smith.langchain.com",
)
@click.option(
"--allow-blocking",
is_flag=True,
help="Don't raise errors for synchronous I/O blocking operations in your code.",
default=False,
)
@cli.command(
"dev",
help="🏃‍♀️‍➡️ Run LangGraph API server in development mode with hot reloading and debugging support",
@@ -595,6 +587,7 @@ def dev(
debug_port: Optional[int],
wait_for_client: bool,
studio_url: Optional[str],
allow_blocking: bool,
):
"""CLI entrypoint for running the LangGraph API server."""
try:
@@ -658,7 +651,10 @@ def dev(
wait_for_client=wait_for_client,
auth=config_json.get("auth"),
http=config_json.get("http"),
ui=config_json.get("ui"),
ui_config=config_json.get("ui_config"),
studio_url=studio_url,
allow_blocking=allow_blocking,
)
@@ -709,11 +705,7 @@ def prepare_args_and_stdin(
config_path,
config,
watch=watch,
base_image=(
"langchain/langgraphjs-api"
if config.get("node_version")
else "langchain/langgraph-api"
),
base_image=langgraph_cli.config.default_base_image(config),
)
return args, stdin
@@ -740,11 +732,7 @@ def prepare(
subp_exec(
"docker",
"pull",
(
f"langchain/langgraphjs-api:{config_json['node_version']}"
if config_json.get("node_version")
else f"langchain/langgraph-api:{config_json['python_version']}"
),
langgraph_cli.config.docker_tag(config_json),
verbose=verbose,
)
)
+257 -111
View File
@@ -3,12 +3,15 @@ import os
import pathlib
import textwrap
from collections import Counter
from typing import Any, NamedTuple, Optional, TypedDict, Union
from typing import Any, Literal, NamedTuple, Optional, TypedDict, Union
import click
MIN_NODE_VERSION = "20"
DEFAULT_NODE_VERSION = "20"
MIN_PYTHON_VERSION = "3.11"
DEFAULT_PYTHON_VERSION = "3.11"
class TTLConfig(TypedDict, total=False):
@@ -111,6 +114,36 @@ class StoreConfig(TypedDict, total=False):
"""
class ThreadTTLConfig(TypedDict, total=False):
"""Configure a default TTL for checkpointed data within threads."""
strategy: Literal["delete"]
"""Strategy to use for deleting checkpointed data.
Choices:
- "delete": Delete all checkpoints for a thread after TTL expires.
"""
default_ttl: Optional[float]
"""Default TTL (time-to-live) in minutes for checkpointed data."""
sweep_interval_minutes: Optional[int]
"""Interval in minutes between sweep iterations.
If omitted, a default interval will be used (typically ~ 5 minutes)."""
class CheckpointerConfig(TypedDict, total=False):
"""Configuration for the built-in checkpointer, which handles checkpointing of state.
If omitted, no checkpointer is set up (the object store will still be present, however).
"""
ttl: Optional[ThreadTTLConfig]
"""Optional. Defines the TTL (time-to-live) behavior configuration.
If provided, the checkpointer will apply TTL settings according to the configuration.
If omitted, no TTL behavior is configured.
"""
class SecurityConfig(TypedDict, total=False):
"""Configuration for OpenAPI security definitions and requirements.
@@ -229,7 +262,7 @@ class CorsConfig(TypedDict, total=False):
allow_origin_regex: str
"""Optional. A regex pattern for matching allowed origins, used if you have dynamic subdomains.
Example: "^https://.*\.mycompany\.com$"
Example: "^https://.*\\.mycompany\\.com$"
"""
expose_headers: list[str]
"""Optional. List of headers that browsers are allowed to read from the response in cross-origin contexts."""
@@ -355,6 +388,12 @@ class Config(TypedDict, total=False):
If omitted, no vector index is set up (the object store will still be present, however).
"""
checkpointer: Optional[CheckpointerConfig]
"""Optional. Configuration for the built-in checkpointer, which handles checkpointing of state.
If omitted, no checkpointer is set up (the object store will still be present, however).
"""
auth: Optional[AuthConfig]
"""Optional. Custom authentication config, including the path to your Python auth logic and
the OpenAPI security definitions it uses.
@@ -370,6 +409,18 @@ class Config(TypedDict, total=False):
"""
PIP_CLEANUP_LINES = """# -- Ensure user deps didn't inadvertently overwrite langgraph-api
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && \
touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir --no-deps -e /api
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
# -- Removing pip from the final image ~<:===~~~ --
RUN pip uninstall -y pip setuptools wheel && \
rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && \
find /usr/local/bin -name "pip*" -delete
# -- End of pip removal --"""
def _parse_version(version_str: str) -> tuple[int, int]:
"""Parse a version string into a tuple of (major, minor)."""
try:
@@ -392,34 +443,48 @@ def _parse_node_version(version_str: str) -> int:
) from None
def _is_python_graph(spec: Union[str, dict]) -> bool:
"""Check if a graph is a Python graph based on the file extension."""
# handle new style config
if isinstance(spec, dict):
spec = spec.get("path")
file_path = spec.split(":")[0]
file_ext = os.path.splitext(file_path)[1]
return file_ext in [".py", ".pyx", ".pyd", ".pyi"]
def validate_config(config: Config) -> Config:
"""Validate a configuration dictionary."""
config = (
{
"node_version": config.get("node_version"),
"dockerfile_lines": config.get("dockerfile_lines", []),
"dependencies": config.get("dependencies", []),
"graphs": config.get("graphs", {}),
"env": config.get("env", {}),
"store": config.get("store"),
"auth": config.get("auth"),
"http": config.get("http"),
"ui": config.get("ui"),
}
if config.get("node_version")
else {
"python_version": config.get("python_version", "3.11"),
"pip_config_file": config.get("pip_config_file"),
"dockerfile_lines": config.get("dockerfile_lines", []),
"dependencies": config.get("dependencies", []),
"graphs": config.get("graphs", {}),
"env": config.get("env", {}),
"store": config.get("store"),
"auth": config.get("auth"),
"http": config.get("http"),
"ui": config.get("ui"),
}
graphs = config.get("graphs", {})
some_python = any(_is_python_graph(spec) for spec in graphs.values())
some_node = any(not _is_python_graph(spec) for spec in graphs.values())
node_version = config.get(
"node_version", DEFAULT_NODE_VERSION if some_node else None
)
python_version = config.get(
"python_version", DEFAULT_PYTHON_VERSION if some_python else None
)
config = {
"node_version": node_version,
"python_version": python_version,
"pip_config_file": config.get("pip_config_file"),
"dependencies": config.get("dependencies", []),
"dockerfile_lines": config.get("dockerfile_lines", []),
"graphs": config.get("graphs", {}),
"env": config.get("env", {}),
"store": config.get("store"),
"auth": config.get("auth"),
"http": config.get("http"),
"checkpointer": config.get("checkpointer"),
"ui": config.get("ui"),
"ui_config": config.get("ui_config"),
}
if config.get("node_version"):
node_version = config["node_version"]
@@ -738,7 +803,22 @@ def _update_graph_paths(
FileNotFoundError: If the local file (module) does not actually exist on disk.
IsADirectoryError: If `module_str` points to a directory instead of a file.
"""
for graph_id, import_str in config["graphs"].items():
for graph_id, data in config["graphs"].items():
if isinstance(data, dict):
# Then we're looking for a 'path' key
if "path" not in data:
raise ValueError(
f"Graph '{graph_id}' must contain a 'path' key if "
f" it is a dictionary."
)
import_str = data["path"]
elif isinstance(data, str):
import_str = data
else:
raise ValueError(
f"Graph '{graph_id}' must be a string or a dictionary with a 'path' key."
)
module_str, _, attr_str = import_str.partition(":")
if not module_str or not attr_str:
message = (
@@ -778,7 +858,10 @@ def _update_graph_paths(
"Add its containing package to 'dependencies' list."
)
# update the config
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
if isinstance(data, dict):
config["graphs"][graph_id]["path"] = f"{module_str}:{attr_str}"
else:
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
def _update_auth_path(
@@ -875,6 +958,66 @@ def _update_http_app_path(
http_config["app"] = f"{module_str}:{attr_str}"
def _get_node_pm_install_cmd(config_path: pathlib.Path, config: Config) -> str:
def test_file(file_name):
full_path = config_path.parent / file_name
try:
return full_path.is_file()
except OSError:
return False
# inspired by `package-manager-detector`
def get_pkg_manager_name():
try:
with open(config_path.parent / "package.json") as f:
pkg = json.load(f)
if (pkg_manager_name := pkg.get("packageManager")) and isinstance(
pkg_manager_name, str
):
return pkg_manager_name.lstrip("^").split("@")[0]
if (
dev_engine_name := (
(pkg.get("devEngines") or {}).get("packageManager") or {}
).get("name")
) and isinstance(dev_engine_name, str):
return dev_engine_name
return None
except Exception:
return None
npm, yarn, pnpm, bun = [
test_file("package-lock.json"),
test_file("yarn.lock"),
test_file("pnpm-lock.yaml"),
test_file("bun.lockb"),
]
if yarn:
install_cmd = "yarn install --frozen-lockfile"
elif pnpm:
install_cmd = "pnpm i --frozen-lockfile"
elif npm:
install_cmd = "npm ci"
elif bun:
install_cmd = "bun i"
else:
pkg_manager_name = get_pkg_manager_name()
if pkg_manager_name == "yarn":
install_cmd = "yarn install"
elif pkg_manager_name == "pnpm":
install_cmd = "pnpm i"
elif pkg_manager_name == "bun":
install_cmd = "bun i"
else:
install_cmd = "npm i"
return install_cmd
def python_config_to_docker(
config_path: pathlib.Path, config: Config, base_image: str
) -> tuple[str, dict[str, str]]:
@@ -955,10 +1098,17 @@ ADD {relpath} /deps/{name}
for fullpath, (relpath, name) in local_deps.real_pkgs.items()
)
install_node_str: str = (
"RUN /storage/install-node.sh"
if (config.get("ui") or config.get("node_version")) and local_deps.working_dir
else ""
)
installs = f"{os.linesep}{os.linesep}".join(
filter(
None,
[
install_node_str,
pip_config_file_str,
pip_pkgs_str,
pip_reqs_str,
@@ -979,8 +1129,29 @@ ADD {relpath} /deps/{name}
if (http_config := config.get("http")) is not None:
env_vars.append(f"ENV LANGGRAPH_HTTP='{json.dumps(http_config)}'")
graphs = config["graphs"]
env_vars.append(f"ENV LANGSERVE_GRAPHS='{json.dumps(graphs)}'")
if (checkpointer_config := config.get("checkpointer")) is not None:
env_vars.append(
f"ENV LANGGRAPH_CHECKPOINTER='{json.dumps(checkpointer_config)}'"
)
if (ui := config.get("ui")) is not None:
env_vars.append(f"ENV LANGGRAPH_UI='{json.dumps(ui)}'")
if (ui_config := config.get("ui_config")) is not None:
env_vars.append(f"ENV LANGGRAPH_UI_CONFIG='{json.dumps(ui_config)}'")
env_vars.append(f"ENV LANGSERVE_GRAPHS='{json.dumps(config['graphs'])}'")
js_inst_str: str = ""
if (config.get("ui") or config.get("node_version")) and local_deps.working_dir:
js_inst_str = os.linesep.join(
[
"# -- Installing JS dependencies --",
f"ENV NODE_VERSION={config.get('node_version') or DEFAULT_NODE_VERSION}",
f"RUN cd {local_deps.working_dir} && {_get_node_pm_install_cmd(config_path, config)} && tsx /api/langgraph_api/js/build.mts",
"# -- End of JS dependencies install --",
]
)
docker_file_contents = [
f"FROM {base_image}:{config['python_version']}",
@@ -994,6 +1165,10 @@ ADD {relpath} /deps/{name}
"# -- End of local dependencies install --",
os.linesep.join(env_vars),
"",
js_inst_str,
"",
PIP_CLEANUP_LINES, # Add pip cleanup after all installations are complete
"",
f"WORKDIR {local_deps.working_dir}" if local_deps.working_dir else "",
]
@@ -1014,102 +1189,71 @@ def node_config_to_docker(
config_path: pathlib.Path, config: Config, base_image: str
) -> tuple[str, dict[str, str]]:
faux_path = f"/deps/{config_path.parent.name}"
install_cmd = _get_node_pm_install_cmd(config_path, config)
def test_file(file_name):
full_path = config_path.parent / file_name
try:
return full_path.is_file()
except OSError:
return False
env_vars: list[str] = []
# inspired by `package-manager-detector`
def get_pkg_manager_name():
try:
with open(config_path.parent / "package.json") as f:
pkg = json.load(f)
if (store_config := config.get("store")) is not None:
env_vars.append(f"ENV LANGGRAPH_STORE='{json.dumps(store_config)}'")
if (pkg_manager_name := pkg.get("packageManager")) and isinstance(
pkg_manager_name, str
):
return pkg_manager_name.lstrip("^").split("@")[0]
if (auth_config := config.get("auth")) is not None:
env_vars.append(f"ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'")
if (
dev_engine_name := (
(pkg.get("devEngines") or {}).get("packageManager") or {}
).get("name")
) and isinstance(dev_engine_name, str):
return dev_engine_name
if (http_config := config.get("http")) is not None:
env_vars.append(f"ENV LANGGRAPH_HTTP='{json.dumps(http_config)}'")
return None
except Exception:
return None
if (checkpointer_config := config.get("checkpointer")) is not None:
env_vars.append(
f"ENV LANGGRAPH_CHECKPOINTER='{json.dumps(checkpointer_config)}'"
)
npm, yarn, pnpm, bun = [
test_file("package-lock.json"),
test_file("yarn.lock"),
test_file("pnpm-lock.yaml"),
test_file("bun.lockb"),
if ui := config.get("ui"):
env_vars.append(f"ENV LANGGRAPH_UI='{json.dumps(ui)}'")
if ui_config := config.get("ui_config"):
env_vars.append(f"ENV LANGGRAPH_UI_CONFIG='{json.dumps(ui_config)}'")
env_vars.append(f"ENV LANGSERVE_GRAPHS='{json.dumps(config['graphs'])}'")
docker_file_contents = [
f"FROM {base_image}:{config['node_version']}",
"",
os.linesep.join(config["dockerfile_lines"]),
"",
f"ADD . {faux_path}",
"",
f"RUN cd {faux_path} && {install_cmd}",
"",
os.linesep.join(env_vars),
"",
f"WORKDIR {faux_path}",
"",
'RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts',
]
if yarn:
install_cmd = "yarn install --frozen-lockfile"
elif pnpm:
install_cmd = "pnpm i --frozen-lockfile"
elif npm:
install_cmd = "npm ci"
elif bun:
install_cmd = "bun i"
else:
pkg_manager_name = get_pkg_manager_name()
return os.linesep.join(docker_file_contents), {}
if pkg_manager_name == "yarn":
install_cmd = "yarn install"
elif pkg_manager_name == "pnpm":
install_cmd = "pnpm i"
elif pkg_manager_name == "bun":
install_cmd = "bun i"
else:
install_cmd = "npm i"
store_config = config.get("store")
env_additional_config = (
""
if not store_config
else f"""
ENV LANGGRAPH_STORE='{json.dumps(store_config)}'
"""
)
if (auth_config := config.get("auth")) is not None:
env_additional_config += f"""
ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'
"""
if (http_config := config.get("http")) is not None:
env_additional_config += f"""
ENV LANGGRAPH_HTTP='{json.dumps(http_config)}'
"""
return (
f"""FROM {base_image}:{config['node_version']}
def default_base_image(config: Config) -> str:
if config.get("node_version") and not config.get("python_version"):
return "langchain/langgraphjs-api"
return "langchain/langgraph-api"
{os.linesep.join(config["dockerfile_lines"])}
ADD . {faux_path}
def docker_tag(config: Config, base_image: Optional[str] = None) -> str:
base_image = base_image or default_base_image(config)
RUN cd {faux_path} && {install_cmd}
{env_additional_config}
ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}'
{f"ENV LANGGRAPH_UI='{json.dumps(config['ui'])}'" if config.get("ui") else ""}
WORKDIR {faux_path}
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts""",
{},
)
if config.get("node_version") and not config.get("python_version"):
return f"{base_image}:{config['node_version']}"
return f"{base_image}:{config['python_version']}"
def config_to_docker(
config_path: pathlib.Path, config: Config, base_image: str
config_path: pathlib.Path, config: Config, base_image: Optional[str] = None
) -> tuple[str, dict[str, str]]:
if config.get("node_version"):
base_image = base_image or default_base_image(config)
if config.get("node_version") and not config.get("python_version"):
return node_config_to_docker(config_path, config, base_image)
return python_config_to_docker(config_path, config, base_image)
@@ -1118,9 +1262,11 @@ def config_to_docker(
def config_to_compose(
config_path: pathlib.Path,
config: Config,
base_image: str,
base_image: Optional[str] = None,
watch: bool = False,
) -> str:
base_image = base_image or default_base_image(config)
env_vars = config["env"].items() if isinstance(config["env"], dict) else {}
env_vars_str = "\n".join(f' {k}: "{v}"' for k, v in env_vars)
env_file_str = (
+234 -99
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -6,6 +6,8 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@@ -17,6 +19,8 @@ version = "4.8.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"},
{file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"},
@@ -29,15 +33,33 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
trio = ["trio (>=0.26.1)"]
[[package]]
name = "blockbuster"
version = "1.5.24"
description = "Utility to detect blocking calls in the async event loop"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "blockbuster-1.5.24-py3-none-any.whl", hash = "sha256:e703497b55bc72af09d60d1cd746c2f3ba7ce0c446fa256be6ccda5e7d403520"},
{file = "blockbuster-1.5.24.tar.gz", hash = "sha256:97645775761a5d425666ec0bc99629b65c7eccdc2f770d2439850682567af4ec"},
]
[package.dependencies]
forbiddenfruit = {version = ">=0.1.4", markers = "implementation_name == \"cpython\""}
[[package]]
name = "certifi"
version = "2025.1.31"
description = "Python package for providing Mozilla's CA Bundle."
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
@@ -49,6 +71,8 @@ version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -128,6 +152,8 @@ version = "3.4.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
@@ -229,6 +255,7 @@ version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
@@ -237,12 +264,26 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "cloudpickle"
version = "3.1.1"
description = "Pickler class to extend the standard pickle.Pickler functionality"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"},
{file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"},
]
[[package]]
name = "codespell"
version = "2.4.1"
description = "Fix common misspellings in text files"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"},
{file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"},
@@ -251,7 +292,7 @@ files = [
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli"]
toml = ["tomli ; python_version < \"3.11\""]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
@@ -260,10 +301,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "platform_system == \"Windows\""}
[[package]]
name = "cryptography"
@@ -271,6 +314,8 @@ version = "43.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
@@ -320,6 +365,7 @@ version = "0.6.2"
description = "Pythonic argument parser, that will make you smile"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
@@ -330,6 +376,8 @@ version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
@@ -338,12 +386,26 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "forbiddenfruit"
version = "0.1.4"
description = "Patch python built-in objects"
optional = true
python-versions = "*"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\" and implementation_name == \"cpython\""
files = [
{file = "forbiddenfruit-0.1.4.tar.gz", hash = "sha256:e3f7e66561a29ae129aac139a85d610dbf3dd896128187ed5454b6421f624253"},
]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -355,6 +417,8 @@ version = "1.0.7"
description = "A minimal low-level HTTP client."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
@@ -376,6 +440,8 @@ version = "0.28.1"
description = "The next generation HTTP client."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
@@ -388,7 +454,7 @@ httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
@@ -400,6 +466,8 @@ version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -414,6 +482,7 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -425,6 +494,8 @@ version = "1.33"
description = "Apply JSON-Patches (RFC 6902)"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"},
{file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"},
@@ -439,6 +510,8 @@ version = "3.0.0"
description = "Identify specific nodes in a JSON document (RFC 6901)"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"},
{file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"},
@@ -450,6 +523,8 @@ version = "0.20.0"
description = "A high-performance JSON Schema validator for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "jsonschema_rs-0.20.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d4b12f8aaec5037529fd11e5f71032cb53d44e8e2236bb7c3fb35e6efc7ce7f2"},
{file = "jsonschema_rs-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:25d512c47c5c391020c9fc4223f270cf42fbdd39b2906dbc894fe0205168b8f8"},
@@ -499,6 +574,8 @@ version = "0.3.40"
description = "Building applications with LLMs through composability"
optional = true
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langchain_core-0.3.40-py3-none-any.whl", hash = "sha256:9f31358741f10a13db8531e8288b8a5ae91904018c5c2e6f739d6645a98fca03"},
{file = "langchain_core-0.3.40.tar.gz", hash = "sha256:893a238b38491967c804662c1ec7c3e6ebaf223d1125331249c3cf3862ff2746"},
@@ -522,6 +599,8 @@ version = "0.3.1"
description = "Building stateful, multi-actor applications with LLMs"
optional = true
python-versions = "<4.0,>=3.9.0"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langgraph-0.3.1-py3-none-any.whl", hash = "sha256:212e1220d6a2af27048109604c816ccfbceb53a9aa93721be874305d8e28b7f5"},
{file = "langgraph-0.3.1.tar.gz", hash = "sha256:81cb89c381b089a20eac9a247f7ebcf3f41c922ac79e06dbcc4fc136c6f73dd5"},
@@ -535,23 +614,27 @@ langgraph-sdk = ">=0.1.42,<0.2.0"
[[package]]
name = "langgraph-api"
version = "0.0.32"
version = "0.1.0"
description = ""
optional = true
python-versions = "<4.0,>=3.11.0"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langgraph_api-0.0.32-py3-none-any.whl", hash = "sha256:7990cedc65f784813aba867c5bde3fdfae3fa4588baef1aa346cbeac7c3aebf1"},
{file = "langgraph_api-0.0.32.tar.gz", hash = "sha256:6f5b698ad8d136b73c2c53bcfa30670e9244a318b08b5e9cf00a707ea57c058c"},
{file = "langgraph_api-0.1.0-py3-none-any.whl", hash = "sha256:93eb369849d5ea0dd6076f0a36fe0a9669415c46e95fcb28ccf30c73e13e5e01"},
{file = "langgraph_api-0.1.0.tar.gz", hash = "sha256:d02201d34172f3020af8f21c4560142b302474cee4a27f4335fbd950ab22f22f"},
]
[package.dependencies]
cryptography = ">=43.0.3,<44.0.0"
blockbuster = ">=1.5.24,<2.0.0"
cloudpickle = ">=3.0.0,<4.0.0"
cryptography = ">=42.0.0,<45.0"
httpx = ">=0.25.0"
jsonschema-rs = ">=0.20.0,<0.30"
langchain-core = ">=0.2.38,<0.4.0"
langgraph = ">=0.2.56,<0.4.0"
langgraph-checkpoint = ">=2.0.21,<3.0"
langgraph-sdk = ">=0.1.58,<0.2.0"
langgraph-checkpoint = ">=2.0.23,<3.0"
langgraph-sdk = ">=0.1.61,<0.2.0"
langsmith = ">=0.1.63,<0.4.0"
orjson = ">=3.9.7"
pyjwt = ">=2.9.0,<3.0.0"
@@ -564,18 +647,20 @@ watchfiles = ">=0.13"
[[package]]
name = "langgraph-checkpoint"
version = "2.0.21"
version = "2.0.23"
description = "Library with base interfaces for LangGraph checkpoint savers."
optional = true
python-versions = "<4.0.0,>=3.9.0"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langgraph_checkpoint-2.0.21-py3-none-any.whl", hash = "sha256:ca89c2090cd9729f83f9782226935dc5ff9fe7756c24936f484ccb0ce367f87b"},
{file = "langgraph_checkpoint-2.0.21.tar.gz", hash = "sha256:52beeb6dc1bd8c487b8315466cab271093b65eb97f54a0942dfe105cd20b237f"},
{file = "langgraph_checkpoint-2.0.23-py3-none-any.whl", hash = "sha256:e54d070124f685eab095bd87e4df35dc5eca11d1e28553d5803c28c5f571b4e0"},
{file = "langgraph_checkpoint-2.0.23.tar.gz", hash = "sha256:38bd1fe451b569b773fef6e3daecdeb85f3deac2d94f7551bfd20f1818042c8a"},
]
[package.dependencies]
langchain-core = ">=0.2.38,<0.4"
msgpack = ">=1.1.0,<2.0.0"
ormsgpack = ">=1.8.0,<2.0.0"
[[package]]
name = "langgraph-prebuilt"
@@ -583,6 +668,8 @@ version = "0.1.1"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
optional = true
python-versions = "<4.0.0,>=3.9.0"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langgraph_prebuilt-0.1.1-py3-none-any.whl", hash = "sha256:148a9558a36ec7e83cc6512f3521425c862b0463251ae0242ade52a448c54e78"},
{file = "langgraph_prebuilt-0.1.1.tar.gz", hash = "sha256:420a748ff93842f2b1a345a0c1ca3939d2bc7a2d46c20e9a9a0d8f148152cc47"},
@@ -592,15 +679,30 @@ files = [
langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0"
langgraph-checkpoint = ">=2.0.10,<3.0.0"
[[package]]
name = "langgraph-runtime-inmem"
version = "0.0.1"
description = "Inmem implementation for the LangGraph API server."
optional = true
python-versions = ">=3.11"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langgraph_runtime_inmem-0.0.1-py3-none-any.whl", hash = "sha256:a25ec8e3219f2fd60450de38412d24c83fbf0b2521c13871cc26ce9a68ead496"},
{file = "langgraph_runtime_inmem-0.0.1.tar.gz", hash = "sha256:144bf5217efec4969f7f9c5e8279d6914cc5133d4b1a19e466b4966fa05f00c5"},
]
[[package]]
name = "langgraph-sdk"
version = "0.1.58"
version = "0.1.61"
description = "SDK for interacting with LangGraph API"
optional = true
python-versions = "<4.0.0,>=3.9.0"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "langgraph_sdk-0.1.58-py3-none-any.whl", hash = "sha256:65f88cf5582da0c316714dc475126fa03c5f74d72bc0b9221dd42649de8e23d4"},
{file = "langgraph_sdk-0.1.58.tar.gz", hash = "sha256:ef8b0e4c08af8c7efd3919497879c87a3627806b51e4ba5e8b06e0717e3d44cd"},
{file = "langgraph_sdk-0.1.61-py3-none-any.whl", hash = "sha256:f2d774b12497c428862993090622d51e0dbc3f53e0cee3d74a13c7495d835cc6"},
{file = "langgraph_sdk-0.1.61.tar.gz", hash = "sha256:87dd1f07ab82da8875ac343268ece8bf5414632017ebc9d1cef4b523962fd601"},
]
[package.dependencies]
@@ -613,6 +715,8 @@ version = "0.3.11"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = true
python-versions = "<4.0,>=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "langsmith-0.3.11-py3-none-any.whl", hash = "sha256:0cca22737ef07d3b038a437c141deda37e00add56022582680188b681bec095e"},
{file = "langsmith-0.3.11.tar.gz", hash = "sha256:ddf29d24352e99de79c9618aaf95679214324e146c5d3d9475a7ddd2870018b1"},
@@ -634,85 +738,13 @@ zstandard = ">=0.23.0,<0.24.0"
langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"]
pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"]
[[package]]
name = "msgpack"
version = "1.1.0"
description = "MessagePack serializer"
optional = true
python-versions = ">=3.8"
files = [
{file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"},
{file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"},
{file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"},
{file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"},
{file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"},
{file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"},
{file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"},
{file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"},
{file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"},
{file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"},
{file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"},
{file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"},
{file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"},
{file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"},
{file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"},
{file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"},
{file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"},
{file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"},
{file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"},
{file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"},
{file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"},
{file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"},
{file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"},
{file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"},
{file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"},
{file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"},
{file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"},
{file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"},
{file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"},
{file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"},
{file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"},
{file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"},
{file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"},
{file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"},
{file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"},
{file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"},
{file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"},
{file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"},
{file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"},
{file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"},
{file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"},
{file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"},
{file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"},
{file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"},
{file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"},
{file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"},
{file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"},
{file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"},
{file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"},
{file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"},
{file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"},
{file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"},
{file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"},
{file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"},
{file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"},
{file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"},
{file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"},
{file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"},
{file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"},
{file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"},
{file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"},
{file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"},
{file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"},
{file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"},
]
[[package]]
name = "msgspec"
version = "0.19.0"
description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259"},
{file = "msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36"},
@@ -753,10 +785,10 @@ files = [
]
[package.extras]
dev = ["attrs", "coverage", "eval-type-backport", "furo", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli_w"]
dev = ["attrs", "coverage", "eval-type-backport ; python_version < \"3.10\"", "furo", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli ; python_version < \"3.11\"", "tomli_w"]
doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"]
test = ["attrs", "eval-type-backport", "msgpack", "pytest", "pyyaml", "tomli", "tomli_w"]
toml = ["tomli", "tomli_w"]
test = ["attrs", "eval-type-backport ; python_version < \"3.10\"", "msgpack", "pytest", "pyyaml", "tomli ; python_version < \"3.11\"", "tomli_w"]
toml = ["tomli ; python_version < \"3.11\"", "tomli_w"]
yaml = ["pyyaml"]
[[package]]
@@ -765,6 +797,7 @@ version = "1.15.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
@@ -818,6 +851,7 @@ version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
groups = ["dev"]
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -829,6 +863,8 @@ version = "3.10.15"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"},
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"},
@@ -911,16 +947,70 @@ files = [
{file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"},
]
[[package]]
name = "ormsgpack"
version = "1.9.1"
description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "ormsgpack-1.9.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f1f804fd9c0fd84213a6022c34172f82323b34afa7052a4af18797582cf56365"},
{file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eab5cec99c46276b37071d570aab98603f3d0309b3818da3247eb64bb95e5cfc"},
{file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c12c6bb30e6df6fc0213b77f0a5e143f371d618be2e8eb4d555340ce01c6900"},
{file = "ormsgpack-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994d4bbb7ee333264a3e55e30ccee063df6635d785f21a08bf52f67821454a51"},
{file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a668a584cf4bb6e1a6ef5a35f3f0d0fdae80cfb7237344ad19a50cce8c79317b"},
{file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:aaf77699203822638014c604d100f132583844d4fd01eb639a2266970c02cfdf"},
{file = "ormsgpack-1.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:003d7e1992b447898caf25a820b3037ec68a57864b3e2f34b64693b7d60a9984"},
{file = "ormsgpack-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:67fefc77e4ba9469f79426769eb4c78acf21f22bef3ab1239a72dd728036ffc2"},
{file = "ormsgpack-1.9.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:16eaf32c33ab4249e242181d59e2509b8e0330d6f65c1d8bf08c3dea38fd7c02"},
{file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c70f2e5b2f9975536e8f7936a9721601dc54febe363d2d82f74c9b31d4fe1c65"},
{file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:17c9e18b07d69e3db2e0f8af4731040175e11bdfde78ad8e28126e9e66ec5167"},
{file = "ormsgpack-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73538d749096bb6470328601a2be8f7bdec28849ec6fd19595c232a5848d7124"},
{file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:827ff71de228cfd6d07b9d6b47911aa61b1e8dc995dec3caf8fdcdf4f874bcd0"},
{file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7307f808b3df282c8e8ed92c6ebceeb3eea3d8eeec808438f3f212226b25e217"},
{file = "ormsgpack-1.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f30aad7fb083bed1c540a3c163c6a9f63a94e3c538860bf8f13386c29b560ad5"},
{file = "ormsgpack-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:829a1b4c5bc3c38ece0c55cf91ebc09c3b987fceb24d3f680c2bcd03fd3789a4"},
{file = "ormsgpack-1.9.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1ede445fc3fdba219bb0e0d1f289df26a9c7602016b7daac6fafe8fe4e91548f"},
{file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db50b9f918e25b289114312ed775794d0978b469831b992bdc65bfe20b91fe30"},
{file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c7d8fc58e4333308f58ec720b1ee6b12b2b3fe2d2d8f0766ab751cb351e8757"},
{file = "ormsgpack-1.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeee6d08c040db265cb8563444aba343ecb32cbdbe2414a489dcead9f70c6765"},
{file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2fbb8181c198bdc413a4e889e5200f010724eea4b6d5a9a7eee2df039ac04aca"},
{file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:16488f094ac0e2250cceea6caf72962614aa432ee11dd57ef45e1ad25ece3eff"},
{file = "ormsgpack-1.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:422d960bfd6ad88be20794f50ec7953d8f7a0f2df60e19d0e8feb994e2ed64ee"},
{file = "ormsgpack-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6e2f9eab527cf43fb4a4293e493370276b1c8716cf305689202d646c6a782ef"},
{file = "ormsgpack-1.9.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ac61c18d9dd085e8519b949f7e655f7fb07909fd09c53b4338dd33309012e289"},
{file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134840b8c6615da2c24ce77bd12a46098015c808197a9995c7a2d991e1904eec"},
{file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38fd42618f626394b2c7713c5d4bcbc917254e9753d5d4cde460658b51b11a74"},
{file = "ormsgpack-1.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d36397333ad07b9eba4c2e271fa78951bd81afc059c85a6e9f6c0eb2de07cda"},
{file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:603063089597917d04e4c1b1d53988a34f7dc2ff1a03adcfd1cf4ae966d5fba6"},
{file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:94bbf2b185e0cb721ceaba20e64b7158e6caf0cecd140ca29b9f05a8d5e91e2f"},
{file = "ormsgpack-1.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38f380b1e8c96a712eb302b9349347385161a8e29046868ae2bfdfcb23e2692"},
{file = "ormsgpack-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4bc63fb30db94075611cedbbc3d261dd17cf2aa8ff75a0fd684cd45ca29cb1b"},
{file = "ormsgpack-1.9.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e95909248bece8e88a310a913838f17ff5a39190aa4e61de909c3cd27f59744b"},
{file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3939188810c5c641d6b207f29994142ae2b1c70534f7839bbd972d857ac2072"},
{file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b6476344a585aea00a2acc9fd07355bf2daac04062cfdd480fa83ec3e2403b"},
{file = "ormsgpack-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d8b9d53da82b31662ce5a3834b65479cf794a34befb9fc50baa51518383250"},
{file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3933d4b0c0d404ee234dbc372836d6f2d2f4b6330c2a2fb9709ba4eaebfae7ba"},
{file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f824e94a7969f0aee9a6847ec232cf731a03b8734951c2a774dd4762308ea2d2"},
{file = "ormsgpack-1.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c1f3f2295374020f9650e4aa7af6403ff016a0d92778b4a48bb3901fd801232d"},
{file = "ormsgpack-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:92eb1b4f7b168da47f547329b4b58d16d8f19508a97ce5266567385d42d81968"},
{file = "ormsgpack-1.9.1.tar.gz", hash = "sha256:3da6e63d82565e590b98178545e64f0f8506137b92bd31a2d04fd7c82baf5794"},
]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
markers = {main = "python_version >= \"3.11\" and extra == \"inmem\""}
[[package]]
name = "pluggy"
@@ -928,6 +1018,7 @@ version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -943,6 +1034,8 @@ version = "2.22"
description = "C parser in Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -954,6 +1047,8 @@ version = "2.10.6"
description = "Data validation using Python type hints"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
@@ -966,7 +1061,7 @@ typing-extensions = ">=4.12.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"]
timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
@@ -974,6 +1069,8 @@ version = "2.27.2"
description = "Core functionality for Pydantic validation and serialization"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
@@ -1086,6 +1183,8 @@ version = "2.10.1"
description = "JSON Web Token implementation in Python"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"},
{file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
@@ -1103,6 +1202,7 @@ version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
@@ -1125,6 +1225,7 @@ version = "0.21.2"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"},
{file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"},
@@ -1143,6 +1244,7 @@ version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
@@ -1160,6 +1262,7 @@ version = "4.2.0"
description = "Local continuous test runner with pytest and watchdog."
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"},
]
@@ -1176,6 +1279,8 @@ version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"inmem\""
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
@@ -1190,6 +1295,8 @@ version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -1252,6 +1359,8 @@ version = "2.32.3"
description = "Python HTTP for Humans."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -1273,6 +1382,8 @@ version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
@@ -1287,6 +1398,7 @@ version = "0.6.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
{file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
@@ -1314,6 +1426,8 @@ version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version >= \"3.11\""
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1325,6 +1439,8 @@ version = "2.1.3"
description = "SSE plugin for Starlette"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"},
{file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"},
@@ -1344,6 +1460,8 @@ version = "0.46.0"
description = "The little ASGI library that shines."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038"},
{file = "starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50"},
@@ -1361,6 +1479,8 @@ version = "25.2.0"
description = "Structured Logging for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "structlog-25.2.0-py3-none-any.whl", hash = "sha256:0fecea2e345d5d491b72f3db2e5fcd6393abfc8cd06a4851f21fcd4d1a99f437"},
{file = "structlog-25.2.0.tar.gz", hash = "sha256:d9f9776944207d1035b8b26072b9b140c63702fd7aa57c2f85d28ab701bd8e92"},
@@ -1378,6 +1498,8 @@ version = "9.0.0"
description = "Retry code until it succeeds"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
{file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
@@ -1393,6 +1515,8 @@ version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -1434,10 +1558,12 @@ version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
markers = {main = "python_version >= \"3.11\" and (python_version < \"3.13\" or extra == \"inmem\")"}
[[package]]
name = "urllib3"
@@ -1445,13 +1571,15 @@ version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
@@ -1462,6 +1590,8 @@ version = "0.34.0"
description = "The lightning-fast ASGI server."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
{file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
@@ -1472,7 +1602,7 @@ click = ">=7.0"
h11 = ">=0.8"
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "watchdog"
@@ -1480,6 +1610,7 @@ version = "6.0.0"
description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
@@ -1522,6 +1653,8 @@ version = "1.0.4"
description = "Simple, modern and high performance file watching and code reload in python."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08"},
{file = "watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1"},
@@ -1605,6 +1738,8 @@ version = "0.23.0"
description = "Zstandard bindings for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version >= \"3.11\" and extra == \"inmem\""
files = [
{file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"},
{file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"},
@@ -1712,9 +1847,9 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\
cffi = ["cffi (>=1.11)"]
[extras]
inmem = ["langgraph-api", "python-dotenv"]
inmem = ["langgraph-api", "langgraph-runtime-inmem", "python-dotenv"]
[metadata]
lock-version = "2.0"
lock-version = "2.1"
python-versions = "^3.9.0,<4.0"
content-hash = "f5aa4d66f9c0b98b8321a70a82387dc6e5f3a3a7ecedd87ac00d6415199038f9"
content-hash = "afc2f8776b4b6144bd1197df49ba34089889e2a1110b8470d8f1b212e0b08380"
+5 -3
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.80"
version = "0.2.2"
description = "CLI for interacting with LangGraph API"
authors = []
license = "MIT"
@@ -14,7 +14,9 @@ langgraph = "langgraph_cli.cli:cli"
[tool.poetry.dependencies]
python = "^3.9.0,<4.0"
click = "^8.1.7"
langgraph-api = { version = ">=0.0.32,<0.1.0", optional = true, python = ">=3.11,<4.0" }
langgraph-api = { version = ">=0.1.0,<0.2.0", optional = true, python = ">=3.11,<4.0" }
langgraph-runtime-inmem = { version = ">=0.0.1,<0.1.0", optional = true, python = ">=3.11,<4.0" }
langgraph-sdk = { version = ">=0.1.0,<0.2.0", optional = true, python = ">=3.11,<4.0" }
python-dotenv = { version = ">=0.8.0", optional = true }
[tool.poetry.group.dev.dependencies]
@@ -28,7 +30,7 @@ mypy = "^1.10.0"
msgspec = "^0.19.0"
[tool.poetry.extras]
inmem = ["langgraph-api", "python-dotenv"]
inmem = ["langgraph-api", "langgraph-runtime-inmem", "python-dotenv"]
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
+83 -3
View File
@@ -40,6 +40,17 @@
],
"description": "Optional. Custom authentication config, including the path to your Python auth logic and\nthe OpenAPI security definitions it uses.\n"
},
"checkpointer": {
"anyOf": [
{
"$ref": "#/$defs/CheckpointerConfig"
},
{
"type": "null"
}
],
"description": "Optional. Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).\n"
},
"dependencies": {
"type": "array",
"items": {
@@ -145,6 +156,17 @@
],
"description": "Optional. Custom authentication config, including the path to your Python auth logic and\nthe OpenAPI security definitions it uses.\n"
},
"checkpointer": {
"anyOf": [
{
"$ref": "#/$defs/CheckpointerConfig"
},
{
"type": "null"
}
],
"description": "Optional. Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).\n"
},
"dependencies": {
"type": "array",
"items": {
@@ -291,6 +313,61 @@
},
"required": []
},
"CheckpointerConfig": {
"title": "CheckpointerConfig",
"description": "Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).",
"type": "object",
"properties": {
"ttl": {
"anyOf": [
{
"$ref": "#/$defs/ThreadTTLConfig"
},
{
"type": "null"
}
],
"description": "Optional. Defines the TTL (time-to-live) behavior configuration.\n\nIf provided, the checkpointer will apply TTL settings according to the configuration.\nIf omitted, no TTL behavior is configured.\n"
}
},
"required": []
},
"ThreadTTLConfig": {
"title": "ThreadTTLConfig",
"description": "Configure a default TTL for checkpointed data within threads.",
"type": "object",
"properties": {
"default_ttl": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Default TTL (time-to-live) in minutes for checkpointed data."
},
"strategy": {
"enum": [
"delete"
],
"description": "Strategy to use for deleting checkpointed data.\n"
},
"sweep_interval_minutes": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"description": "Interval in minutes between sweep iterations.\nIf omitted, a default interval will be used (typically ~ 5 minutes)."
}
},
"required": []
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
@@ -455,10 +532,12 @@
{
"type": "null"
}
]
],
"description": "Optional. Default TTL (time-to-live) in minutes for new items.\n\nIf provided, all new items will have this TTL unless explicitly overridden.\nIf omitted, items will have no TTL by default.\n"
},
"refresh_on_read": {
"type": "boolean"
"type": "boolean",
"description": "Default behavior for refreshing TTLs on read operations (GET and SEARCH).\n\nIf True, TTLs will be refreshed on read operations (get/search) by default.\nThis can be overridden per-operation by explicitly setting refresh_ttl.\nDefaults to True if not configured.\n"
},
"sweep_interval_minutes": {
"anyOf": [
@@ -468,7 +547,8 @@
{
"type": "null"
}
]
],
"description": "Optional. Interval in minutes between TTL sweep iterations.\n\nIf provided, the store will periodically delete expired items based on the TTL.\nIf omitted, no automatic sweeping will occur.\n"
}
},
"required": []
+83 -3
View File
@@ -40,6 +40,17 @@
],
"description": "Optional. Custom authentication config, including the path to your Python auth logic and\nthe OpenAPI security definitions it uses.\n"
},
"checkpointer": {
"anyOf": [
{
"$ref": "#/$defs/CheckpointerConfig"
},
{
"type": "null"
}
],
"description": "Optional. Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).\n"
},
"dependencies": {
"type": "array",
"items": {
@@ -145,6 +156,17 @@
],
"description": "Optional. Custom authentication config, including the path to your Python auth logic and\nthe OpenAPI security definitions it uses.\n"
},
"checkpointer": {
"anyOf": [
{
"$ref": "#/$defs/CheckpointerConfig"
},
{
"type": "null"
}
],
"description": "Optional. Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).\n"
},
"dependencies": {
"type": "array",
"items": {
@@ -291,6 +313,61 @@
},
"required": []
},
"CheckpointerConfig": {
"title": "CheckpointerConfig",
"description": "Configuration for the built-in checkpointer, which handles checkpointing of state.\n\nIf omitted, no checkpointer is set up (the object store will still be present, however).",
"type": "object",
"properties": {
"ttl": {
"anyOf": [
{
"$ref": "#/$defs/ThreadTTLConfig"
},
{
"type": "null"
}
],
"description": "Optional. Defines the TTL (time-to-live) behavior configuration.\n\nIf provided, the checkpointer will apply TTL settings according to the configuration.\nIf omitted, no TTL behavior is configured.\n"
}
},
"required": []
},
"ThreadTTLConfig": {
"title": "ThreadTTLConfig",
"description": "Configure a default TTL for checkpointed data within threads.",
"type": "object",
"properties": {
"default_ttl": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Default TTL (time-to-live) in minutes for checkpointed data."
},
"strategy": {
"enum": [
"delete"
],
"description": "Strategy to use for deleting checkpointed data.\n"
},
"sweep_interval_minutes": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"description": "Interval in minutes between sweep iterations.\nIf omitted, a default interval will be used (typically ~ 5 minutes)."
}
},
"required": []
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
@@ -455,10 +532,12 @@
{
"type": "null"
}
]
],
"description": "Optional. Default TTL (time-to-live) in minutes for new items.\n\nIf provided, all new items will have this TTL unless explicitly overridden.\nIf omitted, items will have no TTL by default.\n"
},
"refresh_on_read": {
"type": "boolean"
"type": "boolean",
"description": "Default behavior for refreshing TTLs on read operations (GET and SEARCH).\n\nIf True, TTLs will be refreshed on read operations (get/search) by default.\nThis can be overridden per-operation by explicitly setting refresh_ttl.\nDefaults to True if not configured.\n"
},
"sweep_interval_minutes": {
"anyOf": [
@@ -468,7 +547,8 @@
{
"type": "null"
}
]
],
"description": "Optional. Interval in minutes between TTL sweep iterations.\n\nIf provided, the store will periodically delete expired items based on the TTL.\nIf omitted, no automatic sweeping will occur.\n"
}
},
"required": []
+49 -2
View File
@@ -2,13 +2,14 @@ import json
import pathlib
import shutil
import tempfile
import textwrap
from contextlib import contextmanager
from pathlib import Path
from click.testing import CliRunner
from langgraph_cli.cli import cli, prepare_args_and_stdin
from langgraph_cli.config import Config, validate_config
from langgraph_cli.config import PIP_CLEANUP_LINES, Config, validate_config
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
from langgraph_cli.util import clean_empty_lines
@@ -143,6 +144,7 @@ services:
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{{"agent": "agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/cli
develop:
@@ -176,8 +178,9 @@ def test_dockerfile_command_basic() -> None:
"""Test the 'dockerfile' command with basic configuration."""
runner = CliRunner()
config_content = {
"node_version": "20", # Add any other necessary configuration fields
"python_version": "3.11",
"graphs": {"agent": "agent.py:graph"},
"dependencies": ["."],
}
with temporary_config_folder(config_content) as temp_dir:
@@ -196,6 +199,50 @@ def test_dockerfile_command_basic() -> None:
assert save_path.exists()
def test_dockerfile_command_new_style_config() -> None:
"""Test `dockerfile` command with a new style config.
This config format allows specifying agent data as a dictionary.
{
"graphs": {
"agent1": {
"path": ... # path to graph definition,
... # other fields
}
}
}
"""
runner = CliRunner()
config_content = {
"dependencies": ["./my_agent"],
"graphs": {
"agent": {
"path": "./my_agent/agent.py:graph",
"description": "This is a test agent",
}
},
"env": ".env",
}
with temporary_config_folder(config_content) as temp_dir:
save_path = temp_dir / "Dockerfile"
# Add agent.py file
agent_path = temp_dir / "my_agent" / "agent.py"
agent_path.parent.mkdir(parents=True, exist_ok=True)
agent_path.touch()
result = runner.invoke(
cli,
["dockerfile", str(save_path), "--config", str(temp_dir / "config.json")],
)
# Assert command was successful
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
# Check if Dockerfile was created
assert save_path.exists()
def test_dockerfile_command_with_docker_compose() -> None:
"""Test the 'dockerfile' command with Docker Compose configuration."""
runner = CliRunner()
+193 -31
View File
@@ -2,11 +2,13 @@ import json
import os
import pathlib
import tempfile
import textwrap
import click
import pytest
from langgraph_cli.config import (
PIP_CLEANUP_LINES,
config_to_compose,
config_to_docker,
validate_config,
@@ -25,24 +27,28 @@ def test_validate_config():
"agent": "./agent.py:graph",
},
}
actual_config = validate_config(expected_config)
expected_config = {
"python_version": "3.11",
"node_version": None,
"pip_config_file": None,
"dockerfile_lines": [],
"env": {},
"store": None,
"auth": None,
"checkpointer": None,
"http": None,
"ui": None,
"ui_config": None,
**expected_config,
}
actual_config = validate_config(expected_config)
assert actual_config == expected_config
# full config
env = ".env"
expected_config = {
"python_version": "3.12",
"node_version": None,
"pip_config_file": "pipconfig.txt",
"dockerfile_lines": ["ARG meow"],
"dependencies": [".", "langchain"],
@@ -52,8 +58,10 @@ def test_validate_config():
"env": env,
"store": None,
"auth": None,
"checkpointer": None,
"http": None,
"ui": None,
"ui_config": None,
}
actual_config = validate_config(expected_config)
assert actual_config == expected_config
@@ -63,16 +71,12 @@ def test_validate_config():
# check wrong python version raises
with pytest.raises(click.UsageError):
validate_config(
{
"python_version": "3.9",
}
)
validate_config({"python_version": "3.9"})
# check missing dependencies key raises
with pytest.raises(click.UsageError):
validate_config(
{"python_version": "3.9", "graphs": {"agent": "./agent.py:graph"}},
{"python_version": "3.9", "graphs": {"agent": "./agent.py:graph"}}
)
# check missing graphs key raises
@@ -190,6 +194,47 @@ def test_validate_config_file():
validate_config_file(config_path)
def test_validate_config_multiplatform():
# default node
config = validate_config(
{"dependencies": ["."], "graphs": {"js": "./js.mts:graph"}}
)
assert config["node_version"] == "20"
assert config["python_version"] is None
# default multiplatform
config = validate_config(
{
"node_version": "22",
"python_version": "3.12",
"dependencies": ["."],
"graphs": {"python": "./python.py:graph", "js": "./js.mts:graph"},
}
)
assert config["node_version"] == "22"
assert config["python_version"] == "3.12"
# default multiplatform (full infer)
graphs = {"python": "./python.py:graph", "js": "./js.mts:graph"}
config = validate_config({"dependencies": ["."], "graphs": graphs})
assert config["node_version"] == "20"
assert config["python_version"] == "3.11"
# default multiplatform (partial node)
config = validate_config(
{"node_version": "22", "dependencies": ["."], "graphs": graphs}
)
assert config["node_version"] == "22"
assert config["python_version"] == "3.11"
# default multiplatform (partial python)
config = validate_config(
{"python_version": "3.12", "dependencies": ["."], "graphs": graphs}
)
assert config["node_version"] == "20"
assert config["python_version"] == "3.12"
# config_to_docker
def test_config_to_docker_simple():
graphs = {"agent": "./agent.py:graph"}
@@ -204,7 +249,7 @@ def test_config_to_docker_simple():
),
"langchain/langgraph-api",
)
expected_docker_stdin = """\
expected_docker_stdin = f"""\
FROM langchain/langgraph-api:3.11
# -- Installing local requirements --
COPY --from=__outer_requirements.txt requirements.txt /deps/__outer_graphs_reqs_a/graphs_reqs_a/requirements.txt
@@ -238,8 +283,9 @@ RUN set -ex && \\
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGGRAPH_HTTP='{"app": "/deps/examples/my_app.py:app"}'
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGGRAPH_HTTP='{{"app": "/deps/examples/my_app.py:app"}}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{PIP_CLEANUP_LINES}
WORKDIR /deps/__outer_unit_tests/unit_tests\
"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
@@ -259,7 +305,8 @@ def test_config_to_docker_outside_path():
validate_config({"dependencies": [".", ".."], "graphs": graphs}),
"langchain/langgraph-api",
)
expected_docker_stdin = """\
expected_docker_stdin = (
"""\
FROM langchain/langgraph-api:3.11
# -- Adding non-package dependency unit_tests --
ADD . /deps/__outer_unit_tests/unit_tests
@@ -287,8 +334,12 @@ RUN set -ex && \\
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
"""
+ PIP_CLEANUP_LINES
+ """
WORKDIR /deps/__outer_unit_tests/unit_tests\
"""
)
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {
"__outer_tests": str(pathlib.Path(__file__).parent.parent.absolute()),
@@ -308,7 +359,8 @@ def test_config_to_docker_pipconfig():
),
"langchain/langgraph-api",
)
expected_docker_stdin = """\
expected_docker_stdin = (
"""\
FROM langchain/langgraph-api:3.11
ADD pipconfig.txt /pipconfig.txt
# -- Adding non-package dependency unit_tests --
@@ -326,8 +378,12 @@ RUN set -ex && \\
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
"""
+ PIP_CLEANUP_LINES
+ """
WORKDIR /deps/__outer_unit_tests/unit_tests\
"""
)
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -364,7 +420,7 @@ def test_config_to_docker_local_deps():
),
"langchain/langgraph-api-custom",
)
expected_docker_stdin = """\
expected_docker_stdin = f"""\
FROM langchain/langgraph-api-custom:3.11
# -- Adding non-package dependency graphs --
ADD ./graphs /deps/__outer_graphs/src
@@ -380,7 +436,8 @@ RUN set -ex && \\
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph"}'\
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_graphs/src/agent.py:graph"}}'
{PIP_CLEANUP_LINES}\
"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -407,7 +464,8 @@ dependencies = ["langchain"]"""
"langchain/langgraph-api",
)
os.remove(pyproject_path)
expected_docker_stdin = """FROM langchain/langgraph-api:3.11
expected_docker_stdin = (
"""FROM langchain/langgraph-api:3.11
# -- Adding local package . --
ADD . /deps/unit_tests
# -- End of local package . --
@@ -415,7 +473,12 @@ ADD . /deps/unit_tests
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/unit_tests/graphs/agent.py:graph"}'
WORKDIR /deps/unit_tests"""
"""
+ PIP_CLEANUP_LINES
+ "\n"
+ "WORKDIR /deps/unit_tests"
""
)
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -435,7 +498,7 @@ def test_config_to_docker_end_to_end():
),
"langchain/langgraph-api",
)
expected_docker_stdin = """FROM langchain/langgraph-api:3.12
expected_docker_stdin = f"""FROM langchain/langgraph-api:3.12
ARG meow
ARG foo
ADD pipconfig.txt /pipconfig.txt
@@ -454,7 +517,8 @@ RUN set -ex && \\
# -- Installing all local dependencies --
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph"}'"""
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_graphs/src/agent.py:graph"}}'
{PIP_CLEANUP_LINES}"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
@@ -469,7 +533,9 @@ def test_config_to_docker_nodejs():
"node_version": "20",
"graphs": graphs,
"dockerfile_lines": ["ARG meow", "ARG foo"],
"auth": {"path": "./graphs/auth.mts:auth"},
"ui": {"agent": "./graphs/agent.ui.jsx"},
"ui_config": {"shared": ["nuqs"]},
}
),
"langchain/langgraphjs-api",
@@ -479,8 +545,10 @@ ARG meow
ARG foo
ADD . /deps/unit_tests
RUN cd /deps/unit_tests && npm i
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
ENV LANGGRAPH_AUTH='{"path": "./graphs/auth.mts:auth"}'
ENV LANGGRAPH_UI='{"agent": "./graphs/agent.ui.jsx"}'
ENV LANGGRAPH_UI_CONFIG='{"shared": ["nuqs"]}'
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
WORKDIR /deps/unit_tests
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
@@ -488,11 +556,97 @@ RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not foun
assert additional_contexts == {}
def test_config_to_docker_gen_ui_python():
graphs = {"agent": "./agent.py:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config(
{
"dependencies": ["."],
"graphs": graphs,
"ui": {"agent": "./graphs/agent.ui.jsx"},
"ui_config": {"shared": ["nuqs"]},
}
),
"langchain/langgraph-api",
)
expected_docker_stdin = f"""FROM langchain/langgraph-api:3.11
RUN /storage/install-node.sh
# -- Adding non-package dependency unit_tests --
ADD . /deps/__outer_unit_tests/unit_tests
RUN set -ex && \\
for line in '[project]' \\
'name = "unit_tests"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_unit_tests/pyproject.toml; \\
done
# -- End of non-package dependency unit_tests --
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGGRAPH_UI='{{"agent": "./graphs/agent.ui.jsx"}}'
ENV LANGGRAPH_UI_CONFIG='{{"shared": ["nuqs"]}}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
# -- Installing JS dependencies --
ENV NODE_VERSION=20
RUN cd /deps/__outer_unit_tests/unit_tests && npm i && tsx /api/langgraph_api/js/build.mts
# -- End of JS dependencies install --
{PIP_CLEANUP_LINES}
WORKDIR /deps/__outer_unit_tests/unit_tests"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
def test_config_to_docker_multiplatform():
graphs = {
"python": "./multiplatform/python.py:graph",
"js": "./multiplatform/js.mts:graph",
}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config(
{"node_version": "22", "dependencies": ["."], "graphs": graphs}
),
"langchain/langgraph-api",
)
expected_docker_stdin = f"""FROM langchain/langgraph-api:3.11
RUN /storage/install-node.sh
# -- Adding non-package dependency unit_tests --
ADD . /deps/__outer_unit_tests/unit_tests
RUN set -ex && \\
for line in '[project]' \\
'name = "unit_tests"' \\
'version = "0.1"' \\
'[tool.setuptools.package-data]' \\
'"*" = ["**/*"]'; do \\
echo "$line" >> /deps/__outer_unit_tests/pyproject.toml; \\
done
# -- End of non-package dependency unit_tests --
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{{"python": "/deps/__outer_unit_tests/unit_tests/multiplatform/python.py:graph", "js": "/deps/__outer_unit_tests/unit_tests/multiplatform/js.mts:graph"}}'
# -- Installing JS dependencies --
ENV NODE_VERSION=22
RUN cd /deps/__outer_unit_tests/unit_tests && npm i && tsx /api/langgraph_api/js/build.mts
# -- End of JS dependencies install --
{PIP_CLEANUP_LINES}
WORKDIR /deps/__outer_unit_tests/unit_tests"""
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
assert additional_contexts == {}
# config_to_compose
def test_config_to_compose_simple_config():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
# Create a properly indented version of PIP_CLEANUP_LINES for compose files
expected_compose_stdin = f"""
pull_policy: build
build:
context: .
@@ -512,7 +666,8 @@ def test_config_to_compose_simple_config():
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
actual_compose_stdin = config_to_compose(
@@ -520,12 +675,15 @@ def test_config_to_compose_simple_config():
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
)
assert clean_empty_lines(actual_compose_stdin) == expected_compose_stdin
assert (
clean_empty_lines(actual_compose_stdin).strip()
== expected_compose_stdin.strip()
)
def test_config_to_compose_env_vars():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """ OPENAI_API_KEY: "key"
expected_compose_stdin = f""" OPENAI_API_KEY: "key"
pull_policy: build
build:
@@ -546,7 +704,8 @@ def test_config_to_compose_env_vars():
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
openai_api_key = "key"
@@ -566,7 +725,7 @@ def test_config_to_compose_env_vars():
def test_config_to_compose_env_file():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
expected_compose_stdin = f"""\
env_file: .env
pull_policy: build
build:
@@ -587,7 +746,8 @@ def test_config_to_compose_env_file():
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/__outer_unit_tests/unit_tests
"""
actual_compose_stdin = config_to_compose(
@@ -600,7 +760,7 @@ def test_config_to_compose_env_file():
def test_config_to_compose_watch():
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
expected_compose_stdin = f"""\
pull_policy: build
build:
@@ -621,7 +781,8 @@ def test_config_to_compose_watch():
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/__outer_unit_tests/unit_tests
develop:
@@ -643,7 +804,7 @@ def test_config_to_compose_watch():
def test_config_to_compose_end_to_end():
# test all of the above + langgraph API path
graphs = {"agent": "./agent.py:graph"}
expected_compose_stdin = """\
expected_compose_stdin = f"""\
env_file: .env
pull_policy: build
build:
@@ -664,7 +825,8 @@ def test_config_to_compose_end_to_end():
# -- Installing all local dependencies --
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
# -- End of local dependencies install --
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}'
ENV LANGSERVE_GRAPHS='{{"agent": "/deps/__outer_unit_tests/unit_tests/agent.py:graph"}}'
{textwrap.indent(textwrap.dedent(PIP_CLEANUP_LINES), " ")}
WORKDIR /deps/__outer_unit_tests/unit_tests
develop:
+116 -34
View File
@@ -9,6 +9,7 @@ from bench.fanout_to_subgraph import fanout_to_subgraph, fanout_to_subgraph_sync
from bench.pydantic_state import pydantic_state
from bench.react_agent import react_agent
from bench.sequential import create_sequential
from bench.wide_dict import wide_dict
from bench.wide_state import wide_state
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
@@ -25,6 +26,7 @@ async def arun(graph: Pregel, input: dict):
"configurable": {"thread_id": str(uuid4())},
"recursion_limit": 1000000000,
},
checkpoint_during=False,
)
]
)
@@ -41,6 +43,7 @@ async def arun_first_event_latency(graph: Pregel, input: dict) -> None:
"configurable": {"thread_id": str(uuid4())},
"recursion_limit": 1000000000,
},
checkpoint_during=False,
)
try:
@@ -60,6 +63,7 @@ def run(graph: Pregel, input: dict):
"configurable": {"thread_id": str(uuid4())},
"recursion_limit": 1000000000,
},
checkpoint_during=False,
)
]
)
@@ -76,6 +80,7 @@ def run_first_event_latency(graph: Pregel, input: dict) -> None:
"configurable": {"thread_id": str(uuid4())},
"recursion_limit": 1000000000,
},
checkpoint_during=False,
)
try:
@@ -252,27 +257,111 @@ benchmarks = (
},
),
(
"sequential_20",
create_sequential(20).compile(),
create_sequential(20).compile(),
"wide_dict_25x300",
wide_dict(300).compile(checkpointer=None),
wide_dict(300).compile(checkpointer=None),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(5)
}
for i in range(5)
}
]
},
),
(
"wide_dict_25x300_checkpoint",
wide_dict(300).compile(checkpointer=MemorySaver()),
wide_dict(300).compile(checkpointer=MemorySaver()),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(5)
}
for i in range(5)
}
]
},
),
(
"wide_dict_15x600",
wide_dict(600).compile(checkpointer=None),
wide_dict(600).compile(checkpointer=None),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(5)
}
for i in range(3)
}
]
},
),
(
"wide_dict_15x600_checkpoint",
wide_dict(600).compile(checkpointer=MemorySaver()),
wide_dict(600).compile(checkpointer=MemorySaver()),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(5)
}
for i in range(3)
}
]
},
),
(
"wide_dict_9x1200",
wide_dict(1200).compile(checkpointer=None),
wide_dict(1200).compile(checkpointer=None),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(3)
}
for i in range(3)
}
]
},
),
(
"wide_dict_9x1200_checkpoint",
wide_dict(1200).compile(checkpointer=MemorySaver()),
wide_dict(1200).compile(checkpointer=MemorySaver()),
{
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(3)
}
for i in range(3)
}
]
},
),
(
"sequential_10",
create_sequential(10).compile(),
create_sequential(10).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_50",
create_sequential(50).compile(),
create_sequential(50).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_100",
create_sequential(100).compile(),
create_sequential(100).compile(),
{"messages": []}, # Empty list of messages
),
(
"sequential_200",
create_sequential(200).compile(),
create_sequential(200).compile(),
"sequential_1000",
create_sequential(1000).compile(),
create_sequential(1000).compile(),
{"messages": []}, # Empty list of messages
),
(
@@ -383,8 +472,17 @@ for name, agraph, graph, input in benchmarks:
r.bench_func(name + "_sync", run, graph, input)
# Pick a handful of graphs to measure the first event latency.
# At the moment, limiting just due to the size of the annotation on github.
GRAPHS_FOR_1st_EVENT_LATENCY = (
"sequential_1000",
"pydantic_state_25x300",
)
# First event latency
for name, agraph, graph, input in benchmarks:
if graph not in GRAPHS_FOR_1st_EVENT_LATENCY:
continue
r.bench_async_func(
name + "_first_event_latency",
arun_first_event_latency,
@@ -403,30 +501,14 @@ compilation_benchmarks = (
"sequential_1000",
create_sequential(1_000),
),
(
"sequential_10000",
create_sequential(10_000),
),
(
"pydantic_state_25x300",
pydantic_state(300),
),
(
"pydantic_state_15x600",
pydantic_state(600),
),
(
"pydantic_state_9x1200",
pydantic_state(1200),
),
(
"wide_state_15x600",
wide_state(600),
),
(
"wide_state_9x1200",
wide_state(1200),
),
)
for name, graph in compilation_benchmarks:
+1 -1
View File
@@ -34,7 +34,7 @@ if __name__ == "__main__":
import uvloop
graph = create_sequential(2000).compile()
graph = create_sequential(3000).compile()
input = {"messages": []} # Empty list of messages
config = {"recursion_limit": 20000000000}
+153
View File
@@ -0,0 +1,153 @@
import operator
from functools import partial
from random import choice
from typing import Annotated, Optional, Sequence
from typing_extensions import TypedDict
from langgraph.constants import END, START
from langgraph.graph.state import StateGraph
def wide_dict(n: int) -> StateGraph:
class State(TypedDict):
messages: Annotated[list, operator.add]
trigger_events: Annotated[list, operator.add]
"""The external events that are converted by the graph."""
primary_issue_medium: Annotated[str, lambda x, y: y or x]
autoresponse: Annotated[Optional[dict], lambda _, y: y] # Always overwrite
issue: Annotated[dict | None, lambda x, y: y if y else x]
relevant_rules: Optional[list[dict]]
"""SOPs fetched from the rulebook that are relevant to the current conversation."""
memory_docs: Optional[list[dict]]
"""Memory docs fetched from the memory service that are relevant to the current conversation."""
categorizations: Annotated[list[dict], operator.add]
"""The issue categorizations auto-generated by the AI."""
responses: Annotated[list[dict], operator.add]
"""The draft responses recommended by the AI."""
user_info: Annotated[Optional[dict], lambda x, y: y if y is not None else x]
"""The current user state (by email)."""
crm_info: Annotated[Optional[dict], lambda x, y: y if y is not None else x]
"""The CRM information for organization the current user is from."""
email_thread_id: Annotated[
Optional[str], lambda x, y: y if y is not None else x
]
"""The current email thread ID."""
slack_participants: Annotated[dict, operator.or_]
"""The growing list of current slack participants."""
bot_id: Optional[str]
"""The ID of the bot user in the slack channel."""
notified_assignees: Annotated[dict, operator.or_]
list_fields = {
"messages",
"trigger_events",
"categorizations",
"responses",
"memory_docs",
"relevant_rules",
}
dict_fields = {
"user_info",
"crm_info",
"slack_participants",
"notified_assignees",
"autoresponse",
"issue",
}
def read_write(read: str, write: Sequence[str], input: State) -> dict:
val = input.get(read)
val = {val: val} if isinstance(val, str) else val
val_single = val[-1] if isinstance(val, list) else val
val_list = val if isinstance(val, list) else [val]
return {
k: val_list
if k in list_fields
else val_single
if k in dict_fields
else "".join(choice("abcdefghijklmnopqrstuvwxyz") for _ in range(n))
for k in write
}
builder = StateGraph(State)
builder.add_edge(START, "one")
builder.add_node(
"one",
partial(read_write, "messages", ["trigger_events", "primary_issue_medium"]),
)
builder.add_edge("one", "two")
builder.add_node(
"two",
partial(read_write, "trigger_events", ["autoresponse", "issue"]),
)
builder.add_edge("two", "three")
builder.add_edge("two", "four")
builder.add_node(
"three",
partial(read_write, "autoresponse", ["relevant_rules"]),
)
builder.add_node(
"four",
partial(
read_write,
"trigger_events",
["categorizations", "responses", "memory_docs"],
),
)
builder.add_node(
"five",
partial(
read_write,
"categorizations",
[
"user_info",
"crm_info",
"email_thread_id",
"slack_participants",
"bot_id",
"notified_assignees",
],
),
)
builder.add_edge(["three", "four"], "five")
builder.add_edge("five", "six")
builder.add_node(
"six",
partial(read_write, "responses", ["messages"]),
)
builder.add_conditional_edges(
"six", lambda state: END if len(state["messages"]) > n else "one"
)
return builder
if __name__ == "__main__":
import asyncio
import uvloop
from langgraph.checkpoint.memory import MemorySaver
graph = wide_dict(1000).compile(checkpointer=MemorySaver())
input = {
"messages": [
{
str(i) * 10: {
str(j) * 10: ["hi?" * 10, True, 1, 6327816386138, None] * 5
for j in range(50)
}
for i in range(50)
}
]
}
config = {"configurable": {"thread_id": "1"}, "recursion_limit": 20000000000}
async def run():
async for c in graph.astream(input, config=config):
print(c.keys())
uvloop.install()
asyncio.run(run())
+24 -1
View File
@@ -1,6 +1,7 @@
import operator
from dataclasses import dataclass, field
from functools import partial
from random import choice
from typing import Annotated, Optional, Sequence
from langgraph.constants import END, START
@@ -49,12 +50,34 @@ def wide_state(n: int) -> StateGraph:
"""The ID of the bot user in the slack channel."""
notified_assignees: Annotated[dict, operator.or_] = field(default_factory=dict)
list_fields = {
"messages",
"trigger_events",
"categorizations",
"responses",
"memory_docs",
"relevant_rules",
}
dict_fields = {
"user_info",
"crm_info",
"slack_participants",
"notified_assignees",
"autoresponse",
"issue",
}
def read_write(read: str, write: Sequence[str], input: State) -> dict:
val = getattr(input, read)
val = {val: val} if isinstance(val, str) else val
val_single = val[-1] if isinstance(val, list) else val
val_list = val if isinstance(val, list) else [val]
return {
k: val_list if isinstance(getattr(input, k), list) else val_single
k: val_list
if k in list_fields
else val_single
if k in dict_fields
else "".join(choice("abcdefghijklmnopqrstuvwxyz") for _ in range(n))
for k in write
}
+13 -5
View File
@@ -1,4 +1,4 @@
from typing import Any, Generic, Optional, Sequence, Type
from typing import Any, Generic, Sequence, Type
from typing_extensions import Self
@@ -30,10 +30,15 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
"""The type of the update received by the channel."""
return self.typ
def from_checkpoint(self, checkpoint: Optional[Value]) -> Self:
empty = self.__class__(self.typ)
empty.key = self.key
if checkpoint is not None:
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.key)
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.key)
if checkpoint is not MISSING:
empty.value = checkpoint
return empty
@@ -55,3 +60,6 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
+14 -4
View File
@@ -1,8 +1,9 @@
from abc import ABC, abstractmethod
from typing import Any, Generic, Optional, Sequence, TypeVar
from typing import Any, Generic, Sequence, TypeVar
from typing_extensions import Self
from langgraph.constants import MISSING
from langgraph.errors import EmptyChannelError, InvalidUpdateError
Value = TypeVar("Value")
@@ -29,14 +30,23 @@ class BaseChannel(Generic[Value, Update, C], ABC):
# serialize/deserialize methods
def checkpoint(self) -> Optional[C]:
def copy(self) -> Self:
"""Return a copy of the channel.
By default, delegates to checkpoint() and from_checkpoint().
Subclasses can override this method with a more efficient implementation."""
return self.from_checkpoint(self.checkpoint())
def checkpoint(self) -> C:
"""Return a serializable representation of the channel's current state.
Raises EmptyChannelError if the channel is empty (never updated yet),
or doesn't support checkpoints."""
return self.get()
try:
return self.get()
except EmptyChannelError:
return MISSING
@abstractmethod
def from_checkpoint(self, checkpoint: Optional[C]) -> Self:
def from_checkpoint(self, checkpoint: C) -> Self:
"""Return a new identical channel, optionally initialized from a checkpoint.
If the checkpoint contains complex data structures, they should be copied."""
+13 -9
View File
@@ -1,11 +1,5 @@
import collections.abc
from typing import (
Callable,
Generic,
Optional,
Sequence,
Type,
)
from typing import Callable, Generic, Sequence, Type
from typing_extensions import NotRequired, Required, Self
@@ -72,10 +66,17 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
"""The type of the update received by the channel."""
return self.typ
def from_checkpoint(self, checkpoint: Optional[Value]) -> Self:
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.operator)
empty.key = self.key
if checkpoint is not None:
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.operator)
empty.key = self.key
if checkpoint is not MISSING:
empty.value = checkpoint
return empty
@@ -96,3 +97,6 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -3,6 +3,7 @@ from typing import Any, Generic, NamedTuple, Optional, Sequence, Type, Union
from typing_extensions import Self
from langgraph.channels.base import BaseChannel, Value
from langgraph.constants import MISSING
from langgraph.errors import EmptyChannelError, InvalidUpdateError
@@ -45,16 +46,23 @@ class DynamicBarrierValue(
"""The type of the update received by the channel."""
return self.typ
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ)
empty.key = self.key
empty.names = self.names
empty.seen = self.seen.copy()
return empty
def checkpoint(self) -> tuple[Optional[set[Value]], set[Value]]:
return (self.names, self.seen)
def from_checkpoint(
self,
checkpoint: Optional[tuple[Optional[set[Value]], set[Value]]],
self, checkpoint: tuple[Optional[set[Value]], set[Value]]
) -> Self:
empty = self.__class__(self.typ)
empty.key = self.key
if checkpoint is not None:
if checkpoint is not MISSING:
names, seen = checkpoint
empty.names = names if names is not None else None
empty.seen = seen
@@ -1,4 +1,4 @@
from typing import Any, Generic, Optional, Sequence, Type
from typing import Any, Generic, Sequence, Type
from typing_extensions import Self
@@ -30,10 +30,17 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
"""The type of the update received by the channel."""
return self.typ
def from_checkpoint(self, checkpoint: Optional[Value]) -> Self:
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.guard)
empty.key = self.key
if checkpoint is not None:
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.guard)
empty.key = self.key
if checkpoint is not MISSING:
empty.value = checkpoint
return empty
@@ -59,3 +66,6 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -1,4 +1,4 @@
from typing import Any, Generic, Optional, Sequence, Type
from typing import Any, Generic, Sequence, Type
from typing_extensions import Self
@@ -34,10 +34,15 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
"""The type of the update received by the channel."""
return self.typ
def from_checkpoint(self, checkpoint: Optional[Value]) -> Self:
empty = self.__class__(self.typ)
empty.key = self.key
if checkpoint is not None:
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.key)
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.key)
if checkpoint is not MISSING:
empty.value = checkpoint
return empty
@@ -61,3 +66,6 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -1,8 +1,9 @@
from typing import Generic, Optional, Sequence, Type
from typing import Generic, Sequence, Type
from typing_extensions import Self
from langgraph.channels.base import BaseChannel, Value
from langgraph.constants import MISSING
from langgraph.errors import EmptyChannelError, InvalidUpdateError
@@ -32,13 +33,20 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]):
"""The type of the update received by the channel."""
return self.typ
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.names)
empty.key = self.key
empty.seen = self.seen.copy()
return empty
def checkpoint(self) -> set[Value]:
return self.seen
def from_checkpoint(self, checkpoint: Optional[set[Value]]) -> Self:
def from_checkpoint(self, checkpoint: set[Value]) -> Self:
empty = self.__class__(self.typ, self.names)
empty.key = self.key
if checkpoint is not None:
if checkpoint is not MISSING:
empty.seen = checkpoint
return empty
+17 -10
View File
@@ -1,8 +1,9 @@
from typing import Any, Generic, Iterator, Optional, Sequence, Type, Union
from typing import Any, Generic, Iterator, Sequence, Type, Union
from typing_extensions import Self
from langgraph.channels.base import BaseChannel, Value
from langgraph.constants import MISSING
from langgraph.errors import EmptyChannelError
@@ -16,9 +17,7 @@ def flatten(values: Sequence[Union[Value, list[Value]]]) -> Iterator[Value]:
class Topic(
Generic[Value],
BaseChannel[
Sequence[Value], Union[Value, list[Value]], tuple[set[Value], list[Value]]
],
BaseChannel[Sequence[Value], Union[Value, list[Value]], list[Value]],
):
"""A configurable PubSub Topic.
@@ -49,20 +48,28 @@ class Topic(
"""The type of the update received by the channel."""
return Union[self.typ, list[self.typ]] # type: ignore[name-defined]
def checkpoint(self) -> tuple[set[Value], list[Value]]:
return self.values
def from_checkpoint(self, checkpoint: Optional[list[Value]]) -> Self:
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.accumulate)
empty.key = self.key
if checkpoint is not None:
empty.values = self.values.copy()
return empty
def checkpoint(self) -> list[Value]:
return self.values
def from_checkpoint(self, checkpoint: list[Value]) -> Self:
empty = self.__class__(self.typ, self.accumulate)
empty.key = self.key
if checkpoint is not MISSING:
if isinstance(checkpoint, tuple):
# backwards compatibility
empty.values = checkpoint[1]
else:
empty.values = checkpoint
return empty
def update(self, values: Sequence[Union[Value, list[Value]]]) -> None:
def update(self, values: Sequence[Union[Value, list[Value]]]) -> bool:
current = list(self.values)
if not self.accumulate:
self.values = list[Value]()
@@ -1,4 +1,4 @@
from typing import Generic, Optional, Sequence, Type
from typing import Generic, Sequence, Type
from typing_extensions import Self
@@ -30,10 +30,17 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
"""The type of the update received by the channel."""
return self.typ
def checkpoint(self) -> Value:
raise EmptyChannelError()
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.guard)
empty.key = self.key
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Optional[Value]) -> Self:
def checkpoint(self) -> Value:
return MISSING
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.guard)
empty.key = self.key
return empty
+2
View File
@@ -83,6 +83,8 @@ CONFIG_KEY_PREVIOUS = sys.intern("__pregel_previous")
# holds the previous return value from a stateful Pregel graph.
CONFIG_KEY_RUNNER_SUBMIT = sys.intern("__pregel_runner_submit")
# holds a function that receives tasks from runner, executes them and returns results
CONFIG_KEY_CHECKPOINT_DURING = sys.intern("__pregel_checkpoint_during")
# holds a boolean indicating whether to checkpoint during the run (or only at the end)
# --- Other constants ---
PUSH = sys.intern("__pregel_push")
+10 -1
View File
@@ -27,6 +27,8 @@ from langgraph.graph.state import StateGraph
Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation]
REMOVE_ALL_MESSAGES = "__remove_all__"
def _add_messages_wrapper(func: Callable) -> Callable[[Messages, Messages], Messages]:
def _add_messages(
@@ -158,6 +160,7 @@ def add_messages(
Support for 'format="langchain-openai"' flag added.
"""
remove_all_idx = None
# coerce to list
if not isinstance(left, list):
left = [left] # type: ignore[assignment]
@@ -176,9 +179,15 @@ def add_messages(
for m in left:
if m.id is None:
m.id = str(uuid.uuid4())
for m in right:
for idx, m in enumerate(right):
if m.id is None:
m.id = str(uuid.uuid4())
if isinstance(m, RemoveMessage) and m.id == REMOVE_ALL_MESSAGES:
remove_all_idx = idx
if remove_all_idx is not None:
return right[remove_all_idx + 1 :]
# merge
merged = left.copy()
merged_by_id = {m.id: i for i, m in enumerate(merged)}
+160 -50
View File
@@ -1,3 +1,4 @@
import functools
import logging
import weakref
from inspect import isclass
@@ -16,6 +17,8 @@ from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from typing_extensions import Annotated
__all__ = ["SchemaCoercionMapper"]
logger = logging.getLogger(__name__)
@@ -25,54 +28,60 @@ _cache: weakref.WeakKeyDictionary[Type[Any], dict[int, "SchemaCoercionMapper"]]
class SchemaCoercionMapper:
"""Lightweight coercion of *dict* → *BaseModel* instances."""
def __new__(
cls,
schema: Type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
) -> "SchemaCoercionMapper":
if schema not in _cache:
_cache[schema] = {}
if max_depth in _cache[schema]:
return _cache[schema][max_depth]
by_depth = _cache.setdefault(schema, {})
if max_depth in by_depth:
return by_depth[max_depth]
inst = super().__new__(cls)
_cache[schema][max_depth] = inst
by_depth[max_depth] = inst
return inst
def __init__(
self,
schema: Type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
):
if hasattr(self, "_inited"):
) -> None:
if hasattr(self, "_initialised"):
return
self._inited = True
self._initialised = True
self.schema = schema
self.max_depth = max_depth
self.type_hints = (
type_hints
if type_hints is not None
else get_type_hints(schema, localns={schema.__name__: schema})
)
self.max_depth = max_depth
if issubclass(schema, BaseModel):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.model_fields.items()
}
self._construct: Callable[..., Any] = schema.model_construct
elif issubclass(schema, BaseModelV1):
if issubclass(schema, BaseModelV1):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.__fields__.items()
}
self._construct = schema.construct
elif issubclass(schema, BaseModel):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.model_fields.items()
}
self._construct: Callable[..., Any] = schema.model_construct # type: ignore
else:
raise TypeError("Schema is neither valid Pydantic v1 nor v2 model.")
self._field_coercers: Optional[dict[str, Callable[[Any, Any], Any]]] = None
raise TypeError("Schema is neither a Pydantic v1 nor v2 model.")
self._field_coercers: Optional[dict[str, Callable[[Any, int], Any]]] = None
def __call__(self, input_data: Any, depth: Optional[int] = None) -> Any:
return self.coerce(input_data, depth)
@@ -82,45 +91,51 @@ class SchemaCoercionMapper:
depth = self.max_depth
if not isinstance(input_data, dict) or depth <= 0:
return input_data
processed = {}
if self._field_coercers is None:
self._field_coercers = {
n: self._build_coercer(t, depth - 1) for n, t in self._fields.items()
}
processed: dict[str, Any] = {}
for k, v in input_data.items():
fn = self._field_coercers.get(k)
processed[k] = fn(v, depth - 1) if fn else v
return self._construct(**processed)
def _build_coercer(
self, field_type: Any, depth: int, throw: bool = False
self, field_type: Any, depth: int, *, throw: bool = False
) -> Callable[[Any, Any], Any]:
if depth == 0:
return self._passthrough
origin = get_origin(field_type)
if (field_type in _IDENTITY_TYPES) or (origin in _IDENTITY_TYPES):
return self._passthrough
if origin is Annotated:
real_type, *_ = get_args(field_type)
sub = self._build_coercer(real_type, depth - 1)
return lambda v, d: sub(v, d)
if isclass(field_type):
# This is needed bcs. of issubclass issues on older versions of python
is_class_ = True
try:
is_base_model = issubclass(field_type, BaseModel)
is_bm_v2 = issubclass(field_type, BaseModel)
except TypeError:
# python < 3.11 issue.
is_class_ = False
is_base_model = False
is_bm_v2 = False
if is_bm_v2 or (is_class_ and issubclass(field_type, BaseModelV1)):
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
if is_base_model:
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
if is_class_ and issubclass(field_type, BaseModelV1):
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
if origin is list or field_type is list:
if origin is list:
args = get_args(field_type)
if len(args) != 1:
return lambda v, d: v
return self._passthrough
sub = self._build_coercer(args[0], depth - 1)
def list_coercer(v: Any, d: Any) -> Any:
@@ -129,15 +144,21 @@ class SchemaCoercionMapper:
return [sub(x, d - 1) for x in v]
return list_coercer
if origin is set or field_type is set:
args = get_args(field_type)
if len(args) != 1:
return lambda v, d: v
sub = self._build_coercer(args[0], depth - 1)
if len(args) > 1:
return self._passthrough
elif len(args) == 1:
sub = self._build_coercer(args[0], depth - 1)
else:
sub = None # type: ignore
def set_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, (list, tuple, set)):
return v
if sub is None:
return set(v)
return {sub(x, d - 1) for x in v}
return set_coercer
@@ -165,20 +186,19 @@ class SchemaCoercionMapper:
return dict_coercer
if origin is tuple:
targs = get_args(field_type)
if not targs:
return lambda v, d: v
subs = [self._build_coercer(a, depth - 1) for a in targs]
elem_types = get_args(field_type)
if not elem_types:
return self._passthrough
subs = [self._build_coercer(t, depth - 1) for t in elem_types]
return lambda v, d: (
tuple(
subs[i](v[i] if i < len(v) else None, d - 1)
for i in range(len(subs))
)
if isinstance(v, (list, tuple))
else v
)
def tuple_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, (list, tuple)):
return v
out = []
for i, sp in enumerate(subs):
out.append(sp(v[i] if i < len(v) else None, d - 1))
return tuple(out)
return tuple_coercer
if origin is Union:
uargs = get_args(field_type)
subs, none_in_union = [], False
@@ -204,7 +224,97 @@ class SchemaCoercionMapper:
return v
return union_coercer
return self._passthrough
def _passthrough(self, v: Any, d: Any) -> Any:
adapter_fn = _get_adapter(field_type)
return lambda v, _d: adapter_fn(v)
@staticmethod
def _passthrough(v: Any, _d: Any) -> Any: # noqa: D401
return v
_adapter_cache: dict[Any, Callable[[Any], Any]] = {}
_IDENTITY_TYPES: tuple[type[Any], ...] = (
int,
float,
str,
bool,
bytes,
bytearray,
complex,
memoryview,
type(None),
)
try:
# Pydantic v2.
from pydantic import TypeAdapter
try:
import pydantic.v1.types as v1_types_
from pydantic.v1 import parse_obj_as
v1_types = tuple(
v for k, v in vars(v1_types_).items() if k in v1_types_.__all__
)
except ImportError:
v1_types = ()
def parse_obj_as(tp: Any, v: Any) -> Any: # type: ignore
return v
try:
from pydantic.v1 import parse_obj_as
from pydantic.v1.main import create_model
except ImportError:
create_model = None # type: ignore
def _get_v1_parser(tp: Any) -> Any:
if create_model is not None:
try:
parser = create_model(
f"ParsingModel[{tp}]",
__root__=(tp, ...),
)
return lambda v: parser(__root__=v).__root__ # type: ignore
except RuntimeError:
return lambda v: v
return lambda v: parse_obj_as(tp, v)
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
if tp in v1_types:
return _get_v1_parser(tp)
try:
return TypeAdapter(
tp, config={"arbitrary_types_allowed": True}
).validate_python
except TypeError:
# Delayed classes like ConstrainedList
return _get_v1_parser(tp)
except ImportError:
# Pydantic V1
from pydantic.v1.main import create_model
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
try:
parser = create_model(
f"ParsingModel[{tp}]",
__root__=(tp, ...),
)
return lambda v: parser(__root__=v).__root__ # type: ignore
except RuntimeError:
return lambda v: v
def _get_adapter(tp: Any) -> Callable[[Any], Any]:
try:
return _adapter_cache[tp]
except KeyError:
fn = _adapter_for(tp)
_adapter_cache[tp] = fn
return fn
+141 -43
View File
@@ -2,6 +2,7 @@ import inspect
import logging
import typing
import warnings
from collections import defaultdict
from functools import partial
from inspect import isclass, isfunction, ismethod, signature
from types import FunctionType
@@ -35,7 +36,16 @@ from langgraph.channels.dynamic_barrier_value import DynamicBarrierValue, WaitFo
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.channels.last_value import LastValue
from langgraph.channels.named_barrier_value import NamedBarrierValue
from langgraph.constants import EMPTY_SEQ, MISSING, NS_END, NS_SEP, SELF, TAG_HIDDEN
from langgraph.checkpoint.base import Checkpoint
from langgraph.constants import (
EMPTY_SEQ,
INTERRUPT,
MISSING,
NS_END,
NS_SEP,
SELF,
TAG_HIDDEN,
)
from langgraph.errors import (
ErrorCode,
InvalidUpdateError,
@@ -242,7 +252,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
Will take the name of the function/runnable as the node name.
@@ -267,7 +277,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
@@ -291,7 +301,7 @@ class StateGraph(Graph):
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
retry: Optional[RetryPolicy] = None,
destinations: Optional[Union[dict[str, str], tuple[str]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Adds a new node to the state graph.
@@ -303,7 +313,7 @@ class StateGraph(Graph):
metadata (Optional[dict[str, Any]]): The metadata associated with the node. (default: None)
input (Optional[Type[Any]]): The input schema for the node. (default: the graph's input schema)
retry (Optional[RetryPolicy]): The policy for retrying the node. (default: None)
destinations (Optional[Union[dict[str, str], tuple[str]]]): Destinations that indicate where a node can route to.
destinations (Optional[Union[dict[str, str], tuple[str, ...]]]): Destinations that indicate where a node can route to.
This is useful for edgeless graphs with nodes that return `Command` objects.
If a dict is provided, the keys will be used as the target node names and the values will be used as the labels for the edges.
If a tuple is provided, the values will be used as the target node names.
@@ -766,13 +776,13 @@ class CompiledStateGraph(CompiledGraph):
return updates
elif (t := type(input)) and get_type_hints(t):
# Pydantic v2
if isinstance(input, BaseModel):
keep: Optional[set[str]] = input.model_fields_set
if isinstance(input, BaseModelV1):
keep: Optional[set[str]] = input.__fields_set__
defaults = {k: v.default for k, v in t.__fields__.items()}
elif isinstance(input, BaseModel):
keep = input.model_fields_set
defaults = {k: v.default for k, v in input.model_fields.items()}
# Pydantic v1
elif isinstance(input, BaseModelV1):
keep = input.__fields_set__
defaults = {k: v.default for k, v in t.__fields__.items()}
else:
keep = None
defaults = {}
@@ -799,11 +809,11 @@ class CompiledStateGraph(CompiledGraph):
raise InvalidUpdateError(msg)
# state updaters
write_entries: list[Union[ChannelWriteEntry, ChannelWriteTupleEntry]] = [
write_entries: tuple[Union[ChannelWriteEntry, ChannelWriteTupleEntry], ...] = (
ChannelWriteTupleEntry(
mapper=_get_root if output_keys == ["__root__"] else _get_updates
)
]
),
)
# add node and output channel
if key == START:
@@ -811,20 +821,14 @@ class CompiledStateGraph(CompiledGraph):
tags=[TAG_HIDDEN],
triggers=[START],
channels=[START],
writers=[
ChannelWrite(
write_entries,
tags=[TAG_HIDDEN],
),
],
writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])],
)
elif node is not None:
input_schema = node.input if node else self.builder.schema
input_values = {k: k for k in self.builder.schemas[input_schema]}
is_single_input = len(input_values) == 1 and "__root__" in input_values
branch_channel = f"branch:to:{key}"
self.channels[key] = EphemeralValue(Any, guard=False)
branch_channel = CHANNEL_BRANCH_TO.format(key)
self.channels[branch_channel] = EphemeralValue(Any, guard=False)
self.nodes[key] = PregelNode(
triggers=[branch_channel],
@@ -836,13 +840,8 @@ class CompiledStateGraph(CompiledGraph):
input_schema,
self.builder.type_hints[input_schema],
),
writers=[
# publish to this channel and state keys
ChannelWrite(
write_entries + [ChannelWriteEntry(key, key)],
tags=[TAG_HIDDEN],
),
],
# publish to state keys
writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])],
metadata=node.metadata,
retry_policy=node.retry_policy,
bound=node.runnable,
@@ -852,21 +851,13 @@ class CompiledStateGraph(CompiledGraph):
def attach_edge(self, starts: Union[str, Sequence[str]], end: str) -> None:
if isinstance(starts, str):
if starts == START:
channel_name = f"start:{end}"
# register channel
self.channels[channel_name] = EphemeralValue(Any)
# subscribe to channel
self.nodes[end].triggers.append(channel_name)
# publish to channel
self.nodes[START].writers.append(
# subscribe to start channel
if end != END:
self.nodes[starts].writers.append(
ChannelWrite(
[ChannelWriteEntry(channel_name, START)], tags=[TAG_HIDDEN]
(ChannelWriteEntry(CHANNEL_BRANCH_TO.format(end), None),)
)
)
elif end != END:
# subscribe to start channel
self.nodes[end].triggers.append(starts)
elif end != END:
channel_name = f"join:{'+'.join(starts)}:{end}"
# register channel
@@ -877,7 +868,7 @@ class CompiledStateGraph(CompiledGraph):
for start in starts:
self.nodes[start].writers.append(
ChannelWrite(
[ChannelWriteEntry(channel_name, start)], tags=[TAG_HIDDEN]
(ChannelWriteEntry(channel_name, start),), tags=[TAG_HIDDEN]
)
)
@@ -890,7 +881,7 @@ class CompiledStateGraph(CompiledGraph):
if filtered := [p for p in packets if p != END]:
writes = [
(
ChannelWriteEntry(f"branch:to:{p}", start)
ChannelWriteEntry(CHANNEL_BRANCH_TO.format(p), None)
if not isinstance(p, Send)
else p
)
@@ -941,6 +932,110 @@ class CompiledStateGraph(CompiledGraph):
)
)
def _migrate_checkpoint(self, checkpoint: Checkpoint) -> None:
"""Migrate a checkpoint to new channel layout."""
values = checkpoint["channel_values"]
versions = checkpoint["channel_versions"]
seen = checkpoint["versions_seen"]
# empty checkpoints do not need migration
if not versions:
return
# current version
if checkpoint["v"] >= 3:
return
# Migrate from start:node to branch:to:node
for k in list(versions):
if k.startswith("start:"):
# confirm node is present
node = k.split(":")[1]
if node not in self.nodes:
continue
# get next version
new_k = f"branch:to:{node}"
new_v = (
max(versions[new_k], versions.pop(k))
if new_k in versions
else versions.pop(k)
)
# update seen
for ss in (seen.get(node, {}), seen.get(INTERRUPT, {})):
if k in ss:
s = ss.pop(k)
if new_k in ss:
ss[new_k] = max(s, ss[new_k])
else:
ss[new_k] = s
# update value
if new_k not in values and k in values:
values[new_k] = values.pop(k)
# update version
versions[new_k] = new_v
# Migrate from branch:source:condition:node to branch:to:node
for k in list(versions):
if k.startswith("branch:") and k.count(":") == 3:
# confirm node is present
node = k.split(":")[-1]
if node not in self.nodes:
continue
# get next version
new_k = f"branch:to:{node}"
new_v = (
max(versions[new_k], versions.pop(k))
if new_k in versions
else versions.pop(k)
)
# update seen
for ss in (seen.get(node, {}), seen.get(INTERRUPT, {})):
if k in ss:
s = ss.pop(k)
if new_k in ss:
ss[new_k] = max(s, ss[new_k])
else:
ss[new_k] = s
# update value
if new_k not in values and k in values:
values[new_k] = values.pop(k)
# update version
versions[new_k] = new_v
if not set(self.nodes).isdisjoint(versions):
# Migrate from "node" to "branch:to:node"
source_to_target = defaultdict(list)
for start, end in self.builder.edges:
if start != START and end != END:
source_to_target[start].append(end)
for k in list(versions):
if k == START:
continue
if k in self.nodes:
v = versions.pop(k)
c = values.pop(k, MISSING)
for end in source_to_target[k]:
# get next version
new_k = f"branch:to:{end}"
new_v = max(versions[new_k], v) if new_k in versions else v
# update seen
for ss in (seen.get(end, {}), seen.get(INTERRUPT, {})):
if k in ss:
s = ss.pop(k)
if new_k in ss:
ss[new_k] = max(s, ss[new_k])
else:
ss[new_k] = s
# update value
if new_k not in values and c is not MISSING:
values[new_k] = c
# update version
versions[new_k] = new_v
# pop interrupt seen
if INTERRUPT in seen:
seen[INTERRUPT].pop(k, MISSING)
def _get_state_reader(
builder: StateGraph, schema: Type[Any]
@@ -965,7 +1060,7 @@ def _pick_mapper(
if issubclass(schema, dict):
return None
if issubclass(schema, (BaseModel, BaseModelV1)):
return SchemaCoercionMapper(schema, type_hints)
return SchemaCoercionMapper(schema, type_hints=type_hints)
return partial(_coerce_state, schema)
@@ -1166,3 +1261,6 @@ def _get_schema(
if k in channels and isinstance(channels[k], BaseChannel)
},
)
CHANNEL_BRANCH_TO = "branch:to:{}"
+206
View File
@@ -0,0 +1,206 @@
from typing import Any, Literal, Optional, Union
from uuid import uuid4
from langchain_core.messages import AnyMessage
from typing_extensions import TypedDict
from langgraph.constants import CONF, CONFIG_KEY_SEND
from langgraph.utils.config import get_config, get_stream_writer
class UIMessage(TypedDict):
"""A message type for UI updates in LangGraph.
This TypedDict represents a UI message that can be sent to update the UI state.
It contains information about the UI component to render and its properties.
Attributes:
type: Literal type indicating this is a UI message.
id: Unique identifier for the UI message.
name: Name of the UI component to render.
props: Properties to pass to the UI component.
metadata: Additional metadata about the UI message.
"""
type: Literal["ui"]
id: str
name: str
props: dict[str, Any]
metadata: dict[str, Any]
class RemoveUIMessage(TypedDict):
"""A message type for removing UI components in LangGraph.
This TypedDict represents a message that can be sent to remove a UI component
from the current state.
Attributes:
type: Literal type indicating this is a remove-ui message.
id: Unique identifier of the UI message to remove.
"""
type: Literal["remove-ui"]
id: str
AnyUIMessage = Union[UIMessage, RemoveUIMessage]
def push_ui_message(
name: str,
props: dict[str, Any],
*,
id: Optional[str] = None,
metadata: Optional[dict[str, Any]] = None,
message: Optional[AnyMessage] = None,
state_key: str = "ui",
) -> UIMessage:
"""Push a new UI message to update the UI state.
This function creates and sends a UI message that will be rendered in the UI.
It also updates the graph state with the new UI message.
Args:
name: Name of the UI component to render.
props: Properties to pass to the UI component.
id: Optional unique identifier for the UI message.
If not provided, a random UUID will be generated.
metadata: Optional additional metadata about the UI message.
message: Optional message object to associate with the UI message.
state_key: Key in the graph state where the UI messages are stored.
Defaults to "ui".
Returns:
The created UI message.
Example:
.. code-block:: python
push_ui_message(
name="component-name",
props={"content": "Hello world"},
)
"""
writer = get_stream_writer()
config = get_config()
message_id = None
if message:
if isinstance(message, dict) and "id" in message:
message_id = message.get("id")
elif hasattr(message, "id"):
message_id = message.id
evt: UIMessage = {
"type": "ui",
"id": id or str(uuid4()),
"name": name,
"props": props,
"metadata": {
**(config.get("metadata") or {}),
"tags": config.get("tags", None),
"name": config.get("run_name", None),
"run_id": config.get("run_id", None),
**(metadata or {}),
**({"message_id": message_id} if message_id else {}),
},
}
writer(evt)
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
return evt
def delete_ui_message(id: str, *, state_key: str = "ui") -> RemoveUIMessage:
"""Delete a UI message by ID from the UI state.
This function creates and sends a message to remove a UI component from the current state.
It also updates the graph state to remove the UI message.
Args:
id: Unique identifier of the UI component to remove.
state_key: Key in the graph state where the UI messages are stored. Defaults to "ui".
Returns:
The remove UI message.
Example:
.. code-block:: python
delete_ui_message("message-123")
"""
writer = get_stream_writer()
config = get_config()
evt: RemoveUIMessage = {"type": "remove-ui", "id": id}
writer(evt)
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
return evt
def ui_message_reducer(
left: Union[list[AnyUIMessage], AnyUIMessage],
right: Union[list[AnyUIMessage], AnyUIMessage],
) -> list[AnyUIMessage]:
"""Merge two lists of UI messages, supporting removing UI messages.
This function combines two lists of UI messages, handling both regular UI messages
and `remove-ui` messages. When a `remove-ui` message is encountered, it removes any
UI message with the matching ID from the current state.
Args:
left: First list of UI messages or single UI message.
right: Second list of UI messages or single UI message.
Returns:
Combined list of UI messages with removals applied.
Example:
.. code-block:: python
messages = ui_message_reducer(
[{"type": "ui", "id": "1", "name": "Chat", "props": {}}],
{"type": "remove-ui", "id": "1"}
)
"""
if not isinstance(left, list):
left = [left]
if not isinstance(right, list):
right = [right]
# merge messages
merged = left.copy()
merged_by_id = {m.get("id"): i for i, m in enumerate(merged)}
ids_to_remove = set()
for msg in right:
msg_id = msg.get("id")
if (existing_idx := merged_by_id.get(msg_id)) is not None:
if msg.get("type") == "remove-ui":
ids_to_remove.add(msg_id)
else:
ids_to_remove.discard(msg_id)
merged[existing_idx] = msg
else:
if msg.get("type") == "remove-ui":
raise ValueError(
f"Attempting to delete an UI message with an ID that doesn't exist ('{msg_id}')"
)
merged_by_id[msg_id] = len(merged)
merged.append(msg)
merged = [m for m in merged if m.get("id") not in ids_to_remove]
return merged
+56 -22
View File
@@ -39,7 +39,6 @@ from langchain_core.runnables.utils import (
ConfigurableFieldSpec,
get_unique_config_specs,
)
from langchain_core.tracers._streaming import _StreamingCallbackHandler
from pydantic import BaseModel
from typing_extensions import Self
@@ -48,13 +47,13 @@ from langgraph.channels.base import (
)
from langgraph.checkpoint.base import (
BaseCheckpointSaver,
Checkpoint,
CheckpointTuple,
copy_checkpoint,
create_checkpoint,
empty_checkpoint,
)
from langgraph.constants import (
CONF,
CONFIG_KEY_CHECKPOINT_DURING,
CONFIG_KEY_CHECKPOINT_ID,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_CHECKPOINTER,
@@ -91,6 +90,7 @@ from langgraph.pregel.algo import (
local_write,
prepare_next_tasks,
)
from langgraph.pregel.checkpoint import create_checkpoint, empty_checkpoint
from langgraph.pregel.debug import tasks_w_writes
from langgraph.pregel.io import map_input, read_channels
from langgraph.pregel.loop import AsyncPregelLoop, StreamProtocol, SyncPregelLoop
@@ -125,6 +125,11 @@ from langgraph.utils.fields import get_enhanced_type_hints
from langgraph.utils.pydantic import create_model, is_supported_by_pydantic
from langgraph.utils.queue import AsyncQueue, SyncQueue # type: ignore[attr-defined]
try:
from langchain_core.tracers._streaming import _StreamingCallbackHandler
except ImportError:
_StreamingCallbackHandler = None # type: ignore
WriteValue = Union[Callable[[Input], Output], Any]
@@ -767,6 +772,10 @@ class Pregel(PregelProtocol):
for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse):
yield name, node
def _migrate_checkpoint(self, checkpoint: Checkpoint) -> None:
"""Migrate a saved checkpoint to new channel layout."""
pass
def _prepare_state_snapshot(
self,
config: RunnableConfig,
@@ -785,6 +794,9 @@ class Pregel(PregelProtocol):
tasks=(),
)
# migrate checkpoint if needed
self._migrate_checkpoint(saved.checkpoint)
with ChannelsManager(
self.channels,
saved.checkpoint,
@@ -898,6 +910,9 @@ class Pregel(PregelProtocol):
tasks=(),
)
# migrate checkpoint if needed
self._migrate_checkpoint(saved.checkpoint)
async with AsyncChannelsManager(
self.channels,
saved.checkpoint,
@@ -1223,6 +1238,8 @@ class Pregel(PregelProtocol):
# get last checkpoint
config = ensure_config(self.config, input_config)
saved = checkpointer.get_tuple(config)
if saved is not None:
self._migrate_checkpoint(saved.checkpoint)
checkpoint = (
copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint()
)
@@ -1535,12 +1552,9 @@ class Pregel(PregelProtocol):
),
CONFIG_KEY_READ: partial(
local_read,
step + 1,
checkpoint,
channels,
managed,
task,
config,
),
},
),
@@ -1636,6 +1650,8 @@ class Pregel(PregelProtocol):
# get last checkpoint
config = ensure_config(self.config, input_config)
saved = await checkpointer.aget_tuple(config)
if saved is not None:
self._migrate_checkpoint(saved.checkpoint)
checkpoint = (
copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint()
)
@@ -1944,12 +1960,9 @@ class Pregel(PregelProtocol):
),
CONFIG_KEY_READ: partial(
local_read,
step + 1,
checkpoint,
channels,
managed,
task,
config,
),
},
),
@@ -2086,6 +2099,7 @@ class Pregel(PregelProtocol):
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
subgraphs: bool = False,
) -> Iterator[Union[dict[str, Any], Any]]:
@@ -2107,6 +2121,7 @@ class Pregel(PregelProtocol):
output_keys: The keys to stream, defaults to all non-context channels.
interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph.
interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph.
checkpoint_during: Whether to checkpoint intermediate steps, defaults to True. If False, only the final checkpoint is saved.
debug: Whether to print debug information during execution, defaults to False.
subgraphs: Whether to stream subgraphs, defaults to False.
@@ -2268,6 +2283,9 @@ class Pregel(PregelProtocol):
config[CONF][CONFIG_KEY_STREAM_WRITER] = lambda c: stream.put(
((), "custom", c)
)
# set checkpointing mode for subgraphs
if checkpoint_during is not None:
config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during
with SyncPregelLoop(
input,
input_model=self.input_model,
@@ -2283,7 +2301,11 @@ class Pregel(PregelProtocol):
interrupt_after=interrupt_after_,
manager=run_manager,
debug=debug,
checkpoint_during=checkpoint_during
if checkpoint_during is not None
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True),
trigger_to_nodes=self.trigger_to_nodes,
migrate_checkpoint=self._migrate_checkpoint,
) as loop:
# create runner
runner = PregelRunner(
@@ -2364,6 +2386,7 @@ class Pregel(PregelProtocol):
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
subgraphs: bool = False,
) -> AsyncIterator[Union[dict[str, Any], Any]]:
@@ -2385,6 +2408,7 @@ class Pregel(PregelProtocol):
output_keys: The keys to stream, defaults to all non-context channels.
interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph.
interrupt_after: Nodes to interrupt after, defaults to all nodes in the graph.
checkpoint_during: Whether to checkpoint intermediate steps, defaults to True. If False, only the final checkpoint is saved.
debug: Whether to print debug information during execution, defaults to False.
subgraphs: Whether to stream subgraphs, defaults to False.
@@ -2520,13 +2544,17 @@ class Pregel(PregelProtocol):
run_id=config.get("run_id"),
)
# if running from astream_log() run each proc with streaming
do_stream = next(
(
cast(_StreamingCallbackHandler, h)
for h in run_manager.handlers
if isinstance(h, _StreamingCallbackHandler)
),
None,
do_stream = (
next(
(
cast(_StreamingCallbackHandler, h)
for h in run_manager.handlers
if isinstance(h, _StreamingCallbackHandler)
),
None,
)
if _StreamingCallbackHandler is not None
else False
)
try:
# assign defaults
@@ -2562,6 +2590,9 @@ class Pregel(PregelProtocol):
stream.put_nowait, ((), "custom", c)
)
)
# set checkpointing mode for subgraphs
if checkpoint_during is not None:
config[CONF][CONFIG_KEY_CHECKPOINT_DURING] = checkpoint_during
async with AsyncPregelLoop(
input,
input_model=self.input_model,
@@ -2577,12 +2608,11 @@ class Pregel(PregelProtocol):
interrupt_after=interrupt_after_,
manager=run_manager,
debug=debug,
# `self.nodes` can be modified after creation of `Pregel`. For example,
# that's how StateGraph compilation currently works.
# For now, we recompute the trigger_to_nodes mapping every time the
# loop is created. We could potentially memoize this if it becomes a
# performance issue.
trigger_to_nodes=_trigger_to_nodes(self.nodes),
checkpoint_during=checkpoint_during
if checkpoint_during is not None
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True),
trigger_to_nodes=self.trigger_to_nodes,
migrate_checkpoint=self._migrate_checkpoint,
) as loop:
# create runner
runner = PregelRunner(
@@ -2656,6 +2686,7 @@ class Pregel(PregelProtocol):
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
**kwargs: Any,
) -> Union[dict[str, Any], Any]:
@@ -2687,6 +2718,7 @@ class Pregel(PregelProtocol):
output_keys=output_keys,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
checkpoint_during=checkpoint_during,
debug=debug,
**kwargs,
):
@@ -2708,6 +2740,7 @@ class Pregel(PregelProtocol):
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
**kwargs: Any,
) -> Union[dict[str, Any], Any]:
@@ -2740,6 +2773,7 @@ class Pregel(PregelProtocol):
output_keys=output_keys,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
checkpoint_during=checkpoint_during,
debug=debug,
**kwargs,
):
+76 -40
View File
@@ -1,6 +1,7 @@
import binascii
import itertools
import sys
import threading
from collections import defaultdict, deque
from functools import partial
from hashlib import sha1
@@ -32,7 +33,6 @@ from langgraph.checkpoint.base import (
Checkpoint,
PendingWrite,
V,
copy_checkpoint,
)
from langgraph.constants import (
CONF,
@@ -68,12 +68,10 @@ from langgraph.managed.base import ManagedValueMapping
from langgraph.pregel.call import get_runnable_for_task
from langgraph.pregel.io import read_channel, read_channels
from langgraph.pregel.log import logger
from langgraph.pregel.manager import ChannelsManager
from langgraph.pregel.read import PregelNode
from langgraph.store.base import BaseStore
from langgraph.types import (
All,
LoopProtocol,
PregelExecutableTask,
PregelScratchpad,
PregelTask,
@@ -168,39 +166,39 @@ def should_interrupt(
def local_read(
step: int,
checkpoint: Checkpoint,
channels: Mapping[str, BaseChannel],
managed: ManagedValueMapping,
task: WritesProtocol,
config: RunnableConfig,
select: Union[list[str], str],
fresh: bool = False,
) -> Union[dict[str, Any], Any]:
"""Function injected under CONFIG_KEY_READ in task config, to read current state.
Used by conditional edges to read a copy of the state with reflecting the writes
from that node only."""
updated: dict[str, list[Any]] = defaultdict(list)
if isinstance(select, str):
managed_keys = []
for c, _ in task.writes:
for c, v in task.writes:
if c == select:
updated = {c}
break
else:
updated = set()
updated[c].append(v)
else:
managed_keys = [k for k in select if k in managed]
select = [k for k in select if k not in managed]
updated = set(select).intersection(c for c, _ in task.writes)
for c, v in task.writes:
if c in select:
updated[c].append(v)
if fresh and updated:
with ChannelsManager(
{k: v for k, v in channels.items() if k in updated},
checkpoint,
LoopProtocol(config=config, step=step, stop=step + 1),
skip_context=True,
) as (local_channels, _):
apply_writes(copy_checkpoint(checkpoint), local_channels, [task], None)
values = read_channels({**channels, **local_channels}, select)
# apply writes
local_channels: dict[str, BaseChannel] = {}
for k in channels:
if k in updated:
cc = channels[k].copy()
cc.update(updated[k])
else:
cc = channels[k]
local_channels[k] = cc
# read fresh values
values = read_channels(local_channels, select)
else:
values = read_channels(channels, select)
if managed_keys:
@@ -334,6 +332,17 @@ def apply_writes(
return pending_writes_by_managed, updated_channels
def has_next_tasks(
trigger_to_nodes: Mapping[str, Sequence[str]],
updated_channels: set[str],
checkpoint: Checkpoint,
) -> bool:
"""Check if there are any tasks that should be run in the next step."""
return bool(checkpoint["pending_sends"]) or not updated_channels.isdisjoint(
trigger_to_nodes
)
@overload
def prepare_next_tasks(
checkpoint: Checkpoint,
@@ -561,12 +570,9 @@ def prepare_single_task(
),
CONFIG_KEY_READ: partial(
local_read,
step,
checkpoint,
channels,
managed,
PregelTaskWrites(task_path[:3], name, writes, triggers),
config,
),
CONFIG_KEY_STORE: (store or configurable.get(CONFIG_KEY_STORE)),
CONFIG_KEY_CHECKPOINTER: (
@@ -666,14 +672,11 @@ def prepare_single_task(
),
CONFIG_KEY_READ: partial(
local_read,
step,
checkpoint,
channels,
managed,
PregelTaskWrites(
task_path[:3], packet.node, writes, triggers
),
config,
),
CONFIG_KEY_STORE: (
store or configurable.get(CONFIG_KEY_STORE)
@@ -788,8 +791,6 @@ def prepare_single_task(
),
CONFIG_KEY_READ: partial(
local_read,
step,
checkpoint,
channels,
managed,
PregelTaskWrites(
@@ -798,7 +799,6 @@ def prepare_single_task(
writes,
triggers,
),
config,
),
CONFIG_KEY_STORE: (
store or configurable.get(CONFIG_KEY_STORE)
@@ -869,11 +869,30 @@ def _scratchpad(
pending_writes: list[PendingWrite],
task_id: str,
) -> PregelScratchpad:
# None cannot be used as a resume value, because it would be difficult to
# distinguish from missing when used over http
null_resume_write = next(
(w for w in pending_writes if w[0] == NULL_TASK_ID and w[1] == RESUME), None
)
if len(pending_writes) > 0:
# find global resume value
for w in pending_writes:
if w[0] == NULL_TASK_ID and w[1] == RESUME:
null_resume_write = w
break
else:
# None cannot be used as a resume value, because it would be difficult to
# distinguish from missing when used over http
null_resume_write = None
# find task-specific resume value
for w in pending_writes:
if w[0] == task_id and w[1] == RESUME:
task_resume_write = w[2]
if not isinstance(task_resume_write, list):
task_resume_write = [task_resume_write]
break
else:
task_resume_write = []
# clear var
del w
else:
null_resume_write = None
task_resume_write = []
def get_null_resume(consume: bool = False) -> Any:
if null_resume_write is None:
@@ -891,15 +910,13 @@ def _scratchpad(
# using itertools.count as an atomic counter (+= 1 is not thread-safe)
return PregelScratchpad(
# call
call_counter=itertools.count(0).__next__,
call_counter=LazyAtomicCounter(),
# interrupt
interrupt_counter=itertools.count(0).__next__,
resume=next(
(w[2] for w in pending_writes if w[0] == task_id and w[1] == RESUME), []
),
interrupt_counter=LazyAtomicCounter(),
resume=task_resume_write,
get_null_resume=get_null_resume,
# subgraph
subgraph_counter=itertools.count(0).__next__,
subgraph_counter=LazyAtomicCounter(),
)
@@ -973,3 +990,22 @@ def task_path_str(tup: Union[str, int, tuple]) -> str:
if isinstance(tup, int)
else str(tup)
)
LAZY_ATOMIC_COUNTER_LOCK = threading.Lock()
class LazyAtomicCounter:
__slots__ = ("_counter",)
_counter: Optional[Callable[[], int]]
def __init__(self) -> None:
self._counter = None
def __call__(self) -> int:
if self._counter is None:
with LAZY_ATOMIC_COUNTER_LOCK:
if self._counter is None:
self._counter = itertools.count(0).__next__
return self._counter()

Some files were not shown because too many files have changed in this diff Show More