Compare commits

..
Author SHA1 Message Date
Sydney RunkleandGitHub 2920a9dd19 fix(langgraph): Tidy up AgentState (#5801)
Fixes https://github.com/langchain-ai/langgraph/issues/5784

* Removes usage of `is_last_step`, no longer needed with
`remaining_steps`
* Make `remaining_steps` `NotRequired` so that json schema doesn't
suggest need for user input
* Move `PregelScratchpad` to shared utils file to prevent circular
import issue (it's used from `channels/managed` and other pregel files).
* Ensures that managed values wrapped in `NotRequired` or `Required` are
still recognized!
2025-08-03 07:12:53 -04:00
Eugene YurtsevandGitHub db8ed4e9e4 fix(docs): update agents.md (#5800)
fix comment in tip
2025-08-02 06:09:09 -04:00
Lauren Hirata SinghandGitHub b16fcc8468 docs: remove broken links (#5803) 2025-08-01 15:41:21 -04:00
Sydney RunkleandGitHub a2fe4df89b release: langgraph + prebuilt 0.6.3 (#5799) 2025-08-01 14:52:38 -04:00
open-swe[bot]GitHubopen-swe[bot] <open-swe@users.noreply.github.com>Sydney Runkle
69dd20e523 fix(langgraph): Add warning for incorrect node signature with mistyped config param (#5798)
Fixes: #5787

Ensures that if `config` is not typed as one of `RunanbleConfig` or
`Optional[RunnableConfig]` a warning is raised to help developers avoid
unexpected results at invocation time.

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
2025-08-01 17:25:14 +00:00
open-swe[bot]GitHubopen-swe[bot] <open-swe@users.noreply.github.com>
5152a96fce fix(docs): Correct import statement for InMemorySaver in conceptual docs (#5797)
Fixes #5781

Fixes the incorrect import statement in the Python documentation
tutorial.

- Changed import from `MemorySaver` to `InMemorySaver`
- Ensures consistency between import statement and class instantiation
- Verified through formatting and linting checks

The documentation now correctly reflects the proper import for the
InMemorySaver class.

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2025-08-01 14:54:30 +00:00
Sydney RunkleandGitHub 220314b53a fix(langgraph): fix up deprecation warnings (#5796)
Fixes https://github.com/langchain-ai/langgraph/issues/5795

* Must use `category=None` on decorator so that we get type checking
support but no dupe warning
* Fixed tuple on `confix_type` warning causing false warning
2025-08-01 14:33:46 +00:00
38bbd92e01 feat(langgraph): add durability mode for invoke and ainvoke (#5771)
Fixes https://github.com/langchain-ai/langgraph/issues/5741

Follow up to https://github.com/langchain-ai/langgraph/pull/5432

Plus clean up deprecation logic for `checkpoint_during` and add tests.

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
2025-08-01 10:30:24 -04:00
Eugene YurtsevandGitHub e3cb2dd23b chore(docs): fix more admonitions (#5792)
Fix more admonitions
2025-07-31 22:57:44 -04:00
Eugene YurtsevandGitHub 2f23d1a30d chore(docs): fix js build (#5793)
Fix js build
2025-07-31 22:57:32 -04:00
28 changed files with 345 additions and 97 deletions
+3
View File
@@ -18,6 +18,9 @@ build-prebuilt:
build-docs: build-prebuilt
TARGET_LANGUAGE=python uv run python -m mkdocs build --clean -f mkdocs.yml --strict
build-docs-js: build-prebuilt
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
llms-text:
uv run python -m _scripts.generate_llms_text docs/llms-full.txt
+2 -2
View File
@@ -29,7 +29,7 @@ pip install -U langgraph "langchain[anthropic]"
!!! info
LangChain is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
`langchain[anthropic]` is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
:::
@@ -41,7 +41,7 @@ npm install @langchain/langgraph @langchain/core @langchain/anthropic
!!! info
LangChain is installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
`@langchain/core` `@langchain/anthropic` are installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
:::
+2 -1
View File
@@ -35,7 +35,8 @@ LangGraph Platform provides different security defaults:
- Can be customized with your auth handler
!!! note "Custom auth"
Custom auth **is supported** for all plans in LangGraph Platform.
Custom auth **is supported** for all plans in LangGraph Platform.
### Self-Hosted
+45
View File
@@ -51,6 +51,51 @@ For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_a
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
:::
## Durability modes
LangGraph supports three durability modes that allow you to balance performance and data consistency based on your application's requirements. The durability modes, from least to most durable, are as follows:
- [`"exit"`](#exit)
- [`"async"`](#async)
- [`"sync"`](#sync)
A higher durability mode add more overhead to the workflow execution.
!!! version-added "Added in v0.6.0"
Use the `durability` parameter instead of `checkpoint_during` (deprecated in v0.6.0) for persistence policy management:
* `durability="async"` replaces `checkpoint_during=True`
* `durability="exit"` replaces `checkpoint_during=False`
for persistence policy management, with the following mapping:
* `checkpoint_during=True` -> `durability="async"`
* `checkpoint_during=False` -> `durability="exit"`
### `"exit"`
Changes are persisted only when graph execution completes (either successfully or with an error). This provides the best performance for long-running graphs but means intermediate state is not saved, so you cannot recover from mid-execution failures or interrupt the graph execution.
### `"async"`
Changes are persisted asynchronously while the next step executes. This provides good performance and durability, but there's a small risk that checkpoints might not be written if the process crashes during execution.
### `"sync"`
Changes are persisted synchronously before the next step starts. This ensures that every checkpoint is written before continuing execution, providing high durability at the cost of some performance overhead.
You can specify the durability mode when calling any graph execution method:
:::python
```python
graph.stream(
{"input": "test"},
durability="sync"
)
```
:::
## Using tasks in nodes
If a [node](./low_level.md#nodes) contains multiple operations, you may find it easier to convert each operation into a **task** rather than refactor the operations into individual nodes.
+3 -6
View File
@@ -88,8 +88,6 @@ Typically, all graph nodes communicate with a single schema. This means that the
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`.
See [this guide](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) for more detail.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
Let's look at an example:
@@ -473,7 +471,7 @@ const builder = new StateGraph(State);
:::
Behind the scenes, functions are converted to [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)s, which add batch and async support to your function, along with native tracing and debugging.
Behind the scenes, functions are converted to [RunnableLambda](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.RunnableLambda.html)s, which add batch and async support to your function, along with native tracing and debugging.
If you add a node to a graph without specifying a name, it will be given a default name equivalent to the function name.
@@ -701,7 +699,8 @@ graph.addConditionalEdges("nodeA", routingFunction, {
:::
!!! tip
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
### Entry Point
@@ -820,7 +819,6 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
return Command(update={"foo": "baz"}, goto="my_other_node")
```
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
:::
:::js
@@ -860,7 +858,6 @@ builder.addNode("myNode", myNode, {
});
```
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
:::
!!! important
+1 -1
View File
@@ -145,7 +145,7 @@ def my_node(state, config):
By default, if you add custom authorization on your resources, this will also apply to interactions made from the Studio. If you want, you can handle logged-in Studio users differently by checking [is_studio_user()](../../reference/functions/sdk_auth.isStudioUser.html).
!!! note
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
```python
from langgraph_sdk.auth import is_studio_user, Auth
+1 -1
View File
@@ -57,7 +57,7 @@ def create_handoff_tool(*, agent_name: str, description: str | None = None):
return handoff_tool
```
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the @[InjectedState][InjectedState] annotation.
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the @[InjectedState] annotation.
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
3. Name of the agent or node to hand off to.
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
@@ -17,7 +17,7 @@ Create a `MemorySaver` checkpointer:
:::python
```python
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.memory import InMemorySaver
memory = InMemorySaver()
```
@@ -447,3 +447,4 @@ const graph = new StateGraph(State)
## Next steps
In the next tutorial, you will [add human-in-the-loop to the chatbot](./4-human-in-the-loop.md) to handle situations where it may need guidance or verification before proceeding.
Generated
+16 -2
View File
@@ -2337,7 +2337,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.6.1"
version = "0.6.2"
source = { editable = "../libs/langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -2524,6 +2524,7 @@ docs = [
{ name = "markdown-include" },
{ name = "mkdocs" },
{ name = "mkdocs-exclude" },
{ name = "mkdocs-exclude-search" },
{ name = "mkdocs-git-committers-plugin-2" },
{ name = "mkdocs-include-markdown-plugin" },
{ name = "mkdocs-material", extra = ["imaging"] },
@@ -2595,6 +2596,7 @@ docs = [
{ name = "markdown-include" },
{ name = "mkdocs" },
{ name = "mkdocs-exclude" },
{ name = "mkdocs-exclude-search" },
{ name = "mkdocs-git-committers-plugin-2" },
{ name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" },
{ name = "mkdocs-material", extras = ["imaging"] },
@@ -2641,7 +2643,7 @@ test = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.1"
version = "0.6.2"
source = { editable = "../libs/prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -3030,6 +3032,18 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/54/b5/3a8e289282c9e8d7003f8a2f53d673d4fdaa81d493dc6966092d9985b6fc/mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51", size = 6751, upload-time = "2019-02-20T23:34:12.81Z" }
[[package]]
name = "mkdocs-exclude-search"
version = "0.6.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mkdocs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1d/52/8243589d294cf6091c1145896915fe50feea0e91d64d843942d0175770c2/mkdocs-exclude-search-0.6.6.tar.gz", hash = "sha256:3cdff1b9afdc1b227019cd1e124f401453235b92153d60c0e5e651a76be4f044", size = 9501, upload-time = "2023-12-03T22:58:21.259Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ef/9af45ffb1bdba684a0694922abae0bb771e9777aba005933f838b7f1bcea/mkdocs_exclude_search-0.6.6-py3-none-any.whl", hash = "sha256:2b4b941d1689808db533fe4a6afba75ce76c9bab8b21d4e31efc05fd8c4e0a4f", size = 7821, upload-time = "2023-12-03T22:58:19.355Z" },
]
[[package]]
name = "mkdocs-get-deps"
version = "0.2.0"
@@ -4,6 +4,7 @@ import asyncio
import enum
import inspect
import sys
import warnings
from collections.abc import (
AsyncIterator,
Awaitable,
@@ -303,6 +304,16 @@ class RunnableCallable(Runnable):
if typ != (ANY_TYPE,) and p.annotation not in typ:
# A specific type is required, but the function annotation does
# not match the expected type.
# If this is a config parameter with incorrect typing, emit a warning
# because we used to support any type but are moving towards more correct typing
if kw == "config" and p.annotation != inspect.Parameter.empty:
warnings.warn(
f"The 'config' parameter should be typed as 'RunnableConfig' or "
f"'RunnableConfig | None', not '{p.annotation}'. ",
UserWarning,
stacklevel=4,
)
continue
# If the kwarg is accepted by the function, store the key / runtime attribute to inject
+1 -1
View File
@@ -91,7 +91,7 @@ class GraphInterrupt(GraphBubbleUp):
@deprecated(
"NodeInterrupt is deprecated. Please use `langgraph.types.interrupt` instead.",
stacklevel=2,
category=None,
)
class NodeInterrupt(GraphInterrupt):
"""Raised by a node to interrupt execution.
+10 -3
View File
@@ -128,7 +128,6 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
Use this to expose immutable context data to your nodes, like user_id, db_conn, etc.
input_schema: The schema class that defines the input to the graph.
output_schema: The schema class that defines the output from the graph.
name: The default name to use when compiling the graph.
!!! warning "`config_schema` Deprecated"
The `config_schema` parameter is deprecated in v0.6.0 and support will be removed in v2.0.0.
@@ -178,7 +177,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
managed: dict[str, ManagedValueSpec]
schemas: dict[type[Any], dict[str, BaseChannel | ManagedValueSpec]]
waiting_edges: set[tuple[tuple[str, ...], str]]
name: str = "LangGraph"
compiled: bool
state_schema: type[StateT]
context_schema: type[ContextT] | None
@@ -875,7 +874,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
debug=debug,
store=store,
cache=cache,
name=name or self.name,
name=name or "LangGraph",
)
compiled.attach_node(START, None)
@@ -1391,6 +1390,14 @@ def _is_field_managed_value(name: str, typ: type[Any]) -> ManagedValueSpec | Non
if is_managed_value(decoration):
return decoration
# Handle Required, NotRequired, etc wrapped types by extracting the inner type
if (
get_origin(typ) is not None
and (args := get_args(typ))
and (inner_type := args[0])
):
return _is_field_managed_value(name, inner_type)
return None
+1 -1
View File
@@ -8,7 +8,7 @@ from typing import (
from typing_extensions import TypeGuard
from langgraph.pregel._scratchpad import PregelScratchpad
from langgraph._internal._scratchpad import PregelScratchpad
V = TypeVar("V")
U = TypeVar("U")
@@ -1,7 +1,7 @@
from typing import Annotated
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph.managed.base import ManagedValue
from langgraph.pregel._scratchpad import PregelScratchpad
__all__ = ("IsLastStep", "RemainingStepsManager")
+1 -1
View File
@@ -53,6 +53,7 @@ from langgraph._internal._constants import (
RETURN,
TASKS,
)
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph._internal._typing import EMPTY_SEQ, MISSING
from langgraph.channels.base import BaseChannel
from langgraph.channels.topic import Topic
@@ -69,7 +70,6 @@ from langgraph.pregel._call import get_runnable_for_task, identifier
from langgraph.pregel._io import read_channels
from langgraph.pregel._log import logger
from langgraph.pregel._read import INPUT_CACHE_KEY_TYPE, PregelNode
from langgraph.pregel._scratchpad import PregelScratchpad
from langgraph.runtime import DEFAULT_RUNTIME, Runtime
from langgraph.store.base import BaseStore
from langgraph.types import (
+1 -1
View File
@@ -48,6 +48,7 @@ from langgraph._internal._constants import (
PUSH,
RESUME,
)
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph._internal._typing import EMPTY_SEQ, MISSING
from langgraph.cache.base import BaseCache
from langgraph.channels.base import BaseChannel
@@ -100,7 +101,6 @@ from langgraph.pregel._io import (
read_channels,
)
from langgraph.pregel._read import PregelNode
from langgraph.pregel._scratchpad import PregelScratchpad
from langgraph.pregel._utils import get_new_channel_versions, is_xxh3_128_hexdigest
from langgraph.pregel.debug import (
map_debug_checkpoint,
+1 -1
View File
@@ -30,13 +30,13 @@ from langgraph._internal._constants import (
RETURN,
)
from langgraph._internal._future import chain_future, run_coroutine_threadsafe
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph._internal._typing import MISSING
from langgraph.constants import TAG_HIDDEN
from langgraph.errors import GraphBubbleUp, GraphInterrupt
from langgraph.pregel._algo import Call
from langgraph.pregel._executor import Submit
from langgraph.pregel._retry import arun_with_retry, run_with_retry
from langgraph.pregel._scratchpad import PregelScratchpad
from langgraph.types import (
CachePolicy,
PregelExecutableTask,
+44 -41
View File
@@ -11,7 +11,7 @@ from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
from dataclasses import is_dataclass
from functools import partial
from inspect import isclass
from typing import Any, Callable, Generic, Optional, Union, cast, get_type_hints
from typing import Any, Callable, Generic, Union, cast, get_type_hints
from uuid import UUID, uuid5
from langchain_core.globals import get_debug
@@ -637,8 +637,7 @@ class Pregel(
**deprecated_kwargs: Unpack[DeprecatedKwargs],
) -> None:
if (
config_type := deprecated_kwargs.get("config_type"),
MISSING,
config_type := deprecated_kwargs.get("config_type", MISSING)
) is not MISSING:
warnings.warn(
"`config_type` is deprecated and will be removed. Please use `context_schema` instead.",
@@ -785,7 +784,8 @@ class Pregel(
return self
@deprecated(
"`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead."
"`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead.",
category=None,
)
def config_schema(self, *, include: Sequence[str] | None = None) -> type[BaseModel]:
warnings.warn(
@@ -810,7 +810,8 @@ class Pregel(
return create_model(self.get_name("Config"), field_definitions=fields)
@deprecated(
"`get_config_jsonschema` is deprecated. Use `get_context_jsonschema` instead."
"`get_config_jsonschema` is deprecated. Use `get_context_jsonschema` instead.",
category=None,
)
def get_config_jsonschema(
self, *, include: Sequence[str] | None = None
@@ -2351,7 +2352,6 @@ class Pregel(
interrupt_before: All | Sequence[str] | None,
interrupt_after: All | Sequence[str] | None,
durability: Durability | None = None,
checkpoint_during: bool | None = None,
) -> tuple[
set[StreamMode],
str | Sequence[str],
@@ -2399,15 +2399,6 @@ class Pregel(
cache: BaseCache | None = config[CONF][CONFIG_KEY_CACHE]
else:
cache = self.cache
if checkpoint_during is not None:
if durability is not None:
raise ValueError(
"Cannot use both `checkpoint_during` and `durability` parameters."
)
elif checkpoint_during:
durability = "async"
else:
durability = "exit"
if durability is None:
durability = config.get(CONF, {}).get(CONFIG_KEY_DURABILITY, "async")
return (
@@ -2480,6 +2471,17 @@ class Pregel(
Yields:
The output of each step in the graph. The output shape depends on the stream_mode.
"""
if (checkpoint_during := kwargs.get("checkpoint_during")) is not None:
warnings.warn(
"`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
category=LangGraphDeprecatedSinceV10,
stacklevel=2,
)
if durability is not None:
raise ValueError(
"Cannot use both `checkpoint_during` and `durability` parameters. Please use `durability` instead."
)
durability = "async" if checkpoint_during else "exit"
if stream_mode is None:
# if being called as a node in another graph, default to values mode
@@ -2503,14 +2505,6 @@ class Pregel(
run_id=config.get("run_id"),
)
try:
deprecated_checkpoint_during = cast(
Optional[bool], kwargs.get("checkpoint_during")
)
if deprecated_checkpoint_during is not None:
warnings.warn(
"`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
category=LangGraphDeprecatedSinceV10,
)
# assign defaults
(
stream_modes,
@@ -2529,11 +2523,8 @@ class Pregel(
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
durability=durability,
checkpoint_during=deprecated_checkpoint_during,
)
if checkpointer is None and (
durability is not None or deprecated_checkpoint_during is not None
):
if checkpointer is None and durability is not None:
warnings.warn(
"`durability` has no effect when no checkpointer is present.",
)
@@ -2570,7 +2561,7 @@ class Pregel(
pass
# set durability mode for subgraphs
if durability is not None or deprecated_checkpoint_during is not None:
if durability is not None:
config[CONF][CONFIG_KEY_DURABILITY] = durability_
runtime = Runtime(
@@ -2741,6 +2732,17 @@ class Pregel(
Yields:
The output of each step in the graph. The output shape depends on the stream_mode.
"""
if (checkpoint_during := kwargs.get("checkpoint_during")) is not None:
warnings.warn(
"`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
category=LangGraphDeprecatedSinceV10,
stacklevel=2,
)
if durability is not None:
raise ValueError(
"Cannot use both `checkpoint_during` and `durability` parameters. Please use `durability` instead."
)
durability = "async" if checkpoint_during else "exit"
if stream_mode is None:
# if being called as a node in another graph, default to values mode
@@ -2783,14 +2785,6 @@ class Pregel(
else False
)
try:
deprecated_checkpoint_during = cast(
Optional[bool], kwargs.get("checkpoint_during")
)
if deprecated_checkpoint_during is not None:
warnings.warn(
"`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
category=LangGraphDeprecatedSinceV10,
)
# assign defaults
(
stream_modes,
@@ -2809,11 +2803,8 @@ class Pregel(
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
durability=durability,
checkpoint_during=deprecated_checkpoint_during,
)
if checkpointer is None and (
durability is not None or deprecated_checkpoint_during is not None
):
if checkpointer is None and durability is not None:
warnings.warn(
"`durability` has no effect when no checkpointer is present.",
)
@@ -2865,7 +2856,7 @@ class Pregel(
pass
# set durability mode for subgraphs
if durability is not None or deprecated_checkpoint_during is not None:
if durability is not None:
config[CONF][CONFIG_KEY_DURABILITY] = durability_
runtime = Runtime(
@@ -2990,6 +2981,7 @@ class Pregel(
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
durability: Durability | None = None,
**kwargs: Any,
) -> dict[str, Any] | Any:
"""Run the graph with a single input and config.
@@ -3004,6 +2996,10 @@ class Pregel(
output_keys: Optional. The output keys to retrieve from the graph run.
interrupt_before: Optional. The nodes to interrupt the graph run before.
interrupt_after: Optional. The nodes to interrupt the graph run after.
durability: The durability mode for the graph execution, defaults to "async". Options are:
- `"sync"`: Changes are persisted synchronously before the next step starts.
- `"async"`: Changes are persisted asynchronously while the next step executes.
- `"exit"`: Changes are persisted only when the graph exits.
**kwargs: Additional keyword arguments to pass to the graph run.
Returns:
@@ -3027,6 +3023,7 @@ class Pregel(
output_keys=output_keys,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
durability=durability,
**kwargs,
):
if stream_mode == "values":
@@ -3069,6 +3066,7 @@ class Pregel(
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
durability: Durability | None = None,
**kwargs: Any,
) -> dict[str, Any] | Any:
"""Asynchronously invoke the graph on a single input.
@@ -3083,6 +3081,10 @@ class Pregel(
output_keys: Optional. The output keys to include in the result. Default is None.
interrupt_before: Optional. The nodes to interrupt before. Default is None.
interrupt_after: Optional. The nodes to interrupt after. Default is None.
durability: The durability mode for the graph execution, defaults to "async". Options are:
- `"sync"`: Changes are persisted synchronously before the next step starts.
- `"async"`: Changes are persisted asynchronously while the next step executes.
- `"exit"`: Changes are persisted only when the graph exits.
**kwargs: Additional keyword arguments.
Returns:
@@ -3107,6 +3109,7 @@ class Pregel(
output_keys=output_keys,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
durability=durability,
**kwargs,
):
if stream_mode == "values":
+1 -4
View File
@@ -191,10 +191,7 @@ class Interrupt:
return cls(value=value, id=xxh3_128_hexdigest(ns.encode()))
@property
@deprecated(
"`interrupt_id` is deprecated. Use `id` instead.",
stacklevel=2,
)
@deprecated("`interrupt_id` is deprecated. Use `id` instead.", category=None)
def interrupt_id(self) -> str:
warn(
"`interrupt_id` is deprecated. Use `id` instead.",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "0.6.2"
version = "0.6.3"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
@@ -175,10 +175,10 @@
'''
# ---
# name: test_prebuilt_tool_chat
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"additionalProperties": true, "title": "Additional Kwargs", "type": "object"}, "response_metadata": {"additionalProperties": true, "title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "description": "The state of the agent.", "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "is_last_step": {"title": "Is Last Step", "type": "boolean"}, "remaining_steps": {"title": "Remaining Steps", "type": "integer"}}, "required": ["messages", "is_last_step", "remaining_steps"], "title": "AgentState", "type": "object"}'
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"additionalProperties": true, "title": "Additional Kwargs", "type": "object"}, "response_metadata": {"additionalProperties": true, "title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "description": "The state of the agent.", "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "remaining_steps": {"title": "Remaining Steps", "type": "integer"}}, "required": ["messages"], "title": "AgentState", "type": "object"}'
# ---
# name: test_prebuilt_tool_chat.1
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"additionalProperties": true, "title": "Additional Kwargs", "type": "object"}, "response_metadata": {"additionalProperties": true, "title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "description": "The state of the agent.", "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "is_last_step": {"title": "Is Last Step", "type": "boolean"}, "remaining_steps": {"title": "Remaining Steps", "type": "integer"}}, "required": ["messages", "is_last_step", "remaining_steps"], "title": "AgentState", "type": "object"}'
'{"$defs": {"BaseMessage": {"additionalProperties": true, "description": "Base abstract message class.\\n\\nMessages are the inputs and outputs of ChatModels.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"additionalProperties": true, "title": "Additional Kwargs", "type": "object"}, "response_metadata": {"additionalProperties": true, "title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "type"], "title": "BaseMessage", "type": "object"}}, "description": "The state of the agent.", "properties": {"messages": {"items": {"$ref": "#/$defs/BaseMessage"}, "title": "Messages", "type": "array"}, "remaining_steps": {"title": "Remaining Steps", "type": "integer"}}, "required": ["messages"], "title": "AgentState", "type": "object"}'
# ---
# name: test_prebuilt_tool_chat.2
'''
+153 -6
View File
@@ -1,6 +1,12 @@
from __future__ import annotations
import warnings
from typing import Any, Optional
import pytest
from langchain_core.runnables import RunnableConfig
from pytest_mock import MockerFixture
from typing_extensions import TypedDict
from typing_extensions import NotRequired, TypedDict
from langgraph.channels.last_value import LastValue
from langgraph.errors import NodeInterrupt
@@ -94,8 +100,6 @@ def test_pregel_types_deprecation() -> None:
from langgraph.pregel.types import StateSnapshot # noqa: F401
@pytest.mark.filterwarnings("ignore:`config_schema` is deprecated")
@pytest.mark.filterwarnings("ignore:`get_config_jsonschema` is deprecated")
def test_config_schema_deprecation() -> None:
with pytest.warns(
LangGraphDeprecatedSinceV10,
@@ -121,7 +125,6 @@ def test_config_schema_deprecation() -> None:
graph.get_config_jsonschema()
@pytest.mark.filterwarnings("ignore:`config_schema` is deprecated")
def test_config_schema_deprecation_on_entrypoint() -> None:
with pytest.warns(
LangGraphDeprecatedSinceV10,
@@ -132,10 +135,15 @@ def test_config_schema_deprecation_on_entrypoint() -> None:
def my_entrypoint(state: PlainState) -> PlainState:
return state
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead.",
):
assert my_entrypoint.context_schema == PlainState
assert my_entrypoint.config_schema() is not None
@pytest.mark.filterwarnings("ignore:`config_type` is deprecated")
def test_config_type_deprecation_pregel(mocker: MockerFixture) -> None:
add_one = mocker.Mock(side_effect=lambda x: x + 1)
chain = NodeBuilder().subscribe_only("input").do(add_one).write_to("output")
@@ -159,7 +167,6 @@ def test_config_type_deprecation_pregel(mocker: MockerFixture) -> None:
assert instance.context_schema == PlainState
@pytest.mark.filterwarnings("ignore:`interrupt_id` is deprecated. Use `id` instead.")
def test_interrupt_attributes_deprecation() -> None:
interrupt = Interrupt(value="question", id="abc")
@@ -170,7 +177,6 @@ def test_interrupt_attributes_deprecation() -> None:
interrupt.interrupt_id
@pytest.mark.filterwarnings("ignore:NodeInterrupt is deprecated.")
def test_node_interrupt_deprecation() -> None:
with pytest.warns(
LangGraphDeprecatedSinceV10,
@@ -185,3 +191,144 @@ def test_deprecated_import() -> None:
match="Importing PREVIOUS from langgraph.constants is deprecated. This constant is now private and should not be used directly.",
):
from langgraph.constants import PREVIOUS # noqa: F401
@pytest.mark.filterwarnings(
"ignore:`durability` has no effect when no checkpointer is present"
)
def test_checkpoint_during_deprecation_state_graph() -> None:
class CheckDurability(TypedDict):
durability: NotRequired[str]
def plain_node(state: CheckDurability, config: RunnableConfig) -> CheckDurability:
return {"durability": config["configurable"]["__pregel_durability"]}
builder = StateGraph(CheckDurability)
builder.add_node("plain_node", plain_node)
builder.set_entry_point("plain_node")
graph = builder.compile()
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
):
result = graph.invoke({}, checkpoint_during=True)
assert result["durability"] == "async"
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
):
result = graph.invoke({}, checkpoint_during=False)
assert result["durability"] == "exit"
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
):
for chunk in graph.stream({}, checkpoint_during=True): # type: ignore[arg-type]
assert chunk["plain_node"]["durability"] == "async"
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`checkpoint_during` is deprecated and will be removed. Please use `durability` instead.",
):
for chunk in graph.stream({}, checkpoint_during=False): # type: ignore[arg-type]
assert chunk["plain_node"]["durability"] == "exit"
def test_config_parameter_incorrect_typing() -> None:
"""Test that a warning is raised when config parameter is typed incorrectly."""
builder = StateGraph(PlainState)
# Test sync function with config: dict
with pytest.warns(
UserWarning,
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*dict.*'. ",
):
def sync_node_with_dict_config(state: PlainState, config: dict) -> PlainState:
return state
builder.add_node(sync_node_with_dict_config)
# Test async function with config: dict
with pytest.warns(
UserWarning,
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*dict.*'. ",
):
async def async_node_with_dict_config(
state: PlainState, config: dict
) -> PlainState:
return state
builder.add_node(async_node_with_dict_config)
# Test with other incorrect types
with pytest.warns(
UserWarning,
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*Any.*'. ",
):
def sync_node_with_any_config(state: PlainState, config: Any) -> PlainState:
return state
builder.add_node(sync_node_with_any_config)
with pytest.warns(
UserWarning,
match="The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not '.*Any.*'. ",
):
async def async_node_with_any_config(
state: PlainState, config: Any
) -> PlainState:
return state
builder.add_node(async_node_with_any_config)
with warnings.catch_warnings(record=True) as w:
def node_with_correct_config(
state: PlainState, config: RunnableConfig
) -> PlainState:
return state
builder.add_node(node_with_correct_config)
def node_with_optional_config(
state: PlainState,
config: Optional[RunnableConfig], # noqa: UP045
) -> PlainState:
return state
builder.add_node(node_with_optional_config)
def node_with_untyped_config(state: PlainState, config) -> PlainState:
return state
builder.add_node(node_with_untyped_config)
async def async_node_with_correct_config(
state: PlainState, config: RunnableConfig
) -> PlainState:
return state
builder.add_node(async_node_with_correct_config)
async def async_node_with_optional_config(
state: PlainState,
config: Optional[RunnableConfig], # noqa: UP045
) -> PlainState:
return state
builder.add_node(async_node_with_optional_config)
async def async_node_with_untyped_config(
state: PlainState, config
) -> PlainState:
return state
builder.add_node(async_node_with_untyped_config)
assert len(w) == 0
@@ -0,0 +1,27 @@
from typing_extensions import NotRequired, Required, TypedDict
from langgraph.graph import StateGraph
from langgraph.managed import RemainingSteps
class StatePlain(TypedDict):
remaining_steps: RemainingSteps
class StateNotRequired(TypedDict):
remaining_steps: NotRequired[RemainingSteps]
class StateRequired(TypedDict):
remaining_steps: Required[RemainingSteps]
def test_managed_values_recognized() -> None:
graph = StateGraph(StatePlain)
assert "remaining_steps" in graph.managed
graph = StateGraph(StateNotRequired)
assert "remaining_steps" in graph.managed
graph = StateGraph(StateRequired)
assert "remaining_steps" in graph.managed
+2 -2
View File
@@ -1192,7 +1192,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.6.2"
version = "0.6.3"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1433,7 +1433,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.2"
version = "0.6.3"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -34,7 +34,7 @@ from langchain_core.runnables import (
)
from langchain_core.tools import BaseTool
from pydantic import BaseModel
from typing_extensions import Annotated, TypedDict
from typing_extensions import Annotated, NotRequired, TypedDict
from langgraph._internal._runnable import RunnableCallable, RunnableLike
from langgraph._internal._typing import MISSING
@@ -42,7 +42,7 @@ from langgraph.errors import ErrorCode, create_error_message
from langgraph.graph import END, StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.state import CompiledStateGraph
from langgraph.managed import IsLastStep, RemainingSteps
from langgraph.managed import RemainingSteps
from langgraph.prebuilt._internal import ToolCallWithContext
from langgraph.prebuilt.tool_node import ToolNode
from langgraph.runtime import Runtime
@@ -65,9 +65,7 @@ class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
is_last_step: IsLastStep
remaining_steps: RemainingSteps
remaining_steps: NotRequired[RemainingSteps]
class AgentStatePydantic(BaseModel):
@@ -571,16 +569,13 @@ def create_react_agent(
else False
)
remaining_steps = _get_state_value(state, "remaining_steps", None)
is_last_step = _get_state_value(state, "is_last_step", False)
return (
(remaining_steps is None and is_last_step and has_tool_calls)
or (
remaining_steps is not None
and remaining_steps < 1
and all_tools_return_direct
)
or (remaining_steps is not None and remaining_steps < 2 and has_tool_calls)
)
if remaining_steps is not None:
if remaining_steps < 1 and all_tools_return_direct:
return True
elif remaining_steps < 2 and has_tool_calls:
return True
return False
def _get_model_input_state(state: StateSchema) -> StateSchema:
if pre_model_hook is not None:
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-prebuilt"
version = "0.6.2"
version = "0.6.3"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
+2 -2
View File
@@ -316,7 +316,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.6.2"
version = "0.6.3"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -460,7 +460,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.2"
version = "0.6.3"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },