mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 15:12:26 +02:00
release: prep for langgraph v0.6 (#5325)
This commit is contained in:
@@ -3,7 +3,7 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, v1]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
|
||||
+32
-22
@@ -12,56 +12,64 @@ LangGraph provides **three** primary ways to supply context:
|
||||
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Runtime Context**](#runtime-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**Short-term memory (State)**](#short-term-memory-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|
||||
## Provide runtime context
|
||||
### Runtime Context
|
||||
|
||||
### Config (static context)
|
||||
!!! note "`config['configurable']` -> `runtime.context`"
|
||||
|
||||
Config is for immutable data like user metadata or API keys. Use
|
||||
when you have values that don't change mid-run.
|
||||
In LangGraph < v1.0, static runtime context was passed via the `config['configurable']` key, paired with a `config_schema` argument
|
||||
to `StateGraph` or `Pregel`. This is now deprecated and will be removed in v2.0.
|
||||
|
||||
Specify configuration using a key called **"configurable"** which is reserved
|
||||
for this purpose:
|
||||
As of LangGraph v1.0, the Runtime object is recommended to access static context and runtime-specific information like the store and stream writer.
|
||||
|
||||
Runtime context is for immutable data like user metadata or API keys. Use this when you have values that don't change mid-run.
|
||||
|
||||
Specify static context via the `context` argument to `invoke` / `stream`, which is reserved for this purpose:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
user_name: str
|
||||
|
||||
graph.invoke( # (1)!
|
||||
{"messages": [{"role": "user", "content": "hi!"}]}, # (2)!
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}} # (3)!
|
||||
context={"user_name": "John Smith"} # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. This is the invocation of the agent or graph. The `invoke` method runs the underlying graph with the provided input.
|
||||
2. This example uses messages as an input, which is common, but your application may use different input structures.
|
||||
3. This is where you pass the configuration data. The `config` parameter allows you to provide additional context that the agent can use during its execution.
|
||||
3. This is where you pass the runtime data. The `context` parameter allows you to provide additional dependencies that the agent can use during its execution.
|
||||
|
||||
=== "Agent prompt"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]:
|
||||
user_name = config["configurable"].get("user_name")
|
||||
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
|
||||
def prompt(state: AgentState) -> list[AnyMessage]:
|
||||
runtime = get_runtime(ContextSchema)
|
||||
system_msg = f"You are a helpful assistant. Address the user as {runtime.context.user_name}."
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
prompt=prompt
|
||||
prompt=prompt,
|
||||
context_schema=ContextSchema
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
context={"user_name": "John Smith"}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -70,11 +78,11 @@ graph.invoke( # (1)!
|
||||
=== "Workflow node"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: RunnableConfig):
|
||||
user_name = config["configurable"].get("user_name")
|
||||
def node(state: State, config: Runtime[ContextSchema]):
|
||||
user_name = runtime.context.user_name
|
||||
...
|
||||
```
|
||||
|
||||
@@ -83,14 +91,16 @@ graph.invoke( # (1)!
|
||||
=== "In a tool"
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.runtime import get_runtime
|
||||
|
||||
@tool
|
||||
# highlight-next-line
|
||||
def get_user_info(config: RunnableConfig) -> str:
|
||||
def get_user_email() -> str:
|
||||
"""Retrieve user information based on user ID."""
|
||||
user_id = config["configurable"].get("user_id")
|
||||
return "User is John Smith" if user_id == "user_123" else "Unknown user"
|
||||
# simulate fetching user info from a database
|
||||
runtime = get_runtime(ContextSchema)
|
||||
email = get_user_email_from_db(runtime.context.user_name)
|
||||
return email
|
||||
```
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md#configuration) for details.
|
||||
|
||||
@@ -108,11 +108,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -121,11 +121,11 @@ from langgraph.graph import StateGraph, END, START
|
||||
from my_agent.utils.nodes import call_model, should_continue, tool_node # import nodes
|
||||
from my_agent.utils.state import AgentState # import state
|
||||
|
||||
# Define the config
|
||||
class GraphConfig(TypedDict):
|
||||
# Define the runtime context
|
||||
class GraphContext(TypedDict):
|
||||
model_name: Literal["anthropic", "openai"]
|
||||
|
||||
workflow = StateGraph(AgentState, config_schema=GraphConfig)
|
||||
workflow = StateGraph(AgentState, context_schema=GraphContext)
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", tool_node)
|
||||
workflow.add_edge(START, "agent")
|
||||
|
||||
@@ -30,9 +30,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
@@ -203,9 +201,7 @@ To review, edit, and approve tool calls in an agent or workflow, use LangGraph's
|
||||
# > [
|
||||
# > {
|
||||
# > 'value': {'text_to_revise': 'original text'},
|
||||
# > 'resumable': True,
|
||||
# > 'ns': ['human_node:fc722478-2f21-0578-c572-d9fc4dd07c3b'],
|
||||
# > 'when': 'during'
|
||||
# > 'id': '...',
|
||||
# > }
|
||||
# > ]
|
||||
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
|
||||
In this guide we will show how to create, configure, and manage an [assistant](../../concepts/assistants.md).
|
||||
|
||||
First, as a brief refresher on the concept of configurations, consider the following simple `call_model` node and configuration schema. Observe that this node tries to read and use the `model_name` as defined by the `config` object's `configurable`.
|
||||
First, as a brief refresher on the concept of runtime context, consider the following simple `call_model` node and context schema. Observe that this node tries to read and use the `model_provider` as defined by the `Runtime` object's `context` property.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "anthropic"
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model_name: str
|
||||
builder = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
builder = StateGraph(AgentState, config_schema=ConfigSchema)
|
||||
|
||||
def call_model(state, config):
|
||||
def call_model(state, runtime: Runtime[ContextSchema]):
|
||||
messages = state["messages"]
|
||||
model_name = config.get('configurable', {}).get("model_name", "anthropic")
|
||||
model = _get_model(model_name)
|
||||
model = _get_model(runtime.context.llm_provider)
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
@@ -44,7 +43,7 @@ First, as a brief refresher on the concept of configurations, consider the follo
|
||||
}
|
||||
```
|
||||
|
||||
For more information on configurations, [see here](../../concepts/low_level.md#configuration).
|
||||
For more information on runtime context, [see here](../../concepts/low_level.md#runtime-context).
|
||||
|
||||
## Create an assistant
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Assistants
|
||||
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through configuration variations rather than structural changes.
|
||||
**Assistants** allow you to manage configurations (like prompts, LLM selection, tools) separately from your graph's core logic, enabling rapid changes that don't alter the graph architecture. It is a way to create multiple specialized versions of the same graph architecture, each optimized for different use cases through context/configuration variations rather than structural changes.
|
||||
|
||||
For example, imagine a general-purpose writing agent built on a common graph architecture. While the structure remains the same, different writing styles—such as blog posts and tweets—require tailored configurations to optimize performance. To support these variations, you can create multiple assistants (e.g., one for blogs and another for tweets) that share the underlying graph but differ in model selection and system prompt.
|
||||
|
||||
@@ -14,8 +14,8 @@ The LangGraph Cloud API provides several endpoints for creating and managing ass
|
||||
|
||||
## Configuration
|
||||
|
||||
Assistants build on the LangGraph open source concept of [configuration](low_level.md#configuration).
|
||||
While configuration is available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings.
|
||||
Assistants build on the LangGraph open source concepts of configuration and [runtime context](low_level.md#runtime-context).
|
||||
While these features are available in the open source LangGraph library, assistants are only present in [LangGraph Platform](langgraph_platform.md). This is due to the fact that assistants are tightly coupled to your deployed graph. Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default context and configuration settings.
|
||||
|
||||
In practice, an assistant is just an _instance_ of a graph with a specific configuration. Therefore, multiple assistants can reference the same graph but can contain different configurations (e.g. prompts, models, tools). The LangGraph Server API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants.
|
||||
|
||||
@@ -26,6 +26,6 @@ Once you've created an assistant, subsequent edits to that assistant will create
|
||||
|
||||
## Execution
|
||||
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
A **run** is an invocation of an assistant. Each run may have its own input, configuration, context, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](./persistence.md#threads).
|
||||
|
||||
The LangGraph Platform API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details.
|
||||
|
||||
@@ -79,51 +79,54 @@ def workflow(topic: str) -> dict:
|
||||
```python
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
time.sleep(1) # This is a placeholder for a long-running task.
|
||||
return f"An essay about topic: {topic}"
|
||||
|
||||
@entrypoint(checkpointer=InMemorySaver())
|
||||
def workflow(topic: str) -> dict:
|
||||
"""A simple workflow that writes an essay and asks for a review."""
|
||||
essay = write_essay("cat").result()
|
||||
is_approved = interrupt({
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
})
|
||||
|
||||
is_approved = interrupt(
|
||||
{
|
||||
# Any json-serializable payload provided to interrupt as argument.
|
||||
# It will be surfaced on the client side as an Interrupt when streaming data
|
||||
# from the workflow.
|
||||
"essay": essay, # The essay we want reviewed.
|
||||
# We can add any additional information that we need.
|
||||
# For example, introduce a key called "action" with some instructions.
|
||||
"action": "Please approve/reject the essay",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
"essay": essay, # The essay that was generated
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
}
|
||||
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": thread_id
|
||||
}
|
||||
}
|
||||
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
for item in workflow.stream("cat", config):
|
||||
print(item)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'write_essay': 'An essay about topic: cat'}
|
||||
{'__interrupt__': (Interrupt(value={'essay': 'An essay about topic: cat', 'action': 'Please approve/reject the essay'}, resumable=True, ns=['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'], when='during'),)}
|
||||
# > {'write_essay': 'An essay about topic: cat'}
|
||||
# > {
|
||||
# > '__interrupt__': (
|
||||
# > Interrupt(
|
||||
# > value={
|
||||
# > 'essay': 'An essay about topic: cat',
|
||||
# > 'action': 'Please approve/reject the essay'
|
||||
# > },
|
||||
# > id='b9b2b9d788f482663ced6dc755c9e981'
|
||||
# > ),
|
||||
# > )
|
||||
# > }
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
|
||||
@@ -192,35 +192,48 @@ class State(MessagesState):
|
||||
|
||||
## Nodes
|
||||
|
||||
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
|
||||
In LangGraph, nodes are Python functions (either synchronous or asynchronous) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
3. `runtime`: A `Runtime` object that contains [runtime `context`](#runtime-context) and other information like `store` and `stream_writer`
|
||||
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
class State(TypedDict):
|
||||
input: str
|
||||
results: str
|
||||
|
||||
@dataclass
|
||||
class Context:
|
||||
user_id: str
|
||||
|
||||
builder = StateGraph(State)
|
||||
|
||||
def plain_node(state: State):
|
||||
return state
|
||||
|
||||
def my_node(state: State, config: RunnableConfig):
|
||||
print("In node: ", config["configurable"]["user_id"])
|
||||
def node_with_runtime(state: State, runtime: Runtime[Context]):
|
||||
print("In node: ", runtime.context.user_id)
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
def node_with_config(state: State, config: RunnableConfig):
|
||||
print("In node with thread_id: ", config["configurable"]["thread_id"])
|
||||
return {"results": f"Hello, {state['input']}!"}
|
||||
|
||||
|
||||
# The second argument is optional
|
||||
def my_other_node(state: State):
|
||||
return state
|
||||
|
||||
|
||||
builder.add_node("my_node", my_node)
|
||||
builder.add_node("other_node", my_other_node)
|
||||
builder.add_node("plain_node", plain_node)
|
||||
builder.add_node("node_with_runtime", node_with_runtime)
|
||||
builder.add_node("node_with_config", node_with_config)
|
||||
...
|
||||
```
|
||||
|
||||
@@ -459,33 +472,32 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
|
||||
- State keys that are renamed lose their saved state in existing threads
|
||||
- State keys whose types change in incompatible ways could currently cause issues in threads with state from before the change -- if this is a blocker please reach out and we can prioritize a solution.
|
||||
|
||||
## Configuration
|
||||
## Runtime Context
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
You can optionally specify a `config_schema` when creating a graph.
|
||||
When creating a graph, you can specify a `context_schema` for runtime context passed to nodes. This is useful for passing
|
||||
information to nodes that is not part of the graph state. For example, you might want to pass dependencies such as model name or a database connection.
|
||||
|
||||
```python
|
||||
class ConfigSchema(TypedDict):
|
||||
llm: str
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
llm_provider: str = "openai"
|
||||
|
||||
graph = StateGraph(State, config_schema=ConfigSchema)
|
||||
graph = StateGraph(State, context_schema=ContextSchema)
|
||||
```
|
||||
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
You can then pass this context into the graph using the `context` parameter of the `invoke` method.
|
||||
|
||||
```python
|
||||
config = {"configurable": {"llm": "anthropic"}}
|
||||
|
||||
graph.invoke(inputs, config=config)
|
||||
graph.invoke(inputs, context={"llm_provider": "anthropic"})
|
||||
```
|
||||
|
||||
You can then access and use this configuration inside a node or conditional edge:
|
||||
You can then access and use this context inside a node or conditional edge:
|
||||
|
||||
```python
|
||||
def node_a(state, config):
|
||||
llm_type = config.get("configurable", {}).get("llm", "openai")
|
||||
llm = get_llm(llm_type)
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
def node_a(state: State, runtime: Runtime[ContextSchema]):
|
||||
llm = get_llm(runtime.context.llm_provider)
|
||||
...
|
||||
```
|
||||
|
||||
@@ -496,7 +508,7 @@ See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full b
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})
|
||||
graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"})
|
||||
```
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
|
||||
@@ -514,12 +514,12 @@ To add runtime configuration:
|
||||
See below for a simple example:
|
||||
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# 1. Specify config schema
|
||||
class ConfigSchema(TypedDict):
|
||||
class ContextSchema(TypedDict):
|
||||
my_runtime_value: str
|
||||
|
||||
# 2. Define a graph that accesses the config in a node
|
||||
@@ -527,18 +527,18 @@ class State(TypedDict):
|
||||
my_state_value: str
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: RunnableConfig):
|
||||
def node(state: State, runtime: Runtime[ContextSchema]):
|
||||
# highlight-next-line
|
||||
if config["configurable"]["my_runtime_value"] == "a":
|
||||
if runtime.context["my_runtime_value"] == "a":
|
||||
return {"my_state_value": 1}
|
||||
# highlight-next-line
|
||||
elif config["configurable"]["my_runtime_value"] == "b":
|
||||
elif runtime.context["my_runtime_value"] == "b":
|
||||
return {"my_state_value": 2}
|
||||
else:
|
||||
raise ValueError("Unknown values.")
|
||||
|
||||
# highlight-next-line
|
||||
builder = StateGraph(State, config_schema=ConfigSchema)
|
||||
builder = StateGraph(State, context_schema=ContextSchema)
|
||||
builder.add_node(node)
|
||||
builder.add_edge(START, "node")
|
||||
builder.add_edge("node", END)
|
||||
@@ -547,9 +547,9 @@ graph = builder.compile()
|
||||
|
||||
# 3. Pass in configuration at runtime:
|
||||
# highlight-next-line
|
||||
print(graph.invoke({}, {"configurable": {"my_runtime_value": "a"}}))
|
||||
print(graph.invoke({}, context={"my_runtime_value": "a"}))
|
||||
# highlight-next-line
|
||||
print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
print(graph.invoke({}, context={"my_runtime_value": "b"}))
|
||||
```
|
||||
```
|
||||
{'my_state_value': 1}
|
||||
@@ -560,27 +560,28 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
Below we demonstrate a practical example in which we configure what LLM to use at runtime. We will use both OpenAI and Anthropic models.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import MessagesState
|
||||
from langgraph.graph import END, StateGraph, START
|
||||
from langgraph.graph import MessagesState, END, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model: str
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
model_provider: str = "anthropic"
|
||||
|
||||
MODELS = {
|
||||
"anthropic": init_chat_model("anthropic:claude-3-5-haiku-latest"),
|
||||
"openai": init_chat_model("openai:gpt-4.1-mini"),
|
||||
}
|
||||
|
||||
def call_model(state: MessagesState, config: RunnableConfig):
|
||||
model = config["configurable"].get("model", "anthropic")
|
||||
model = MODELS[model]
|
||||
def call_model(state: MessagesState, runtime: Runtime[ContextSchema]):
|
||||
model = MODELS[runtime.context.model_provider]
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": [response]}
|
||||
|
||||
builder = StateGraph(MessagesState, config_schema=ConfigSchema)
|
||||
builder = StateGraph(MessagesState, context_schema=ContextSchema)
|
||||
builder.add_node("model", call_model)
|
||||
builder.add_edge(START, "model")
|
||||
builder.add_edge("model", END)
|
||||
@@ -592,8 +593,7 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
# With no configuration, uses default (Anthropic)
|
||||
response_1 = graph.invoke({"messages": [input_message]})["messages"][-1]
|
||||
# Or, can set OpenAI
|
||||
config = {"configurable": {"model": "openai"}}
|
||||
response_2 = graph.invoke({"messages": [input_message]}, config=config)["messages"][-1]
|
||||
response_2 = graph.invoke({"messages": [input_message]}, context={"model_provider": "openai"})["messages"][-1]
|
||||
|
||||
print(response_1.response_metadata["model_name"])
|
||||
print(response_2.response_metadata["model_name"])
|
||||
@@ -607,32 +607,33 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
Below we demonstrate a practical example in which we configure two parameters: the LLM and system message to use at runtime.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.messages import SystemMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import END, MessagesState, StateGraph, START
|
||||
from langgraph.runtime import Runtime
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
model: Optional[str]
|
||||
system_message: Optional[str]
|
||||
@dataclass
|
||||
class ContextSchema:
|
||||
model_provider: str = "anthropic"
|
||||
system_message: str | None = None
|
||||
|
||||
MODELS = {
|
||||
"anthropic": init_chat_model("anthropic:claude-3-5-haiku-latest"),
|
||||
"openai": init_chat_model("openai:gpt-4.1-mini"),
|
||||
}
|
||||
|
||||
def call_model(state: MessagesState, config: RunnableConfig):
|
||||
model = config["configurable"].get("model", "anthropic")
|
||||
model = MODELS[model]
|
||||
def call_model(state: MessagesState, runtime: Runtime[ContextSchema]):
|
||||
model = MODELS[runtime.context.model_provider]
|
||||
messages = state["messages"]
|
||||
if system_message := config["configurable"].get("system_message"):
|
||||
if (system_message := runtime.context.system_message):
|
||||
messages = [SystemMessage(system_message)] + messages
|
||||
response = model.invoke(messages)
|
||||
return {"messages": [response]}
|
||||
|
||||
builder = StateGraph(MessagesState, config_schema=ConfigSchema)
|
||||
builder = StateGraph(MessagesState, context_schema=ContextSchema)
|
||||
builder.add_node("model", call_model)
|
||||
builder.add_edge(START, "model")
|
||||
builder.add_edge("model", END)
|
||||
@@ -641,8 +642,7 @@ print(graph.invoke({}, {"configurable": {"my_runtime_value": "b"}}))
|
||||
|
||||
# Usage
|
||||
input_message = {"role": "user", "content": "hi"}
|
||||
config = {"configurable": {"model": "openai", "system_message": "Respond in Italian."}}
|
||||
response = graph.invoke({"messages": [input_message]}, config)
|
||||
response = graph.invoke({"messages": [input_message]}, context={"model_provider": "openai", "system_message": "Respond in Italian."})
|
||||
for message in response["messages"]:
|
||||
message.pretty_print()
|
||||
```
|
||||
|
||||
@@ -54,13 +54,7 @@ graph = graph_builder.compile(checkpointer=checkpointer) # (4)!
|
||||
config = {"configurable": {"thread_id": "some_id"}}
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
print(result['__interrupt__']) # (6)!
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={'text_to_revise': 'original text'},
|
||||
# > resumable=True,
|
||||
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
|
||||
# > )
|
||||
# > ]
|
||||
# > [Interrupt(value={'text_to_revise': 'original text'}, id='a0d9dd40440ac7be2720dc5c20858627')]
|
||||
|
||||
# highlight-next-line
|
||||
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
@@ -80,25 +74,27 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
```python
|
||||
from typing import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
# highlight-next-line
|
||||
from langgraph.types import interrupt, Command
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
some_text: str
|
||||
|
||||
|
||||
def human_node(state: State):
|
||||
# highlight-next-line
|
||||
value = interrupt( # (1)!
|
||||
value = interrupt( # (1)!
|
||||
{
|
||||
"text_to_revise": state["some_text"] # (2)!
|
||||
"text_to_revise": state["some_text"] # (2)!
|
||||
}
|
||||
)
|
||||
return {
|
||||
"some_text": value # (3)!
|
||||
"some_text": value # (3)!
|
||||
}
|
||||
|
||||
|
||||
@@ -106,25 +102,15 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
graph_builder = StateGraph(State)
|
||||
graph_builder.add_node("human_node", human_node)
|
||||
graph_builder.add_edge(START, "human_node")
|
||||
|
||||
checkpointer = InMemorySaver() # (4)!
|
||||
|
||||
checkpointer = InMemorySaver() # (4)!
|
||||
graph = graph_builder.compile(checkpointer=checkpointer)
|
||||
|
||||
# Pass a thread ID to the graph to run it.
|
||||
config = {"configurable": {"thread_id": uuid.uuid4()}}
|
||||
|
||||
# Run the graph until the interrupt is hit.
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
result = graph.invoke({"some_text": "original text"}, config=config) # (5)!
|
||||
|
||||
print(result['__interrupt__']) # (6)!
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={'text_to_revise': 'original text'},
|
||||
# > resumable=True,
|
||||
# > ns=['human_node:6ce9e64f-edef-fe5d-f7dc-511fa9526960']
|
||||
# > )
|
||||
# > ]
|
||||
print(result["__interrupt__"]) # (6)!
|
||||
# > [Interrupt(value={'text_to_revise': 'original text'}, id='6d7c4048049254c83195429a3659661d')]
|
||||
|
||||
# highlight-next-line
|
||||
print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)!
|
||||
@@ -167,7 +153,7 @@ For example, once your graph has been interrupted (multiple times, theoretically
|
||||
|
||||
```python
|
||||
resume_map = {
|
||||
i.interrupt_id: f"human input for prompt {i.value}"
|
||||
i.id: f"human input for prompt {i.value}"
|
||||
for i in parent.get_state(thread_config).interrupts
|
||||
}
|
||||
|
||||
@@ -388,14 +374,15 @@ graph.invoke(
|
||||
# Output interrupt payload
|
||||
print(result["__interrupt__"])
|
||||
# Example output:
|
||||
# Interrupt(
|
||||
# value={
|
||||
# 'task': 'Please review and edit the generated summary if necessary.',
|
||||
# 'generated_summary': 'The cat sat on the mat and looked at the stars.'
|
||||
# },
|
||||
# resumable=True,
|
||||
# ...
|
||||
# )
|
||||
# > [
|
||||
# > Interrupt(
|
||||
# > value={
|
||||
# > 'task': 'Please review and edit the generated summary if necessary.',
|
||||
# > 'generated_summary': 'The cat sat on the mat and looked at the stars.'
|
||||
# > },
|
||||
# > id='...'
|
||||
# > )
|
||||
# > ]
|
||||
|
||||
# Resume the graph with human-edited input
|
||||
edited_summary = "The cat lay on the rug, gazing peacefully at the night sky."
|
||||
@@ -1032,7 +1019,7 @@ def node_in_parent_graph(state: State):
|
||||
Entered `parent_node` a total of 1 times
|
||||
Entered `node_in_subgraph` a total of 1 times
|
||||
Entered human_node in sub-graph a total of 1 times
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:4c3a0248-21f0-1287-eacf-3002bc304db4', 'human_node:2fe86d52-6f70-2a3f-6b2f-b1eededd6348'], when='during'),)}
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', id='...'),)}
|
||||
--- Resuming ---
|
||||
Entered `parent_node` a total of 2 times
|
||||
Entered human_node in sub-graph a total of 2 times
|
||||
@@ -1108,7 +1095,7 @@ To avoid issues, refrain from dynamically changing the node's structure between
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['human_node:3a007ef9-c30d-c357-1ec1-86a1a70d8fba'], when='during'),)}
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', id='...'),)}
|
||||
Name: N/A. Age: John
|
||||
{'human_node': {'age': 'John', 'name': 'N/A'}}
|
||||
```
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
options:
|
||||
members:
|
||||
- TAG_HIDDEN
|
||||
- TAG_NOSTREAM
|
||||
- START
|
||||
- END
|
||||
- END
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@@ -280,10 +280,10 @@
|
||||
"from typing import Optional, Dict, Any\n",
|
||||
"from typing_extensions import Annotated, TypedDict\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.runtime import Runtime\n",
|
||||
"\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from langgraph.constants import Send\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langgraph.types import Send\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def update_candidates(\n",
|
||||
@@ -307,22 +307,27 @@
|
||||
" depth: Annotated[int, operator.add]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Configuration(TypedDict, total=False):\n",
|
||||
"class Context(TypedDict, total=False):\n",
|
||||
" max_depth: int\n",
|
||||
" threshold: float\n",
|
||||
" k: int\n",
|
||||
" beam_size: int\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _ensure_configurable(config: RunnableConfig) -> Configuration:\n",
|
||||
"class EnsuredContext(TypedDict):\n",
|
||||
" max_depth: int\n",
|
||||
" threshold: float\n",
|
||||
" k: int\n",
|
||||
" beam_size: int\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _ensure_context(ctx: Context) -> EnsuredContext:\n",
|
||||
" \"\"\"Get params that configure the search algorithm.\"\"\"\n",
|
||||
" configurable = config.get(\"configurable\", {})\n",
|
||||
" return {\n",
|
||||
" **configurable,\n",
|
||||
" \"max_depth\": configurable.get(\"max_depth\", 10),\n",
|
||||
" \"threshold\": config.get(\"threshold\", 0.9),\n",
|
||||
" \"k\": configurable.get(\"k\", 5),\n",
|
||||
" \"beam_size\": configurable.get(\"beam_size\", 3),\n",
|
||||
" \"max_depth\": ctx.get(\"max_depth\", 10),\n",
|
||||
" \"threshold\": ctx.get(\"threshold\", 0.9),\n",
|
||||
" \"k\": ctx.get(\"k\", 5),\n",
|
||||
" \"beam_size\": ctx.get(\"beam_size\", 3),\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -330,9 +335,11 @@
|
||||
" seed: Optional[Candidate]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def expand(state: ExpansionState, *, config: RunnableConfig) -> Dict[str, List[str]]:\n",
|
||||
"def expand(\n",
|
||||
" state: ExpansionState, *, runtime: Runtime[Context]\n",
|
||||
") -> Dict[str, List[Candidate]]:\n",
|
||||
" \"\"\"Generate the next state.\"\"\"\n",
|
||||
" configurable = _ensure_configurable(config)\n",
|
||||
" ctx = _ensure_context(runtime.context)\n",
|
||||
" if not state.get(\"seed\"):\n",
|
||||
" candidate_str = \"\"\n",
|
||||
" else:\n",
|
||||
@@ -342,9 +349,8 @@
|
||||
" {\n",
|
||||
" \"problem\": state[\"problem\"],\n",
|
||||
" \"candidate\": candidate_str,\n",
|
||||
" \"k\": configurable[\"k\"],\n",
|
||||
" \"k\": ctx[\"k\"],\n",
|
||||
" },\n",
|
||||
" config=config,\n",
|
||||
" )\n",
|
||||
" except Exception:\n",
|
||||
" return {\"candidates\": []}\n",
|
||||
@@ -354,7 +360,7 @@
|
||||
" return {\"candidates\": new_candidates}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def score(state: ToTState) -> Dict[str, List[float]]:\n",
|
||||
"def score(state: ToTState) -> Dict[str, Any]:\n",
|
||||
" \"\"\"Evaluate the candidate generations.\"\"\"\n",
|
||||
" candidates = state[\"candidates\"]\n",
|
||||
" scored = []\n",
|
||||
@@ -363,11 +369,9 @@
|
||||
" return {\"scored_candidates\": scored, \"candidates\": \"clear\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def prune(\n",
|
||||
" state: ToTState, *, config: RunnableConfig\n",
|
||||
") -> Dict[str, List[Dict[str, Any]]]:\n",
|
||||
"def prune(state: ToTState, *, runtime: Runtime[Context]) -> Dict[str, Any]:\n",
|
||||
" scored_candidates = state[\"scored_candidates\"]\n",
|
||||
" beam_size = _ensure_configurable(config)[\"beam_size\"]\n",
|
||||
" beam_size = _ensure_context(runtime.context)[\"beam_size\"]\n",
|
||||
" organized = sorted(\n",
|
||||
" scored_candidates, key=lambda candidate: candidate[1], reverse=True\n",
|
||||
" )\n",
|
||||
@@ -383,11 +387,11 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"def should_terminate(\n",
|
||||
" state: ToTState, config: RunnableConfig\n",
|
||||
" state: ToTState, runtime: Runtime[Context]\n",
|
||||
") -> Union[Literal[\"__end__\"], Send]:\n",
|
||||
" configurable = _ensure_configurable(config)\n",
|
||||
" solved = state[\"candidates\"][0].score >= configurable[\"threshold\"]\n",
|
||||
" if solved or state[\"depth\"] >= configurable[\"max_depth\"]:\n",
|
||||
" ctx = _ensure_context(runtime.context)\n",
|
||||
" solved = state[\"candidates\"][0].score >= ctx[\"threshold\"]\n",
|
||||
" if solved or state[\"depth\"] >= ctx[\"max_depth\"]:\n",
|
||||
" return \"__end__\"\n",
|
||||
" return [\n",
|
||||
" Send(\"expand\", {**state, \"somevalseed\": candidate})\n",
|
||||
@@ -396,7 +400,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"# Create the graph\n",
|
||||
"builder = StateGraph(state_schema=ToTState, config_schema=Configuration)\n",
|
||||
"builder = StateGraph(state_schema=ToTState, context_schema=Context)\n",
|
||||
"\n",
|
||||
"# Add nodes\n",
|
||||
"builder.add_node(expand)\n",
|
||||
@@ -467,13 +471,11 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"config = {\n",
|
||||
" \"configurable\": {\n",
|
||||
" \"thread_id\": \"test_1\",\n",
|
||||
" \"depth\": 10,\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
"for step in graph.stream({\"problem\": puzzles[42]}, config):\n",
|
||||
"for step in graph.stream(\n",
|
||||
" {\"problem\": puzzles[42]},\n",
|
||||
" config={\"configurable\": {\"thread_id\": \"test_1\"}},\n",
|
||||
" context={\"depth\": 10},\n",
|
||||
"):\n",
|
||||
" print(step)"
|
||||
]
|
||||
},
|
||||
@@ -491,7 +493,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"final_state = graph.get_state(config)\n",
|
||||
"final_state = graph.get_state({\"configurable\": {\"thread_id\": \"test_1\"}})\n",
|
||||
"winning_solution = final_state.values[\"candidates\"][0]\n",
|
||||
"search_depth = final_state.values[\"depth\"]\n",
|
||||
"if winning_solution[1] == 1:\n",
|
||||
|
||||
Generated
+20
-20
@@ -15,16 +15,16 @@ name = "ag2"
|
||||
version = "0.9.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "asyncer" },
|
||||
{ name = "diskcache" },
|
||||
{ name = "docker" },
|
||||
{ name = "httpx" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "termcolor" },
|
||||
{ name = "tiktoken" },
|
||||
{ name = "anyio", marker = "python_full_version < '3.13'" },
|
||||
{ name = "asyncer", marker = "python_full_version < '3.13'" },
|
||||
{ name = "diskcache", marker = "python_full_version < '3.13'" },
|
||||
{ name = "docker", marker = "python_full_version < '3.13'" },
|
||||
{ name = "httpx", marker = "python_full_version < '3.13'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.13'" },
|
||||
{ name = "pydantic", marker = "python_full_version < '3.13'" },
|
||||
{ name = "python-dotenv", marker = "python_full_version < '3.13'" },
|
||||
{ name = "termcolor", marker = "python_full_version < '3.13'" },
|
||||
{ name = "tiktoken", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/15/edfbbf217e19ea647225b3ab72a6e3755d2677665f1a7f8e5108da3feabd/ag2-0.9.6.tar.gz", hash = "sha256:d6f7812b1a49654d14113fa3c13ccb593115dee1193744ca428d7178d2b32090", size = 3356270, upload-time = "2025-07-08T14:56:21.63Z" }
|
||||
wheels = [
|
||||
@@ -267,7 +267,7 @@ name = "asyncer"
|
||||
version = "0.0.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "anyio", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" }
|
||||
wheels = [
|
||||
@@ -288,7 +288,7 @@ name = "autogen"
|
||||
version = "0.9.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ag2" },
|
||||
{ name = "ag2", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/b9/dc958031b7e08ee50e3d40f5991f4c0bc21538df8d53aa3e9a9f2e2f7818/autogen-0.9.6.tar.gz", hash = "sha256:dc2efbeef61002608983afb120e62f8a109815eb741bcbc9ef398dcff7424a30", size = 43422, upload-time = "2025-07-08T14:56:17.6Z" }
|
||||
wheels = [
|
||||
@@ -914,9 +914,9 @@ name = "docker"
|
||||
version = "7.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||
{ name = "requests" },
|
||||
{ name = "urllib3" },
|
||||
{ name = "pywin32", marker = "python_full_version < '3.13' and sys_platform == 'win32'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.13'" },
|
||||
{ name = "urllib3", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" }
|
||||
wheels = [
|
||||
@@ -2337,7 +2337,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.2"
|
||||
version = "0.6.0a1"
|
||||
source = { editable = "../libs/langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -2365,7 +2365,7 @@ dev = [
|
||||
{ name = "langgraph-checkpoint", editable = "../libs/checkpoint" },
|
||||
{ name = "langgraph-checkpoint-postgres", editable = "../libs/checkpoint-postgres" },
|
||||
{ name = "langgraph-checkpoint-sqlite", editable = "../libs/checkpoint-sqlite" },
|
||||
{ name = "langgraph-cli", extras = ["inmem"] },
|
||||
{ name = "langgraph-cli", extras = ["inmem"], editable = "../libs/cli" },
|
||||
{ name = "langgraph-prebuilt", editable = "../libs/prebuilt" },
|
||||
{ name = "langgraph-sdk", editable = "../libs/sdk-py" },
|
||||
{ name = "mypy" },
|
||||
@@ -2388,7 +2388,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = { editable = "../libs/checkpoint" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -2433,7 +2433,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.21"
|
||||
version = "2.0.23"
|
||||
source = { editable = "../libs/checkpoint-postgres" }
|
||||
dependencies = [
|
||||
{ name = "langgraph-checkpoint" },
|
||||
@@ -2674,7 +2674,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.72"
|
||||
version = "0.2.0a1"
|
||||
source = { editable = "../libs/sdk-py" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
|
||||
@@ -284,10 +284,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
configurable = config["configurable"].copy()
|
||||
thread_id = configurable.pop("thread_id")
|
||||
checkpoint_ns = configurable.pop("checkpoint_ns")
|
||||
checkpoint_id = configurable.pop(
|
||||
"checkpoint_id", configurable.pop("thread_ts", None)
|
||||
)
|
||||
|
||||
checkpoint_id = configurable.pop("checkpoint_id", None)
|
||||
copy = checkpoint.copy()
|
||||
copy["channel_values"] = copy["channel_values"].copy()
|
||||
next_config = {
|
||||
|
||||
@@ -240,9 +240,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
configurable = config["configurable"].copy()
|
||||
thread_id = configurable.pop("thread_id")
|
||||
checkpoint_ns = configurable.pop("checkpoint_ns")
|
||||
checkpoint_id = configurable.pop(
|
||||
"checkpoint_id", configurable.pop("thread_ts", None)
|
||||
)
|
||||
checkpoint_id = configurable.pop("checkpoint_id", None)
|
||||
|
||||
copy = checkpoint.copy()
|
||||
copy["channel_values"] = copy["channel_values"].copy()
|
||||
|
||||
@@ -191,7 +191,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
) -> 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)`.",
|
||||
"Use PostgresSaver instead, and invoke the graph with `graph.invoke(..., durability='exit')`.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
@@ -547,7 +547,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
) -> 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)`.",
|
||||
"Use AsyncPostgresSaver instead, and invoke the graph with `await graph.ainvoke(..., durability='exit')`.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@@ -161,8 +161,7 @@ def test_data():
|
||||
config_1: RunnableConfig = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
"checkpoint_ns": "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +143,7 @@ def test_data():
|
||||
config_1: RunnableConfig = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
"checkpoint_ns": "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ class TestAsyncSqliteSaver:
|
||||
self.config_1: RunnableConfig = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
"checkpoint_ns": "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class TestSqliteSaver:
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
"checkpoint_ns": "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ Each checkpointer should conform to `langgraph.checkpoint.base.BaseCheckpointSav
|
||||
|
||||
- `.put` - Store a checkpoint with its configuration and metadata.
|
||||
- `.put_writes` - Store intermediate writes linked to a checkpoint (i.e. pending writes).
|
||||
- `.get_tuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `thread_ts`).
|
||||
- `.get_tuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `checkpoint_id`).
|
||||
- `.list` - List checkpoints that match a given configuration and filter criteria.
|
||||
|
||||
If the checkpointer will be used with asynchronous graph execution (i.e. executing the graph via `.ainvoke`, `.astream`, `.abatch`), checkpointer must implement asynchronous versions of the above methods (`.aput`, `.aput_writes`, `.aget_tuple`, `.alist`).
|
||||
|
||||
@@ -375,10 +375,8 @@ class EmptyChannelError(Exception):
|
||||
|
||||
|
||||
def get_checkpoint_id(config: RunnableConfig) -> str | None:
|
||||
"""Get checkpoint ID in a backwards-compatible manner (fallback on thread_ts)."""
|
||||
return config["configurable"].get(
|
||||
"checkpoint_id", config["configurable"].get("thread_ts")
|
||||
)
|
||||
"""Get checkpoint ID."""
|
||||
return config["configurable"].get("checkpoint_id")
|
||||
|
||||
|
||||
def get_checkpoint_metadata(
|
||||
@@ -413,7 +411,6 @@ WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2, INTERRUPT: -3, RESUME: -4}
|
||||
|
||||
EXCLUDED_METADATA_KEYS = {
|
||||
"thread_id",
|
||||
"thread_ts",
|
||||
"checkpoint_id",
|
||||
"checkpoint_ns",
|
||||
"checkpoint_map",
|
||||
|
||||
@@ -22,8 +22,7 @@ class TestMemorySaver:
|
||||
"configurable": {
|
||||
"thread_id": "thread-1",
|
||||
"checkpoint_ns": "",
|
||||
# for backwards compatibility testing
|
||||
"thread_ts": "1",
|
||||
"checkpoint_id": "1",
|
||||
}
|
||||
}
|
||||
self.config_2: RunnableConfig = {
|
||||
|
||||
@@ -49,12 +49,12 @@ def call_model(state, config):
|
||||
tool_node = ToolNode(tools)
|
||||
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
class ContextSchema(TypedDict):
|
||||
model: Literal["anthropic", "openai"]
|
||||
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState, config_schema=ConfigSchema)
|
||||
workflow = StateGraph(AgentState, context_schema=ContextSchema)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
|
||||
@@ -26,7 +26,7 @@ async def arun(graph: Pregel, input: dict):
|
||||
"configurable": {"thread_id": str(uuid4())},
|
||||
"recursion_limit": 1000000000,
|
||||
},
|
||||
checkpoint_during=False,
|
||||
durability="exit",
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -43,7 +43,7 @@ async def arun_first_event_latency(graph: Pregel, input: dict) -> None:
|
||||
"configurable": {"thread_id": str(uuid4())},
|
||||
"recursion_limit": 1000000000,
|
||||
},
|
||||
checkpoint_during=False,
|
||||
durability="exit",
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -63,7 +63,7 @@ def run(graph: Pregel, input: dict):
|
||||
"configurable": {"thread_id": str(uuid4())},
|
||||
"recursion_limit": 1000000000,
|
||||
},
|
||||
checkpoint_during=False,
|
||||
durability="exit",
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -80,7 +80,7 @@ def run_first_event_latency(graph: Pregel, input: dict) -> None:
|
||||
"configurable": {"thread_id": str(uuid4())},
|
||||
"recursion_limit": 1000000000,
|
||||
},
|
||||
checkpoint_during=False,
|
||||
durability="exit",
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -3,8 +3,9 @@ from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.constants import END, START, Send
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.types import Send
|
||||
|
||||
|
||||
def fanout_to_subgraph() -> StateGraph:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Create a sequential no-op graph consisting of a few hundred nodes."""
|
||||
|
||||
from langgraph._internal._runnable import RunnableCallable
|
||||
from langgraph.graph import MessagesState, StateGraph
|
||||
from langgraph.utils.runnable import RunnableCallable
|
||||
|
||||
|
||||
def create_sequential(number_nodes: int) -> StateGraph:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Internal modules for LangGraph.
|
||||
|
||||
This module is not part of the public API, and thus stability is not guaranteed.
|
||||
"""
|
||||
+2
-3
@@ -18,9 +18,7 @@ from langchain_core.runnables.config import (
|
||||
var_child_runnable_config,
|
||||
)
|
||||
|
||||
from langgraph.checkpoint.base import CheckpointMetadata
|
||||
from langgraph.config import get_config, get_store, get_stream_writer # noqa
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
@@ -28,6 +26,7 @@ from langgraph.constants import (
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
)
|
||||
from langgraph.checkpoint.base import CheckpointMetadata
|
||||
|
||||
DEFAULT_RECURSION_LIMIT = int(getenv("LANGGRAPH_DEFAULT_RECURSION_LIMIT", "25"))
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Constants used for Pregel operations."""
|
||||
|
||||
import sys
|
||||
from typing import Literal, cast
|
||||
|
||||
# --- Reserved write keys ---
|
||||
INPUT = sys.intern("__input__")
|
||||
# for values passed as input to the graph
|
||||
INTERRUPT = sys.intern("__interrupt__")
|
||||
# for dynamic interrupts raised by nodes
|
||||
RESUME = sys.intern("__resume__")
|
||||
# for values passed to resume a node after an interrupt
|
||||
ERROR = sys.intern("__error__")
|
||||
# for errors raised by nodes
|
||||
NO_WRITES = sys.intern("__no_writes__")
|
||||
# marker to signal node didn't write anything
|
||||
TASKS = sys.intern("__pregel_tasks")
|
||||
# for Send objects returned by nodes/edges, corresponds to PUSH below
|
||||
RETURN = sys.intern("__return__")
|
||||
# for writes of a task where we simply record the return value
|
||||
PREVIOUS = sys.intern("__previous__")
|
||||
# the implicit branch that handles each node's Control values
|
||||
|
||||
|
||||
# --- Reserved cache namespaces ---
|
||||
CACHE_NS_WRITES = sys.intern("__pregel_ns_writes")
|
||||
# cache namespace for node writes
|
||||
|
||||
# --- Reserved config.configurable keys ---
|
||||
CONFIG_KEY_SEND = sys.intern("__pregel_send")
|
||||
# holds the `write` function that accepts writes to state/edges/reserved keys
|
||||
CONFIG_KEY_READ = sys.intern("__pregel_read")
|
||||
# holds the `read` function that returns a copy of the current state
|
||||
CONFIG_KEY_CALL = sys.intern("__pregel_call")
|
||||
# holds the `call` function that accepts a node/func, args and returns a future
|
||||
CONFIG_KEY_CHECKPOINTER = sys.intern("__pregel_checkpointer")
|
||||
# holds a `BaseCheckpointSaver` passed from parent graph to child graphs
|
||||
CONFIG_KEY_STREAM = sys.intern("__pregel_stream")
|
||||
# holds a `StreamProtocol` passed from parent graph to child graphs
|
||||
CONFIG_KEY_CACHE = sys.intern("__pregel_cache")
|
||||
# holds a `BaseCache` made available to subgraphs
|
||||
CONFIG_KEY_RESUMING = sys.intern("__pregel_resuming")
|
||||
# holds a boolean indicating if subgraphs should resume from a previous checkpoint
|
||||
CONFIG_KEY_TASK_ID = sys.intern("__pregel_task_id")
|
||||
# holds the task ID for the current task
|
||||
CONFIG_KEY_THREAD_ID = sys.intern("thread_id")
|
||||
# holds the thread ID for the current invocation
|
||||
CONFIG_KEY_CHECKPOINT_MAP = sys.intern("checkpoint_map")
|
||||
# holds a mapping of checkpoint_ns -> checkpoint_id for parent graphs
|
||||
CONFIG_KEY_CHECKPOINT_ID = sys.intern("checkpoint_id")
|
||||
# holds the current checkpoint_id, if any
|
||||
CONFIG_KEY_CHECKPOINT_NS = sys.intern("checkpoint_ns")
|
||||
# holds the current checkpoint_ns, "" for root graph
|
||||
CONFIG_KEY_NODE_FINISHED = sys.intern("__pregel_node_finished")
|
||||
# holds a callback to be called when a node is finished
|
||||
CONFIG_KEY_SCRATCHPAD = sys.intern("__pregel_scratchpad")
|
||||
# holds a mutable dict for temporary storage scoped to the current task
|
||||
CONFIG_KEY_RUNNER_SUBMIT = sys.intern("__pregel_runner_submit")
|
||||
# holds a function that receives tasks from runner, executes them and returns results
|
||||
CONFIG_KEY_DURABILITY = sys.intern("__pregel_durability")
|
||||
# holds the durability mode, one of "sync", "async", or "exit"
|
||||
CONFIG_KEY_RUNTIME = sys.intern("__pregel_runtime")
|
||||
# holds a `Runtime` instance with context, store, stream writer, etc.
|
||||
CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map")
|
||||
# holds a mapping of task ns -> resume value for resuming tasks
|
||||
|
||||
# --- Other constants ---
|
||||
PUSH = sys.intern("__pregel_push")
|
||||
# denotes push-style tasks, ie. those created by Send objects
|
||||
PULL = sys.intern("__pregel_pull")
|
||||
# denotes pull-style tasks, ie. those triggered by edges
|
||||
NS_SEP = sys.intern("|")
|
||||
# for checkpoint_ns, separates each level (ie. graph|subgraph|subsubgraph)
|
||||
NS_END = sys.intern(":")
|
||||
# for checkpoint_ns, for each level, separates the namespace from the task_id
|
||||
CONF = cast(Literal["configurable"], sys.intern("configurable"))
|
||||
# key for the configurable dict in RunnableConfig
|
||||
NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000")
|
||||
# the task_id to use for writes that are not associated with a task
|
||||
|
||||
# redefined to avoid circular import with langgraph.constants
|
||||
_TAG_HIDDEN = sys.intern("langsmith:hidden")
|
||||
|
||||
RESERVED = {
|
||||
_TAG_HIDDEN,
|
||||
# reserved write keys
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
RESUME,
|
||||
ERROR,
|
||||
NO_WRITES,
|
||||
# reserved config.configurable keys
|
||||
CONFIG_KEY_SEND,
|
||||
CONFIG_KEY_READ,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_RESUMING,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUME_MAP,
|
||||
# other constants
|
||||
PUSH,
|
||||
PULL,
|
||||
NS_SEP,
|
||||
NS_END,
|
||||
CONF,
|
||||
}
|
||||
+1
-3
@@ -9,9 +9,7 @@ from typing import Annotated, Any, Optional, Union, get_type_hints
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import NotRequired, ReadOnly, Required, get_origin
|
||||
|
||||
# NOTE: this is redefined here separately from langgraph.constants
|
||||
# to avoid a circular import
|
||||
MISSING = object()
|
||||
from langgraph._internal._typing import MISSING
|
||||
|
||||
|
||||
def _is_optional_type(type_: Any) -> bool:
|
||||
@@ -128,6 +128,3 @@ class SyncQueue:
|
||||
return len(self._queue)
|
||||
|
||||
__class_getitem__ = classmethod(types.GenericAlias)
|
||||
|
||||
|
||||
__all__ = ["AsyncQueue", "SyncQueue"]
|
||||
@@ -0,0 +1,29 @@
|
||||
def default_retry_on(exc: Exception) -> bool:
|
||||
import httpx
|
||||
import requests
|
||||
|
||||
if isinstance(exc, ConnectionError):
|
||||
return True
|
||||
if isinstance(exc, httpx.HTTPStatusError):
|
||||
return 500 <= exc.response.status_code < 600
|
||||
if isinstance(exc, requests.HTTPError):
|
||||
return 500 <= exc.response.status_code < 600 if exc.response else True
|
||||
if isinstance(
|
||||
exc,
|
||||
(
|
||||
ValueError,
|
||||
TypeError,
|
||||
ArithmeticError,
|
||||
ImportError,
|
||||
LookupError,
|
||||
NameError,
|
||||
SyntaxError,
|
||||
RuntimeError,
|
||||
ReferenceError,
|
||||
StopIteration,
|
||||
StopAsyncIteration,
|
||||
OSError,
|
||||
),
|
||||
):
|
||||
return False
|
||||
return True
|
||||
+93
-76
@@ -42,20 +42,19 @@ from langchain_core.runnables.utils import Input, Output
|
||||
from langchain_core.tracers.langchain import LangChainTracer
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
from langgraph.constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_PREVIOUS,
|
||||
CONFIG_KEY_STORE,
|
||||
CONFIG_KEY_STREAM_WRITER,
|
||||
)
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import StreamWriter
|
||||
from langgraph.utils.config import (
|
||||
from langgraph._internal._config import (
|
||||
ensure_config,
|
||||
get_async_callback_manager_for_config,
|
||||
get_callback_manager_for_config,
|
||||
patch_config,
|
||||
)
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_RUNTIME,
|
||||
)
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import StreamWriter
|
||||
|
||||
try:
|
||||
from langchain_core.tracers._streaming import _StreamingCallbackHandler
|
||||
@@ -128,45 +127,52 @@ ANY_TYPE = object()
|
||||
|
||||
ASYNCIO_ACCEPTS_CONTEXT = sys.version_info >= (3, 11)
|
||||
|
||||
# List of keyword arguments that can be injected at runtime from the config object.
|
||||
# List of keyword arguments that can be injected into nodes / tasks / tools at runtime.
|
||||
# A named argument may appear multiple times if it appears with distinct types.
|
||||
KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
|
||||
(
|
||||
sys.intern("writer"),
|
||||
"config",
|
||||
(RunnableConfig, "RunnableConfig", inspect.Parameter.empty),
|
||||
# for now, use config directly, eventually, will pop off of Runtime
|
||||
"N/A",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
(
|
||||
"writer",
|
||||
(StreamWriter, "StreamWriter", inspect.Parameter.empty),
|
||||
CONFIG_KEY_STREAM_WRITER,
|
||||
"stream_writer",
|
||||
lambda _: None,
|
||||
),
|
||||
(
|
||||
# Covers store that is not optional (will raise an error if a store
|
||||
# cannot be injected).
|
||||
sys.intern("store"),
|
||||
"store",
|
||||
(
|
||||
BaseStore,
|
||||
"BaseStore",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
CONFIG_KEY_STORE,
|
||||
"store",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
(
|
||||
# Covers store that is optional. Will set to None if not found in config.
|
||||
sys.intern("store"),
|
||||
"store",
|
||||
(
|
||||
Optional[BaseStore],
|
||||
# Best effort to catch some forward references.
|
||||
# This will not work for cases like `"Union[None, BaseStore]"`,
|
||||
# we'll need to re-write logic to use get_type_hints()
|
||||
# to resolve forward references.
|
||||
"Optional[BaseStore]",
|
||||
),
|
||||
CONFIG_KEY_STORE,
|
||||
"store",
|
||||
None,
|
||||
),
|
||||
(
|
||||
sys.intern("previous"),
|
||||
"previous",
|
||||
(ANY_TYPE,),
|
||||
CONFIG_KEY_PREVIOUS,
|
||||
"previous",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
(
|
||||
"runtime",
|
||||
(ANY_TYPE,),
|
||||
# we never hit this block, we just inject runtime directly
|
||||
"N/A",
|
||||
inspect.Parameter.empty,
|
||||
),
|
||||
)
|
||||
@@ -174,7 +180,7 @@ KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
|
||||
config keys, default values and type annotations.
|
||||
|
||||
Used to configure keyword arguments that can be injected at runtime
|
||||
from the config object as kwargs to `invoke`, `ainvoke`, `stream` and `astream`.
|
||||
from the `Runtime` object as kwargs to `invoke`, `ainvoke`, `stream` and `astream`.
|
||||
|
||||
For a keyword to be injected from the config object, the function signature
|
||||
must contain a kwarg with the same name and a matching type annotation.
|
||||
@@ -182,8 +188,10 @@ must contain a kwarg with the same name and a matching type annotation.
|
||||
Each tuple contains:
|
||||
- the name of the kwarg in the function signature
|
||||
- the type annotation(s) for the kwarg
|
||||
- the config key to look for the value in
|
||||
- the default value for the kwarg
|
||||
- the `Runtime` attribute for fetching the value (N/A if not applicable)
|
||||
|
||||
This is fully internal and should be further refactored to use `get_type_hints`
|
||||
to resolve forward references and optional types formatted like BaseStore | None.
|
||||
"""
|
||||
|
||||
VALID_KINDS = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
|
||||
@@ -250,7 +258,6 @@ class RunnableCallable(Runnable):
|
||||
trace: bool = True,
|
||||
recurse: bool = True,
|
||||
explode_args: bool = False,
|
||||
func_accepts_config: bool | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.name = name
|
||||
@@ -277,31 +284,23 @@ class RunnableCallable(Runnable):
|
||||
if func is None and afunc is None:
|
||||
raise ValueError("At least one of func or afunc must be provided.")
|
||||
|
||||
if func_accepts_config is not None:
|
||||
self.func_accepts_config = func_accepts_config
|
||||
self.func_accepts: dict[str, tuple[str, Any]] = {}
|
||||
else:
|
||||
params = inspect.signature(cast(Callable, func or afunc)).parameters
|
||||
self.func_accepts: dict[str, tuple[str, Any]] = {}
|
||||
params = inspect.signature(cast(Callable, func or afunc)).parameters
|
||||
|
||||
self.func_accepts_config = "config" in params
|
||||
# Mapping from kwarg name to (config key, default value) to be used.
|
||||
# The default value is used if the config key is not found in the config.
|
||||
self.func_accepts = {}
|
||||
for kw, typ, runtime_key, default in KWARGS_CONFIG_KEYS:
|
||||
p = params.get(kw)
|
||||
|
||||
for kw, typ, config_key, default in KWARGS_CONFIG_KEYS:
|
||||
p = params.get(kw)
|
||||
if p is None or p.kind not in VALID_KINDS:
|
||||
# If parameter is not found or is not a valid kind, skip
|
||||
continue
|
||||
|
||||
if p is None or p.kind not in VALID_KINDS:
|
||||
# If parameter is not found or is not a valid kind, skip
|
||||
continue
|
||||
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.
|
||||
continue
|
||||
|
||||
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.
|
||||
continue
|
||||
|
||||
# If the kwarg is accepted by the function, store the default value
|
||||
self.func_accepts[kw] = (config_key, default)
|
||||
# If the kwarg is accepted by the function, store the key / runtime attribute to inject
|
||||
self.func_accepts[kw] = (runtime_key, default)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
repr_args = {
|
||||
@@ -328,25 +327,33 @@ class RunnableCallable(Runnable):
|
||||
else:
|
||||
args = (input,)
|
||||
kwargs = {**self.kwargs, **kwargs}
|
||||
if self.func_accepts_config:
|
||||
kwargs["config"] = config
|
||||
_conf = config[CONF]
|
||||
|
||||
for kw, (config_key, default_value) in self.func_accepts.items():
|
||||
runtime = config[CONF].get(CONFIG_KEY_RUNTIME)
|
||||
|
||||
for kw, (runtime_key, default) in self.func_accepts.items():
|
||||
# If the kwarg is already set, use the set value
|
||||
if kw in kwargs:
|
||||
continue
|
||||
|
||||
if (
|
||||
# If the kwarg is requested, but isn't in the config AND has no
|
||||
# default value, raise an error
|
||||
config_key not in _conf and default_value is inspect.Parameter.empty
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing required config key '{config_key}' for '{self.name}'."
|
||||
)
|
||||
kw_value: Any = MISSING
|
||||
if kw == "config":
|
||||
kw_value = config
|
||||
elif runtime:
|
||||
if kw == "runtime":
|
||||
kw_value = runtime
|
||||
else:
|
||||
try:
|
||||
kw_value = getattr(runtime, runtime_key)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
kwargs[kw] = _conf.get(config_key, default_value)
|
||||
if kw_value is MISSING:
|
||||
if default is inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"Missing required config key '{runtime_key}' for '{self.name}'."
|
||||
)
|
||||
kw_value = default
|
||||
kwargs[kw] = kw_value
|
||||
|
||||
if self.trace:
|
||||
callback_manager = get_callback_manager_for_config(config, self.tags)
|
||||
@@ -392,23 +399,33 @@ class RunnableCallable(Runnable):
|
||||
else:
|
||||
args = (input,)
|
||||
kwargs = {**self.kwargs, **kwargs}
|
||||
if self.func_accepts_config:
|
||||
kwargs["config"] = config
|
||||
_conf = config[CONF]
|
||||
for kw, (config_key, default_value) in self.func_accepts.items():
|
||||
|
||||
runtime = config[CONF].get(CONFIG_KEY_RUNTIME)
|
||||
|
||||
for kw, (runtime_key, default) in self.func_accepts.items():
|
||||
# If the kwarg has already been set, use the set value
|
||||
if kw in kwargs:
|
||||
continue
|
||||
|
||||
if (
|
||||
# If the kwarg is requested, but isn't in the config AND has no
|
||||
# default value, raise an error
|
||||
config_key not in _conf and default_value is inspect.Parameter.empty
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing required config key '{config_key}' for '{self.name}'."
|
||||
)
|
||||
kwargs[kw] = _conf.get(config_key, default_value)
|
||||
kw_value: Any = MISSING
|
||||
if kw == "config":
|
||||
kw_value = config
|
||||
elif runtime:
|
||||
if kw == "runtime":
|
||||
kw_value = runtime
|
||||
else:
|
||||
try:
|
||||
kw_value = getattr(runtime, runtime_key)
|
||||
except AttributeError:
|
||||
pass
|
||||
if kw_value is MISSING:
|
||||
if default is inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"Missing required config key '{runtime_key}' for '{self.name}'."
|
||||
)
|
||||
kw_value = default
|
||||
kwargs[kw] = kw_value
|
||||
|
||||
if self.trace:
|
||||
callback_manager = get_async_callback_manager_for_config(config, self.tags)
|
||||
run_manager = await callback_manager.on_chain_start(
|
||||
@@ -42,13 +42,13 @@ It can either be a `TypedDict`, `dataclass`, or Pydantic `BaseModel`.
|
||||
Note: we cannot use either `TypedDict` or `dataclass` directly due to limitations in type checking.
|
||||
"""
|
||||
|
||||
|
||||
class Unset:
|
||||
"""A sentinel value to represent an unset type."""
|
||||
|
||||
|
||||
UNSET: Unset = Unset()
|
||||
MISSING = object()
|
||||
"""Unset sentinel value."""
|
||||
|
||||
|
||||
class DeprecatedKwargs(TypedDict):
|
||||
"""TypedDict to use for extra keyword arguments, enabling type checking warnings for deprecated arguments."""
|
||||
|
||||
|
||||
EMPTY_SEQ: tuple[str, ...] = tuple()
|
||||
"""An empty sequence of strings."""
|
||||
@@ -1,15 +1,27 @@
|
||||
from langgraph.channels.any_value import AnyValue
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||
from langgraph.channels.ephemeral_value import EphemeralValue
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.last_value import LastValue, LastValueAfterFinish
|
||||
from langgraph.channels.named_barrier_value import (
|
||||
NamedBarrierValue,
|
||||
NamedBarrierValueAfterFinish,
|
||||
)
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.channels.untracked_value import UntrackedValue
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
# base
|
||||
"BaseChannel",
|
||||
# value types
|
||||
"AnyValue",
|
||||
"LastValue",
|
||||
"Topic",
|
||||
"BinaryOperatorAggregate",
|
||||
"LastValueAfterFinish",
|
||||
"UntrackedValue",
|
||||
"EphemeralValue",
|
||||
"AnyValue",
|
||||
]
|
||||
"BinaryOperatorAggregate",
|
||||
"NamedBarrierValue",
|
||||
"NamedBarrierValueAfterFinish",
|
||||
# topics
|
||||
"Topic",
|
||||
)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Generic
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
__all__ = ("AnyValue",)
|
||||
|
||||
|
||||
class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""Stores the last value received, assumes that if multiple values are
|
||||
@@ -14,6 +18,8 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
|
||||
__slots__ = ("typ", "value")
|
||||
|
||||
value: Value | Any
|
||||
|
||||
def __init__(self, typ: Any, key: str = "") -> None:
|
||||
super().__init__(typ, key)
|
||||
self.value = MISSING
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
Value = TypeVar("Value")
|
||||
Update = TypeVar("Update")
|
||||
C = TypeVar("C")
|
||||
Checkpoint = TypeVar("Checkpoint")
|
||||
|
||||
__all__ = ("BaseChannel",)
|
||||
|
||||
|
||||
class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
class BaseChannel(Generic[Value, Update, Checkpoint], ABC):
|
||||
"""Base class for all channels."""
|
||||
|
||||
__slots__ = ("key", "typ")
|
||||
@@ -39,7 +43,7 @@ class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
Subclasses can override this method with a more efficient implementation."""
|
||||
return self.from_checkpoint(self.checkpoint())
|
||||
|
||||
def checkpoint(self) -> C:
|
||||
def checkpoint(self) -> Checkpoint | Any:
|
||||
"""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."""
|
||||
@@ -49,7 +53,7 @@ class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
return MISSING
|
||||
|
||||
@abstractmethod
|
||||
def from_checkpoint(self, checkpoint: C) -> Self:
|
||||
def from_checkpoint(self, checkpoint: Checkpoint | Any) -> Self:
|
||||
"""Return a new identical channel, optionally initialized from a checkpoint.
|
||||
If the checkpoint contains complex data structures, they should be copied."""
|
||||
|
||||
@@ -99,10 +103,3 @@ class BaseChannel(Generic[Value, Update, C], ABC):
|
||||
Returns True if the channel was updated, False otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BaseChannel",
|
||||
"EmptyChannelError",
|
||||
"InvalidUpdateError",
|
||||
]
|
||||
|
||||
@@ -4,10 +4,12 @@ from typing import Callable, Generic
|
||||
|
||||
from typing_extensions import NotRequired, Required, Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
__all__ = ("BinaryOperatorAggregate",)
|
||||
|
||||
|
||||
# Adapted from typing_extensions
|
||||
def _strip_extras(t): # type: ignore[no-untyped-def]
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Generic
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
|
||||
__all__ = ("EphemeralValue",)
|
||||
|
||||
|
||||
class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""Stores the value received in the step immediately preceding, clears after."""
|
||||
|
||||
__slots__ = ("value", "guard")
|
||||
|
||||
value: Value | Any
|
||||
guard: bool
|
||||
|
||||
def __init__(self, typ: Any, guard: bool = True) -> None:
|
||||
super().__init__(typ)
|
||||
self.guard = guard
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Generic
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import (
|
||||
EmptyChannelError,
|
||||
ErrorCode,
|
||||
@@ -12,12 +14,16 @@ from langgraph.errors import (
|
||||
create_error_message,
|
||||
)
|
||||
|
||||
__all__ = ("LastValue", "LastValueAfterFinish")
|
||||
|
||||
|
||||
class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""Stores the last value received, can receive at most one value per step."""
|
||||
|
||||
__slots__ = ("value",)
|
||||
|
||||
value: Value | Any
|
||||
|
||||
def __init__(self, typ: Any, key: str = "") -> None:
|
||||
super().__init__(typ, key)
|
||||
self.value = MISSING
|
||||
@@ -80,6 +86,9 @@ class LastValueAfterFinish(
|
||||
|
||||
__slots__ = ("value", "finished")
|
||||
|
||||
value: Value | Any
|
||||
finished: bool
|
||||
|
||||
def __init__(self, typ: Any, key: str = "") -> None:
|
||||
super().__init__(typ, key)
|
||||
self.value = MISSING
|
||||
@@ -98,19 +107,19 @@ class LastValueAfterFinish(
|
||||
"""The type of the update received by the channel."""
|
||||
return self.typ
|
||||
|
||||
def checkpoint(self) -> tuple[Value, bool]:
|
||||
def checkpoint(self) -> tuple[Value | Any, bool] | Any:
|
||||
if self.value is MISSING:
|
||||
return MISSING
|
||||
return (self.value, self.finished)
|
||||
|
||||
def from_checkpoint(self, checkpoint: tuple[Value, bool]) -> Self:
|
||||
def from_checkpoint(self, checkpoint: tuple[Value | Any, bool] | Any) -> Self:
|
||||
empty = self.__class__(self.typ)
|
||||
empty.key = self.key
|
||||
if checkpoint is not MISSING:
|
||||
empty.value, empty.finished = checkpoint
|
||||
return empty
|
||||
|
||||
def update(self, values: Sequence[Value]) -> bool:
|
||||
def update(self, values: Sequence[Value | Any]) -> bool:
|
||||
if len(values) == 0:
|
||||
return False
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ from typing import Generic
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
|
||||
__all__ = ("NamedBarrierValue", "NamedBarrierValueAfterFinish")
|
||||
|
||||
|
||||
class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]):
|
||||
"""A channel that waits until all named values are received before making the value available."""
|
||||
|
||||
@@ -5,12 +5,14 @@ from typing import Any, Generic, Union
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
__all__ = ("Topic",)
|
||||
|
||||
def flatten(values: Sequence[Value | list[Value]]) -> Iterator[Value]:
|
||||
|
||||
def _flatten(values: Sequence[Value | list[Value]]) -> Iterator[Value]:
|
||||
for value in values:
|
||||
if isinstance(value, list):
|
||||
yield from value
|
||||
@@ -77,7 +79,7 @@ class Topic(
|
||||
if not self.accumulate:
|
||||
updated = bool(self.values)
|
||||
self.values = list[Value]()
|
||||
if flat_values := tuple(flatten(values)):
|
||||
if flat_values := tuple(_flatten(values)):
|
||||
updated = True
|
||||
self.values.extend(flat_values)
|
||||
return updated
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Generic
|
||||
from typing import Any, Generic
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
|
||||
__all__ = ("UntrackedValue",)
|
||||
|
||||
|
||||
class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""Stores the last value received, never checkpointed."""
|
||||
|
||||
__slots__ = ("value", "guard")
|
||||
|
||||
guard: bool
|
||||
value: Value | Any
|
||||
|
||||
def __init__(self, typ: type[Value], guard: bool = True) -> None:
|
||||
super().__init__(typ)
|
||||
self.guard = guard
|
||||
@@ -38,7 +45,7 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
empty.value = self.value
|
||||
return empty
|
||||
|
||||
def checkpoint(self) -> Value:
|
||||
def checkpoint(self) -> Value | Any:
|
||||
return MISSING
|
||||
|
||||
def from_checkpoint(self, checkpoint: Value) -> Self:
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langchain_core.runnables.config import var_child_runnable_config
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_STORE, CONFIG_KEY_STREAM_WRITER
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_RUNTIME
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import StreamWriter
|
||||
|
||||
@@ -114,8 +114,7 @@ def get_store() -> BaseStore:
|
||||
3
|
||||
```
|
||||
"""
|
||||
config = get_config()
|
||||
return config[CONF][CONFIG_KEY_STORE]
|
||||
return get_config()[CONF][CONFIG_KEY_RUNTIME].store
|
||||
|
||||
|
||||
def get_stream_writer() -> StreamWriter:
|
||||
@@ -181,5 +180,5 @@ def get_stream_writer() -> StreamWriter:
|
||||
{'custom_data': 'Hello!'}
|
||||
```
|
||||
"""
|
||||
config = get_config()
|
||||
return config[CONF].get(CONFIG_KEY_STREAM_WRITER, _no_op_stream_writer)
|
||||
runtime = get_config()[CONF][CONFIG_KEY_RUNTIME]
|
||||
return runtime.stream_writer
|
||||
|
||||
@@ -1,133 +1,64 @@
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Literal, cast
|
||||
from typing import Any
|
||||
from warnings import warn
|
||||
|
||||
from langgraph.types import Interrupt, Send # noqa: F401
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
TASKS,
|
||||
)
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
|
||||
# Interrupt, Send re-exported for backwards compatibility
|
||||
|
||||
|
||||
# --- Empty read-only containers ---
|
||||
EMPTY_MAP: Mapping[str, Any] = MappingProxyType({})
|
||||
EMPTY_SEQ: tuple[str, ...] = tuple()
|
||||
MISSING = object()
|
||||
__all__ = (
|
||||
"TAG_NOSTREAM",
|
||||
"TAG_HIDDEN",
|
||||
"START",
|
||||
"END",
|
||||
# retained for backwards compatibility (mostly langgraph-api), should be removed in v2 (or earlier)
|
||||
"CONF",
|
||||
"TASKS",
|
||||
"CONFIG_KEY_CHECKPOINTER",
|
||||
)
|
||||
|
||||
# --- Public constants ---
|
||||
TAG_NOSTREAM = sys.intern("nostream")
|
||||
"""Tag to disable streaming for a chat model."""
|
||||
TAG_NOSTREAM_ALT = sys.intern("langsmith:nostream")
|
||||
"""Tag to disable streaming for a chat model. (Deprecated in favour of "nostream")"""
|
||||
TAG_HIDDEN = sys.intern("langsmith:hidden")
|
||||
"""Tag to hide a node/edge from certain tracing/streaming environments."""
|
||||
START = sys.intern("__start__")
|
||||
"""The first (maybe virtual) node in graph-style Pregel."""
|
||||
END = sys.intern("__end__")
|
||||
"""The last (maybe virtual) node in graph-style Pregel."""
|
||||
SELF = sys.intern("__self__")
|
||||
"""The implicit branch that handles each node's Control values."""
|
||||
PREVIOUS = sys.intern("__previous__")
|
||||
START = sys.intern("__start__")
|
||||
"""The first (maybe virtual) node in graph-style Pregel."""
|
||||
|
||||
# --- Reserved write keys ---
|
||||
INPUT = sys.intern("__input__")
|
||||
# for values passed as input to the graph
|
||||
INTERRUPT = sys.intern("__interrupt__")
|
||||
# for dynamic interrupts raised by nodes
|
||||
RESUME = sys.intern("__resume__")
|
||||
# for values passed to resume a node after an interrupt
|
||||
ERROR = sys.intern("__error__")
|
||||
# for errors raised by nodes
|
||||
NO_WRITES = sys.intern("__no_writes__")
|
||||
# marker to signal node didn't write anything
|
||||
TASKS = sys.intern("__pregel_tasks")
|
||||
# for Send objects returned by nodes/edges, corresponds to PUSH below
|
||||
RETURN = sys.intern("__return__")
|
||||
# for writes of a task where we simply record the return value
|
||||
|
||||
# --- Reserved cache namespaces ---
|
||||
CACHE_NS_WRITES = sys.intern("__pregel_ns_writes")
|
||||
# cache namespace for node writes
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in ["Send", "Interrupt"]:
|
||||
warn(
|
||||
f"Importing {name} from langgraph.constants is deprecated. "
|
||||
f"Please use 'from langgraph.types import {name}' instead.",
|
||||
LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
# --- Reserved config.configurable keys ---
|
||||
CONFIG_KEY_SEND = sys.intern("__pregel_send")
|
||||
# holds the `write` function that accepts writes to state/edges/reserved keys
|
||||
CONFIG_KEY_READ = sys.intern("__pregel_read")
|
||||
# holds the `read` function that returns a copy of the current state
|
||||
CONFIG_KEY_CALL = sys.intern("__pregel_call")
|
||||
# holds the `call` function that accepts a node/func, args and returns a future
|
||||
CONFIG_KEY_CHECKPOINTER = sys.intern("__pregel_checkpointer")
|
||||
# holds a `BaseCheckpointSaver` passed from parent graph to child graphs
|
||||
CONFIG_KEY_STREAM = sys.intern("__pregel_stream")
|
||||
# holds a `StreamProtocol` passed from parent graph to child graphs
|
||||
CONFIG_KEY_STREAM_WRITER = sys.intern("__pregel_stream_writer")
|
||||
# holds a `StreamWriter` for stream_mode=custom
|
||||
CONFIG_KEY_STORE = sys.intern("__pregel_store")
|
||||
# holds a `BaseStore` made available to managed values
|
||||
CONFIG_KEY_CACHE = sys.intern("__pregel_cache")
|
||||
# holds a `BaseCache` made available to subgraphs
|
||||
CONFIG_KEY_RESUMING = sys.intern("__pregel_resuming")
|
||||
# holds a boolean indicating if subgraphs should resume from a previous checkpoint
|
||||
CONFIG_KEY_TASK_ID = sys.intern("__pregel_task_id")
|
||||
# holds the task ID for the current task
|
||||
CONFIG_KEY_THREAD_ID = sys.intern("thread_id")
|
||||
# holds the thread ID for the current invocation
|
||||
CONFIG_KEY_CHECKPOINT_MAP = sys.intern("checkpoint_map")
|
||||
# holds a mapping of checkpoint_ns -> checkpoint_id for parent graphs
|
||||
CONFIG_KEY_CHECKPOINT_ID = sys.intern("checkpoint_id")
|
||||
# holds the current checkpoint_id, if any
|
||||
CONFIG_KEY_CHECKPOINT_NS = sys.intern("checkpoint_ns")
|
||||
# holds the current checkpoint_ns, "" for root graph
|
||||
CONFIG_KEY_NODE_FINISHED = sys.intern("__pregel_node_finished")
|
||||
# holds a callback to be called when a node is finished
|
||||
CONFIG_KEY_SCRATCHPAD = sys.intern("__pregel_scratchpad")
|
||||
# holds a mutable dict for temporary storage scoped to the current task
|
||||
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)
|
||||
from importlib import import_module
|
||||
|
||||
# --- Other constants ---
|
||||
PUSH = sys.intern("__pregel_push")
|
||||
# denotes push-style tasks, ie. those created by Send objects
|
||||
PULL = sys.intern("__pregel_pull")
|
||||
# denotes pull-style tasks, ie. those triggered by edges
|
||||
NS_SEP = sys.intern("|")
|
||||
# for checkpoint_ns, separates each level (ie. graph|subgraph|subsubgraph)
|
||||
NS_END = sys.intern(":")
|
||||
# for checkpoint_ns, for each level, separates the namespace from the task_id
|
||||
CONF = cast(Literal["configurable"], sys.intern("configurable"))
|
||||
# key for the configurable dict in RunnableConfig
|
||||
NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000")
|
||||
# the task_id to use for writes that are not associated with a task
|
||||
CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map")
|
||||
# holds a mapping of task ns -> resume value for resuming tasks
|
||||
module = import_module("langgraph.types")
|
||||
return getattr(module, name)
|
||||
|
||||
RESERVED = {
|
||||
TAG_HIDDEN,
|
||||
# reserved write keys
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
RESUME,
|
||||
ERROR,
|
||||
NO_WRITES,
|
||||
# reserved config.configurable keys
|
||||
CONFIG_KEY_SEND,
|
||||
CONFIG_KEY_READ,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_STREAM_WRITER,
|
||||
CONFIG_KEY_STORE,
|
||||
CONFIG_KEY_RESUMING,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
# other constants
|
||||
PUSH,
|
||||
PULL,
|
||||
NS_SEP,
|
||||
NS_END,
|
||||
CONF,
|
||||
}
|
||||
try:
|
||||
from importlib import import_module
|
||||
|
||||
private_constants = import_module("langgraph._internal._constants")
|
||||
attr = getattr(private_constants, name)
|
||||
warn(
|
||||
f"Importing {name} from langgraph.constants is deprecated. "
|
||||
f"This constant is now private and should not be used directly. "
|
||||
"Please let the LangGraph team know if you need this value.",
|
||||
LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
return attr
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
raise AttributeError(f"module has no attribute '{name}'")
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from warnings import warn
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
# EmptyChannelError is re-exported from langgraph.channels.base
|
||||
from langgraph.checkpoint.base import EmptyChannelError # noqa: F401
|
||||
from langgraph.types import Command, Interrupt
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
|
||||
# EmptyChannelError re-exported for backwards compatibility
|
||||
__all__ = (
|
||||
"EmptyChannelError",
|
||||
"ErrorCode",
|
||||
"GraphRecursionError",
|
||||
"InvalidUpdateError",
|
||||
"GraphBubbleUp",
|
||||
"GraphInterrupt",
|
||||
"NodeInterrupt",
|
||||
"ParentCommand",
|
||||
"EmptyInputError",
|
||||
"TaskNotFound",
|
||||
)
|
||||
|
||||
|
||||
class ErrorCode(Enum):
|
||||
@@ -71,11 +89,26 @@ class GraphInterrupt(GraphBubbleUp):
|
||||
super().__init__(interrupts)
|
||||
|
||||
|
||||
@deprecated(
|
||||
"NodeInterrupt is deprecated. Please use `langgraph.types.interrupt` instead.",
|
||||
stacklevel=2,
|
||||
)
|
||||
class NodeInterrupt(GraphInterrupt):
|
||||
"""Raised by a node to interrupt execution."""
|
||||
"""Raised by a node to interrupt execution.
|
||||
|
||||
def __init__(self, value: Any) -> None:
|
||||
super().__init__([Interrupt(value=value)])
|
||||
Deprecated in V1.0.0 in favor of [`interrupt`][langgraph.types.interrupt].
|
||||
"""
|
||||
|
||||
def __init__(self, value: Any, id: str | None = None) -> None:
|
||||
warn(
|
||||
"NodeInterrupt is deprecated. Please use `langgraph.types.interrupt` instead.",
|
||||
LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
if id is None:
|
||||
super().__init__([Interrupt(value=value)])
|
||||
else:
|
||||
super().__init__([Interrupt(value=value, id=id)])
|
||||
|
||||
|
||||
class ParentCommand(GraphBubbleUp):
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import (
|
||||
Callable,
|
||||
Generic,
|
||||
TypeVar,
|
||||
cast,
|
||||
get_args,
|
||||
get_origin,
|
||||
overload,
|
||||
@@ -19,14 +20,15 @@ from typing import (
|
||||
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from langgraph._typing import UNSET, DeprecatedKwargs
|
||||
from langgraph._internal._constants import CACHE_NS_WRITES, PREVIOUS
|
||||
from langgraph._internal._typing import MISSING, DeprecatedKwargs
|
||||
from langgraph.cache.base import BaseCache
|
||||
from langgraph.channels.ephemeral_value import EphemeralValue
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.constants import CACHE_NS_WRITES, END, PREVIOUS, START
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.pregel import Pregel
|
||||
from langgraph.pregel.call import (
|
||||
from langgraph.pregel._call import (
|
||||
P,
|
||||
SyncAsyncFuture,
|
||||
T,
|
||||
@@ -34,14 +36,17 @@ from langgraph.pregel.call import (
|
||||
get_runnable_for_entrypoint,
|
||||
identifier,
|
||||
)
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.pregel._read import PregelNode
|
||||
from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import _DC_KWARGS, CachePolicy, RetryPolicy, StreamMode
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV05
|
||||
from langgraph.typing import ContextT
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV05, LangGraphDeprecatedSinceV10
|
||||
|
||||
__all__ = ("task", "entrypoint")
|
||||
|
||||
|
||||
class TaskFunction(Generic[P, T]):
|
||||
class _TaskFunction(Generic[P, T]):
|
||||
def __init__(
|
||||
self,
|
||||
func: Callable[P, T],
|
||||
@@ -97,14 +102,14 @@ def task(
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Callable[
|
||||
[Callable[P, Awaitable[T]] | Callable[P, T]],
|
||||
TaskFunction[P, T],
|
||||
_TaskFunction[P, T],
|
||||
]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def task(
|
||||
__func_or_none__: Callable[P, Awaitable[T]] | Callable[P, T],
|
||||
) -> TaskFunction[P, T]: ...
|
||||
) -> _TaskFunction[P, T]: ...
|
||||
|
||||
|
||||
def task(
|
||||
@@ -115,8 +120,8 @@ def task(
|
||||
cache_policy: CachePolicy[Callable[P, str | bytes]] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> (
|
||||
Callable[[Callable[P, Awaitable[T]] | Callable[P, T]], TaskFunction[P, T]]
|
||||
| TaskFunction[P, T]
|
||||
Callable[[Callable[P, Awaitable[T]] | Callable[P, T]], _TaskFunction[P, T]]
|
||||
| _TaskFunction[P, T]
|
||||
):
|
||||
"""Define a LangGraph task using the `task` decorator.
|
||||
|
||||
@@ -176,7 +181,7 @@ def task(
|
||||
await add_one.ainvoke([1, 2, 3]) # Returns [2, 3, 4]
|
||||
```
|
||||
"""
|
||||
if (retry := kwargs.get("retry", UNSET)) is not UNSET:
|
||||
if (retry := kwargs.get("retry", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`retry` is deprecated and will be removed. Please use `retry_policy` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
@@ -196,7 +201,7 @@ def task(
|
||||
def decorator(
|
||||
func: Callable[P, Awaitable[T]] | Callable[P, T],
|
||||
) -> Callable[P, concurrent.futures.Future[T]] | Callable[P, asyncio.Future[T]]:
|
||||
return TaskFunction(
|
||||
return _TaskFunction(
|
||||
func, retry_policy=retry_policies, cache_policy=cache_policy, name=name
|
||||
)
|
||||
|
||||
@@ -214,7 +219,7 @@ S = TypeVar("S")
|
||||
# In this form, the `final` attribute should play nicely with IDE autocompletion,
|
||||
# and type checking tools.
|
||||
# In addition, we'll be able to surface this information in the API Reference.
|
||||
class entrypoint:
|
||||
class entrypoint(Generic[ContextT]):
|
||||
"""Define a LangGraph workflow using the `entrypoint` decorator.
|
||||
|
||||
### Function signature
|
||||
@@ -230,10 +235,9 @@ class entrypoint:
|
||||
|
||||
| Parameter | Description |
|
||||
|------------------|----------------------------------------------------------------------------------------------------|
|
||||
| **`store`** | An instance of [BaseStore][langgraph.store.base.BaseStore]. Useful for long-term memory. |
|
||||
| **`writer`** | A [StreamWriter][langgraph.types.StreamWriter] instance for writing custom data to a stream. |
|
||||
| **`config`** | A configuration object (aka RunnableConfig) that holds run-time configuration values. |
|
||||
| **`previous`** | The previous return value for the given thread (available only when a checkpointer is provided). |
|
||||
| **`runtime`** | A Runtime object that contains information about the current run, including context, store, writer | |
|
||||
|
||||
The entrypoint decorator can be applied to sync functions or async functions.
|
||||
|
||||
@@ -253,7 +257,7 @@ class entrypoint:
|
||||
store: A generalized key-value store. Some implementations may support
|
||||
semantic search capabilities through an optional `index` configuration.
|
||||
cache: A cache to use for caching the results of the workflow.
|
||||
config_schema: Specifies the schema for the configuration object that will be
|
||||
context_schema: Specifies the schema for the context object that will be
|
||||
passed to the workflow.
|
||||
cache_policy: A cache policy to use for caching the results of the workflow.
|
||||
retry_policy: A retry policy (or list of policies) to use for the workflow in case of a failure.
|
||||
@@ -375,27 +379,36 @@ class entrypoint:
|
||||
checkpointer: BaseCheckpointSaver | None = None,
|
||||
store: BaseStore | None = None,
|
||||
cache: BaseCache | None = None,
|
||||
config_schema: type[Any] | None = None,
|
||||
context_schema: type[ContextT] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> None:
|
||||
"""Initialize the entrypoint decorator."""
|
||||
if (retry := kwargs.get("retry", UNSET)) is not UNSET:
|
||||
if (config_schema := kwargs.get("config_schema", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
if context_schema is None:
|
||||
context_schema = cast(type[ContextT], config_schema)
|
||||
|
||||
if (retry := kwargs.get("retry", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`retry` is deprecated and will be removed. Please use `retry_policy` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
stacklevel=2,
|
||||
)
|
||||
if retry_policy is None:
|
||||
retry_policy = retry # type: ignore[assignment]
|
||||
retry_policy = cast("RetryPolicy | Sequence[RetryPolicy]", retry)
|
||||
|
||||
self.checkpointer = checkpointer
|
||||
self.store = store
|
||||
self.cache = cache
|
||||
self.cache_policy = cache_policy
|
||||
self.retry_policy = retry_policy
|
||||
self.config_schema = config_schema
|
||||
self.context_schema = context_schema
|
||||
|
||||
@dataclass(**_DC_KWARGS)
|
||||
class final(Generic[R, S]):
|
||||
@@ -527,5 +540,5 @@ class entrypoint:
|
||||
cache=self.cache,
|
||||
cache_policy=self.cache_policy,
|
||||
retry_policy=self.retry_policy or (),
|
||||
config_type=self.config_schema,
|
||||
context_schema=self.context_schema, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
@@ -2,11 +2,11 @@ from langgraph.constants import END, START
|
||||
from langgraph.graph.message import MessageGraph, MessagesState, add_messages
|
||||
from langgraph.graph.state import StateGraph
|
||||
|
||||
__all__ = [
|
||||
__all__ = (
|
||||
"END",
|
||||
"START",
|
||||
"StateGraph",
|
||||
"MessageGraph",
|
||||
"add_messages",
|
||||
"MessagesState",
|
||||
]
|
||||
"MessageGraph",
|
||||
)
|
||||
|
||||
+12
-13
@@ -26,15 +26,15 @@ from langchain_core.runnables import (
|
||||
RunnableLambda,
|
||||
)
|
||||
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.pregel.write import PASSTHROUGH, ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.types import Send
|
||||
from langgraph.utils.runnable import (
|
||||
from langgraph._internal._runnable import (
|
||||
RunnableCallable,
|
||||
)
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.pregel._write import PASSTHROUGH, ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.types import Send
|
||||
|
||||
Writer = Callable[
|
||||
_Writer = Callable[
|
||||
[Sequence[Union[str, Send]], bool],
|
||||
Sequence[Union[ChannelWriteEntry, Send]],
|
||||
]
|
||||
@@ -82,7 +82,7 @@ def _get_branch_path_input_schema(
|
||||
return input
|
||||
|
||||
|
||||
class Branch(NamedTuple):
|
||||
class BranchSpec(NamedTuple):
|
||||
path: Runnable[Any, Hashable | list[Hashable]]
|
||||
ends: dict[Hashable, str] | None
|
||||
input_schema: type[Any] | None = None
|
||||
@@ -93,7 +93,7 @@ class Branch(NamedTuple):
|
||||
path: Runnable[Any, Hashable | list[Hashable]],
|
||||
path_map: dict[Hashable, str] | list[str] | None,
|
||||
infer_schema: bool = False,
|
||||
) -> Branch:
|
||||
) -> BranchSpec:
|
||||
# coerce path_map to a dictionary
|
||||
path_map_: dict[Hashable, str] | None = None
|
||||
try:
|
||||
@@ -123,7 +123,7 @@ class Branch(NamedTuple):
|
||||
|
||||
def run(
|
||||
self,
|
||||
writer: Writer,
|
||||
writer: _Writer,
|
||||
reader: Callable[[RunnableConfig], Any] | None = None,
|
||||
) -> RunnableCallable:
|
||||
return ChannelWrite.register_writer(
|
||||
@@ -134,7 +134,6 @@ class Branch(NamedTuple):
|
||||
reader=reader,
|
||||
name=None,
|
||||
trace=False,
|
||||
func_accepts_config=True,
|
||||
),
|
||||
list(
|
||||
zip_longest(
|
||||
@@ -152,7 +151,7 @@ class Branch(NamedTuple):
|
||||
config: RunnableConfig,
|
||||
*,
|
||||
reader: Callable[[RunnableConfig], Any] | None,
|
||||
writer: Writer,
|
||||
writer: _Writer,
|
||||
) -> Runnable:
|
||||
if reader:
|
||||
value = reader(config)
|
||||
@@ -175,7 +174,7 @@ class Branch(NamedTuple):
|
||||
config: RunnableConfig,
|
||||
*,
|
||||
reader: Callable[[RunnableConfig], Any] | None,
|
||||
writer: Writer,
|
||||
writer: _Writer,
|
||||
) -> Runnable:
|
||||
if reader:
|
||||
value = reader(config)
|
||||
@@ -194,7 +193,7 @@ class Branch(NamedTuple):
|
||||
|
||||
def _finish(
|
||||
self,
|
||||
writer: Writer,
|
||||
writer: _Writer,
|
||||
input: Any,
|
||||
result: Any,
|
||||
config: RunnableConfig,
|
||||
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generic, Protocol, Union
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from langgraph._internal._typing import EMPTY_SEQ
|
||||
from langgraph.runtime import Runtime
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import CachePolicy, RetryPolicy, StreamWriter
|
||||
from langgraph.typing import ContextT, NodeInputT, NodeInputT_contra
|
||||
|
||||
_DC_SLOTS = {"slots": True} if sys.version_info >= (3, 10) else {}
|
||||
|
||||
|
||||
class _Node(Protocol[NodeInputT_contra]):
|
||||
def __call__(self, state: NodeInputT_contra) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfig(Protocol[NodeInputT_contra]):
|
||||
def __call__(self, state: NodeInputT_contra, config: RunnableConfig) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithWriter(Protocol[NodeInputT_contra]):
|
||||
def __call__(self, state: NodeInputT_contra, *, writer: StreamWriter) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithStore(Protocol[NodeInputT_contra]):
|
||||
def __call__(self, state: NodeInputT_contra, *, store: BaseStore) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithWriterStore(Protocol[NodeInputT_contra]):
|
||||
def __call__(
|
||||
self, state: NodeInputT_contra, *, writer: StreamWriter, store: BaseStore
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigWriter(Protocol[NodeInputT_contra]):
|
||||
def __call__(
|
||||
self, state: NodeInputT_contra, *, config: RunnableConfig, writer: StreamWriter
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigStore(Protocol[NodeInputT_contra]):
|
||||
def __call__(
|
||||
self, state: NodeInputT_contra, *, config: RunnableConfig, store: BaseStore
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigWriterStore(Protocol[NodeInputT_contra]):
|
||||
def __call__(
|
||||
self,
|
||||
state: NodeInputT_contra,
|
||||
*,
|
||||
config: RunnableConfig,
|
||||
writer: StreamWriter,
|
||||
store: BaseStore,
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithRuntime(Protocol[NodeInputT_contra, ContextT]):
|
||||
def __call__(
|
||||
self, state: NodeInputT_contra, *, runtime: Runtime[ContextT]
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
# TODO: we probably don't want to explicitly support the config / store signatures once
|
||||
# we move to adding a context arg. Maybe what we do is we add support for kwargs with param spec
|
||||
# this is purely for typing purposes though, so can easily change in the coming weeks.
|
||||
StateNode: TypeAlias = Union[
|
||||
_Node[NodeInputT],
|
||||
_NodeWithConfig[NodeInputT],
|
||||
_NodeWithWriter[NodeInputT],
|
||||
_NodeWithStore[NodeInputT],
|
||||
_NodeWithWriterStore[NodeInputT],
|
||||
_NodeWithConfigWriter[NodeInputT],
|
||||
_NodeWithConfigStore[NodeInputT],
|
||||
_NodeWithConfigWriterStore[NodeInputT],
|
||||
_NodeWithRuntime[NodeInputT, ContextT],
|
||||
Runnable[NodeInputT, Any],
|
||||
]
|
||||
|
||||
|
||||
@dataclass(**_DC_SLOTS)
|
||||
class StateNodeSpec(Generic[NodeInputT, ContextT]):
|
||||
runnable: StateNode[NodeInputT, ContextT]
|
||||
metadata: dict[str, Any] | None
|
||||
input_schema: type[NodeInputT]
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None
|
||||
cache_policy: CachePolicy | None
|
||||
ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ
|
||||
defer: bool = False
|
||||
@@ -24,9 +24,15 @@ from langchain_core.messages import (
|
||||
)
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_SEND, NS_SEP
|
||||
from langgraph.graph.state import StateGraph
|
||||
|
||||
__all__ = (
|
||||
"add_messages",
|
||||
"MessagesState",
|
||||
"MessageGraph",
|
||||
)
|
||||
|
||||
Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation]
|
||||
|
||||
REMOVE_ALL_MESSAGES = "__remove_all__"
|
||||
@@ -314,8 +320,7 @@ def push_message(
|
||||
)
|
||||
|
||||
from langgraph.config import get_config
|
||||
from langgraph.constants import NS_SEP
|
||||
from langgraph.pregel.messages import StreamMessagesHandler
|
||||
from langgraph.pregel._messages import StreamMessagesHandler
|
||||
|
||||
config = get_config()
|
||||
message = next(x for x in convert_to_messages([message]))
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import sys
|
||||
import typing
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
@@ -14,8 +15,6 @@ from typing import (
|
||||
Callable,
|
||||
Generic,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Protocol,
|
||||
Union,
|
||||
cast,
|
||||
get_args,
|
||||
@@ -26,9 +25,22 @@ from typing import (
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
from typing_extensions import Self, TypeAlias, Unpack, is_typeddict
|
||||
from typing_extensions import Self, Unpack, is_typeddict
|
||||
|
||||
from langgraph._typing import UNSET, DeprecatedKwargs
|
||||
from langgraph._internal._constants import (
|
||||
INTERRUPT,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
TASKS,
|
||||
)
|
||||
from langgraph._internal._fields import (
|
||||
get_cached_annotated_keys,
|
||||
get_field_default,
|
||||
get_update_as_tuples,
|
||||
)
|
||||
from langgraph._internal._pydantic import create_model
|
||||
from langgraph._internal._runnable import coerce_to_runnable
|
||||
from langgraph._internal._typing import EMPTY_SEQ, MISSING, DeprecatedKwargs
|
||||
from langgraph.cache.base import BaseCache
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||
@@ -39,31 +51,22 @@ from langgraph.channels.named_barrier_value import (
|
||||
NamedBarrierValueAfterFinish,
|
||||
)
|
||||
from langgraph.checkpoint.base import Checkpoint
|
||||
from langgraph.constants import (
|
||||
EMPTY_SEQ,
|
||||
END,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
START,
|
||||
TAG_HIDDEN,
|
||||
TASKS,
|
||||
)
|
||||
from langgraph.constants import END, START, TAG_HIDDEN
|
||||
from langgraph.errors import (
|
||||
ErrorCode,
|
||||
InvalidUpdateError,
|
||||
ParentCommand,
|
||||
create_error_message,
|
||||
)
|
||||
from langgraph.graph.branch import Branch
|
||||
from langgraph.graph._branch import BranchSpec
|
||||
from langgraph.graph._node import StateNode, StateNodeSpec
|
||||
from langgraph.managed.base import (
|
||||
ManagedValueSpec,
|
||||
is_managed_value,
|
||||
)
|
||||
from langgraph.pregel import Pregel
|
||||
from langgraph.pregel.read import ChannelRead, PregelNode
|
||||
from langgraph.pregel.write import (
|
||||
from langgraph.pregel._read import ChannelRead, PregelNode
|
||||
from langgraph.pregel._write import (
|
||||
ChannelWrite,
|
||||
ChannelWriteEntry,
|
||||
ChannelWriteTupleEntry,
|
||||
@@ -76,20 +79,21 @@ from langgraph.types import (
|
||||
Command,
|
||||
RetryPolicy,
|
||||
Send,
|
||||
StreamWriter,
|
||||
)
|
||||
from langgraph.typing import InputT, OutputT, StateT, StateT_contra
|
||||
from langgraph.utils.fields import (
|
||||
get_cached_annotated_keys,
|
||||
get_field_default,
|
||||
get_update_as_tuples,
|
||||
)
|
||||
from langgraph.utils.pydantic import create_model
|
||||
from langgraph.utils.runnable import coerce_to_runnable
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV05
|
||||
from langgraph.typing import ContextT, InputT, NodeInputT, OutputT, StateT
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV05, LangGraphDeprecatedSinceV10
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
NoneType = type(None)
|
||||
else:
|
||||
from types import NoneType as NoneType
|
||||
|
||||
__all__ = ("StateGraph", "CompiledStateGraph")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CHANNEL_BRANCH_TO = "branch:to:{}"
|
||||
|
||||
|
||||
def _warn_invalid_state_schema(schema: type[Any] | Any) -> None:
|
||||
if isinstance(schema, type):
|
||||
@@ -103,89 +107,14 @@ def _warn_invalid_state_schema(schema: type[Any] | Any) -> None:
|
||||
)
|
||||
|
||||
|
||||
class _StateNode(Protocol[StateT_contra]):
|
||||
def __call__(self, state: StateT_contra) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfig(Protocol[StateT_contra]):
|
||||
def __call__(self, state: StateT_contra, config: RunnableConfig) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithWriter(Protocol[StateT_contra]):
|
||||
def __call__(self, state: StateT_contra, *, writer: StreamWriter) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithStore(Protocol[StateT_contra]):
|
||||
def __call__(self, state: StateT_contra, *, store: BaseStore) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithWriterStore(Protocol[StateT_contra]):
|
||||
def __call__(
|
||||
self, state: StateT_contra, *, writer: StreamWriter, store: BaseStore
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigWriter(Protocol[StateT_contra]):
|
||||
def __call__(
|
||||
self, state: StateT_contra, *, config: RunnableConfig, writer: StreamWriter
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigStore(Protocol[StateT_contra]):
|
||||
def __call__(
|
||||
self, state: StateT_contra, *, config: RunnableConfig, store: BaseStore
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
class _NodeWithConfigWriterStore(Protocol[StateT_contra]):
|
||||
def __call__(
|
||||
self,
|
||||
state: StateT_contra,
|
||||
*,
|
||||
config: RunnableConfig,
|
||||
writer: StreamWriter,
|
||||
store: BaseStore,
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
# TODO: we probably don't want to explicitly support the config / store signatures once
|
||||
# we move to adding a context arg. Maybe what we do is we add support for kwargs with param spec
|
||||
# this is purely for typing purposes though, so can easily change in the coming weeks.
|
||||
StateNode: TypeAlias = Union[
|
||||
_StateNode[StateT_contra],
|
||||
_NodeWithConfig[StateT_contra],
|
||||
_NodeWithWriter[StateT_contra],
|
||||
_NodeWithStore[StateT_contra],
|
||||
_NodeWithWriterStore[StateT_contra],
|
||||
_NodeWithConfigWriter[StateT_contra],
|
||||
_NodeWithConfigStore[StateT_contra],
|
||||
_NodeWithConfigWriterStore[StateT_contra],
|
||||
Runnable[StateT_contra, Any],
|
||||
]
|
||||
|
||||
|
||||
def _get_node_name(node: StateNode) -> str:
|
||||
def _get_node_name(node: StateNode[Any, ContextT]) -> str:
|
||||
try:
|
||||
return getattr(node, "__name__", node.__class__.__name__)
|
||||
except AttributeError:
|
||||
raise TypeError(f"Unsupported node type: {type(node)}")
|
||||
|
||||
|
||||
class StateNodeSpec(NamedTuple):
|
||||
# TODO: rename this callable, also move away from NamedTuple so that we can use
|
||||
# a generic StateNode, so maybe a dataclass
|
||||
runnable: StateNode
|
||||
metadata: dict[str, Any] | None
|
||||
# TODO: rename to input_schema, though we really just want to modify this structure to
|
||||
# be a dataclass
|
||||
input: type[Any]
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None
|
||||
cache_policy: CachePolicy | None
|
||||
ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ
|
||||
defer: bool = False
|
||||
|
||||
|
||||
class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
"""A graph whose nodes communicate by reading and writing to a shared state.
|
||||
The signature of each node is State -> Partial<State>.
|
||||
|
||||
@@ -195,8 +124,10 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
|
||||
Args:
|
||||
state_schema: The schema class that defines the state.
|
||||
config_schema: The schema class that defines the configuration.
|
||||
Use this to expose configurable parameters in your API.
|
||||
context_schema: The schema class that defines the runtime context.
|
||||
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.
|
||||
|
||||
Example:
|
||||
```python
|
||||
@@ -204,6 +135,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
from typing_extensions import Annotated, TypedDict
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
def reducer(a: list, b: int | None) -> list:
|
||||
if b is not None:
|
||||
@@ -213,13 +145,13 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
class State(TypedDict):
|
||||
x: Annotated[list, reducer]
|
||||
|
||||
class ConfigSchema(TypedDict):
|
||||
class Context(TypedDict):
|
||||
r: float
|
||||
|
||||
graph = StateGraph(State, config_schema=ConfigSchema)
|
||||
graph = StateGraph(state_schema=State, context_schema=Context)
|
||||
|
||||
def node(state: State, config: RunnableConfig) -> dict:
|
||||
r = config["configurable"].get("r", 1.0)
|
||||
def node(state: State, runtime: Runtime[Context]) -> dict:
|
||||
r = runtie.context.get("r", 1.0)
|
||||
x = state["x"][-1]
|
||||
next_value = x * r * (1 - x)
|
||||
return {"x": next_value}
|
||||
@@ -229,17 +161,14 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
graph.set_finish_point("A")
|
||||
compiled = graph.compile()
|
||||
|
||||
print(compiled.config_specs)
|
||||
# [ConfigurableFieldSpec(id='r', annotation=<class 'float'>, name=None, description=None, default=None, is_shared=False, dependencies=None)]
|
||||
|
||||
step1 = compiled.invoke({"x": 0.5}, {"configurable": {"r": 3.0}})
|
||||
step1 = compiled.invoke({"x": 0.5}, context={"r": 3.0})
|
||||
# {'x': [0.5, 0.75]}
|
||||
```
|
||||
"""
|
||||
|
||||
edges: set[tuple[str, str]]
|
||||
nodes: dict[str, StateNodeSpec]
|
||||
branches: defaultdict[str, dict[str, Branch]]
|
||||
nodes: dict[str, StateNodeSpec[Any, ContextT]]
|
||||
branches: defaultdict[str, dict[str, BranchSpec]]
|
||||
channels: dict[str, BaseChannel]
|
||||
managed: dict[str, ManagedValueSpec]
|
||||
schemas: dict[type[Any], dict[str, BaseChannel | ManagedValueSpec]]
|
||||
@@ -247,35 +176,45 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
|
||||
compiled: bool
|
||||
state_schema: type[StateT]
|
||||
context_schema: type[ContextT] | None
|
||||
input_schema: type[InputT]
|
||||
output_schema: type[OutputT]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
state_schema: type[StateT],
|
||||
config_schema: type[Any] | None = None,
|
||||
context_schema: type[ContextT] | None = None,
|
||||
*,
|
||||
input_schema: type[InputT] | None = None,
|
||||
output_schema: type[OutputT] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> None:
|
||||
if (input_ := kwargs.get("input", UNSET)) is not UNSET:
|
||||
if (config_schema := kwargs.get("config_schema", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
|
||||
category=LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
if context_schema is None:
|
||||
context_schema = cast(type[ContextT], config_schema)
|
||||
|
||||
if (input_ := kwargs.get("input", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`input` is deprecated and will be removed. Please use `input_schema` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
stacklevel=2,
|
||||
)
|
||||
if input_schema is None:
|
||||
input_schema = cast(Union[type[InputT], None], input_)
|
||||
input_schema = cast(type[InputT], input_)
|
||||
|
||||
if (output := kwargs.get("output", UNSET)) is not UNSET:
|
||||
if (output := kwargs.get("output", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`output` is deprecated and will be removed. Please use `output_schema` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
stacklevel=2,
|
||||
)
|
||||
if output_schema is None:
|
||||
output_schema = cast(Union[type[OutputT], None], output)
|
||||
output_schema = cast(type[OutputT], output)
|
||||
|
||||
self.nodes = {}
|
||||
self.edges = set()
|
||||
@@ -289,7 +228,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
self.state_schema = state_schema
|
||||
self.input_schema = cast(type[InputT], input_schema or state_schema)
|
||||
self.output_schema = cast(type[OutputT], output_schema or state_schema)
|
||||
self.config_schema = config_schema
|
||||
self.context_schema = context_schema
|
||||
|
||||
self._add_schema(self.state_schema)
|
||||
self._add_schema(self.input_schema, allow_managed=False)
|
||||
@@ -336,17 +275,35 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
@overload
|
||||
def add_node(
|
||||
self,
|
||||
node: StateNode[StateT],
|
||||
node: StateNode[NodeInputT, ContextT],
|
||||
*,
|
||||
defer: bool = False,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
input_schema: type[Any] | None = None,
|
||||
input_schema: None = None,
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph.
|
||||
"""Add a new node to the state graph, input schema is inferred as the state schema.
|
||||
Will take the name of the function/runnable as the node name.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def add_node(
|
||||
self,
|
||||
node: StateNode[NodeInputT, ContextT],
|
||||
*,
|
||||
defer: bool = False,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
input_schema: type[NodeInputT],
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is specified.
|
||||
Will take the name of the function/runnable as the node name.
|
||||
"""
|
||||
...
|
||||
@@ -355,27 +312,44 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
def add_node(
|
||||
self,
|
||||
node: str,
|
||||
action: StateNode[StateT],
|
||||
action: StateNode[NodeInputT, ContextT],
|
||||
*,
|
||||
defer: bool = False,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
input_schema: type[Any] | None = None,
|
||||
input_schema: None = None,
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph."""
|
||||
"""Add a new node to the state graph, input schema is inferred as the state schema."""
|
||||
...
|
||||
|
||||
@overload
|
||||
def add_node(
|
||||
self,
|
||||
node: str | StateNode[NodeInputT, ContextT],
|
||||
action: StateNode[NodeInputT, ContextT] | None = None,
|
||||
*,
|
||||
defer: bool = False,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
input_schema: type[NodeInputT],
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is specified."""
|
||||
...
|
||||
|
||||
def add_node(
|
||||
self,
|
||||
node: str | StateNode[StateT],
|
||||
action: StateNode[StateT] | None = None,
|
||||
node: str | StateNode[NodeInputT, ContextT],
|
||||
action: StateNode[NodeInputT, ContextT] | None = None,
|
||||
*,
|
||||
defer: bool = False,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
input_schema: type[Any] | None = None,
|
||||
input_schema: type[NodeInputT] | None = None,
|
||||
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
|
||||
cache_policy: CachePolicy | None = None,
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
@@ -434,7 +408,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
Returns:
|
||||
Self: The instance of the state graph, allowing for method chaining.
|
||||
"""
|
||||
if (retry := kwargs.get("retry", UNSET)) is not UNSET:
|
||||
if (retry := kwargs.get("retry", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`retry` is deprecated and will be removed. Please use `retry_policy` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
@@ -442,13 +416,13 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
if retry_policy is None:
|
||||
retry_policy = retry # type: ignore[assignment]
|
||||
|
||||
if (input_ := kwargs.get("input", UNSET)) is not UNSET:
|
||||
if (input_ := kwargs.get("input", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
"`input` is deprecated and will be removed. Please use `input_schema` instead.",
|
||||
category=LangGraphDeprecatedSinceV05,
|
||||
)
|
||||
if input_schema is None:
|
||||
input_schema = cast(Union[type[InputT], None], input_)
|
||||
input_schema = cast(Union[type[NodeInputT], None], input_)
|
||||
|
||||
if not isinstance(node, str):
|
||||
action = node
|
||||
@@ -485,6 +459,8 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
f"'{character}' is a reserved character and is not allowed in the node names."
|
||||
)
|
||||
|
||||
inferred_input_schema = None
|
||||
|
||||
ends: tuple[str, ...] | dict[str, str] = EMPTY_SEQ
|
||||
try:
|
||||
if (
|
||||
@@ -505,7 +481,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
)
|
||||
if input_hint := hints.get(first_parameter_name):
|
||||
if isinstance(input_hint, type) and get_type_hints(input_hint):
|
||||
input_schema = input_hint
|
||||
inferred_input_schema = input_hint
|
||||
if rtn := hints.get("return"):
|
||||
# Handle Union types
|
||||
rtn_origin = get_origin(rtn)
|
||||
@@ -533,17 +509,41 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
if destinations is not None:
|
||||
ends = destinations
|
||||
|
||||
if input_schema is not None:
|
||||
self.nodes[node] = StateNodeSpec[NodeInputT, ContextT](
|
||||
coerce_to_runnable(action, name=node, trace=False), # type: ignore[arg-type]
|
||||
metadata,
|
||||
input_schema=input_schema,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
ends=ends,
|
||||
defer=defer,
|
||||
)
|
||||
elif inferred_input_schema is not None:
|
||||
self.nodes[node] = StateNodeSpec(
|
||||
coerce_to_runnable(action, name=node, trace=False), # type: ignore[arg-type]
|
||||
metadata,
|
||||
input_schema=inferred_input_schema,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
ends=ends,
|
||||
defer=defer,
|
||||
)
|
||||
else:
|
||||
self.nodes[node] = StateNodeSpec[StateT, ContextT](
|
||||
coerce_to_runnable(action, name=node, trace=False), # type: ignore[arg-type]
|
||||
metadata,
|
||||
input_schema=self.state_schema,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
ends=ends,
|
||||
defer=defer,
|
||||
)
|
||||
|
||||
input_schema = input_schema or inferred_input_schema
|
||||
if input_schema is not None:
|
||||
self._add_schema(input_schema)
|
||||
self.nodes[node] = StateNodeSpec(
|
||||
coerce_to_runnable(action, name=node, trace=False),
|
||||
metadata,
|
||||
input=input_schema or self.state_schema,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
ends=ends,
|
||||
defer=defer,
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
def add_edge(self, start_key: str | list[str], end_key: str) -> Self:
|
||||
@@ -641,14 +641,17 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
f"Branch with name `{path.name}` already exists for node `{source}`"
|
||||
)
|
||||
# save it
|
||||
self.branches[source][name] = Branch.from_path(path, path_map, True)
|
||||
self.branches[source][name] = BranchSpec.from_path(path, path_map, True)
|
||||
if schema := self.branches[source][name].input_schema:
|
||||
self._add_schema(schema)
|
||||
return self
|
||||
|
||||
def add_sequence(
|
||||
self,
|
||||
nodes: Sequence[StateNode[StateT] | tuple[str, StateNode[StateT]]],
|
||||
nodes: Sequence[
|
||||
StateNode[NodeInputT, ContextT]
|
||||
| tuple[str, StateNode[NodeInputT, ContextT]]
|
||||
],
|
||||
) -> Self:
|
||||
"""Add a sequence of nodes that will be executed in the provided order.
|
||||
|
||||
@@ -794,7 +797,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
interrupt_after: All | list[str] | None = None,
|
||||
debug: bool = False,
|
||||
name: str | None = None,
|
||||
) -> CompiledStateGraph[StateT, InputT, OutputT]:
|
||||
) -> CompiledStateGraph[StateT, ContextT, InputT, OutputT]:
|
||||
"""Compiles the state graph into a `CompiledStateGraph` object.
|
||||
|
||||
The compiled graph implements the `Runnable` interface and can be invoked,
|
||||
@@ -846,10 +849,10 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
]
|
||||
)
|
||||
|
||||
compiled = CompiledStateGraph[StateT, InputT, OutputT](
|
||||
compiled = CompiledStateGraph[StateT, ContextT, InputT, OutputT](
|
||||
builder=self,
|
||||
schema_to_mapper={},
|
||||
config_type=self.config_schema,
|
||||
context_schema=self.context_schema,
|
||||
nodes={},
|
||||
channels={
|
||||
**self.channels,
|
||||
@@ -888,15 +891,16 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
|
||||
|
||||
|
||||
class CompiledStateGraph(
|
||||
Pregel[StateT, InputT, OutputT], Generic[StateT, InputT, OutputT]
|
||||
Pregel[StateT, ContextT, InputT, OutputT],
|
||||
Generic[StateT, ContextT, InputT, OutputT],
|
||||
):
|
||||
builder: StateGraph[StateT, InputT, OutputT]
|
||||
builder: StateGraph[StateT, ContextT, InputT, OutputT]
|
||||
schema_to_mapper: dict[type[Any], Callable[[Any], Any] | None]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
builder: StateGraph[StateT, InputT, OutputT],
|
||||
builder: StateGraph[StateT, ContextT, InputT, OutputT],
|
||||
schema_to_mapper: dict[type[Any], Callable[[Any], Any] | None],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -924,7 +928,7 @@ class CompiledStateGraph(
|
||||
name=self.get_name("Output"),
|
||||
)
|
||||
|
||||
def attach_node(self, key: str, node: StateNodeSpec | None) -> None:
|
||||
def attach_node(self, key: str, node: StateNodeSpec[Any, ContextT] | None) -> None:
|
||||
if key == START:
|
||||
output_keys = [
|
||||
k
|
||||
@@ -996,7 +1000,7 @@ class CompiledStateGraph(
|
||||
writers=[ChannelWrite(write_entries)],
|
||||
)
|
||||
elif node is not None:
|
||||
input_schema = node.input if node else self.builder.state_schema
|
||||
input_schema = node.input_schema if node else self.builder.state_schema
|
||||
input_channels = list(self.builder.schemas[input_schema])
|
||||
is_single_input = len(input_channels) == 1 and "__root__" in input_channels
|
||||
if input_schema in self.schema_to_mapper:
|
||||
@@ -1005,7 +1009,7 @@ class CompiledStateGraph(
|
||||
mapper = _pick_mapper(input_channels, input_schema)
|
||||
self.schema_to_mapper[input_schema] = mapper
|
||||
|
||||
branch_channel = CHANNEL_BRANCH_TO.format(key)
|
||||
branch_channel = _CHANNEL_BRANCH_TO.format(key)
|
||||
self.channels[branch_channel] = (
|
||||
LastValueAfterFinish(Any)
|
||||
if node.defer
|
||||
@@ -1033,7 +1037,7 @@ class CompiledStateGraph(
|
||||
if end != END:
|
||||
self.nodes[starts].writers.append(
|
||||
ChannelWrite(
|
||||
(ChannelWriteEntry(CHANNEL_BRANCH_TO.format(end), None),)
|
||||
(ChannelWriteEntry(_CHANNEL_BRANCH_TO.format(end), None),)
|
||||
)
|
||||
)
|
||||
elif end != END:
|
||||
@@ -1054,7 +1058,7 @@ class CompiledStateGraph(
|
||||
)
|
||||
|
||||
def attach_branch(
|
||||
self, start: str, name: str, branch: Branch, *, with_reader: bool = True
|
||||
self, start: str, name: str, branch: BranchSpec, *, with_reader: bool = True
|
||||
) -> None:
|
||||
def get_writes(
|
||||
packets: Sequence[str | Send], static: bool = False
|
||||
@@ -1062,7 +1066,7 @@ class CompiledStateGraph(
|
||||
writes = [
|
||||
(
|
||||
ChannelWriteEntry(
|
||||
p if p == END else CHANNEL_BRANCH_TO.format(p), None
|
||||
p if p == END else _CHANNEL_BRANCH_TO.format(p), None
|
||||
)
|
||||
if not isinstance(p, Send)
|
||||
else p
|
||||
@@ -1077,7 +1081,7 @@ class CompiledStateGraph(
|
||||
if with_reader:
|
||||
# get schema
|
||||
schema = branch.input_schema or (
|
||||
self.builder.nodes[start].input
|
||||
self.builder.nodes[start].input_schema
|
||||
if start in self.builder.nodes
|
||||
else self.builder.state_schema
|
||||
)
|
||||
@@ -1247,7 +1251,7 @@ def _control_branch(value: Any) -> Sequence[tuple[str, Any]]:
|
||||
elif isinstance(go, str) and go != END:
|
||||
# END is a special case, it's not actually a node in a practical sense
|
||||
# but rather a special terminal node that we don't need to branch to
|
||||
rtn.append((CHANNEL_BRANCH_TO.format(go), None))
|
||||
rtn.append((_CHANNEL_BRANCH_TO.format(go), None))
|
||||
return rtn
|
||||
|
||||
|
||||
@@ -1256,12 +1260,12 @@ def _control_static(
|
||||
) -> Sequence[tuple[str, Any, str | None]]:
|
||||
if isinstance(ends, dict):
|
||||
return [
|
||||
(k if k == END else CHANNEL_BRANCH_TO.format(k), None, label)
|
||||
(k if k == END else _CHANNEL_BRANCH_TO.format(k), None, label)
|
||||
for k, label in ends.items()
|
||||
]
|
||||
else:
|
||||
return [
|
||||
(e if e == END else CHANNEL_BRANCH_TO.format(e), None, None) for e in ends
|
||||
(e if e == END else _CHANNEL_BRANCH_TO.format(e), None, None) for e in ends
|
||||
]
|
||||
|
||||
|
||||
@@ -1420,6 +1424,3 @@ def _get_json_schema(
|
||||
if k in channels and isinstance(channels[k], BaseChannel)
|
||||
},
|
||||
).model_json_schema()
|
||||
|
||||
|
||||
CHANNEL_BRANCH_TO = "branch:to:{}"
|
||||
|
||||
@@ -6,8 +6,17 @@ 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
|
||||
from langgraph.config import get_config, get_stream_writer
|
||||
from langgraph.constants import CONF
|
||||
|
||||
__all__ = (
|
||||
"UIMessage",
|
||||
"RemoveUIMessage",
|
||||
"AnyUIMessage",
|
||||
"push_ui_message",
|
||||
"delete_ui_message",
|
||||
"ui_message_reducer",
|
||||
)
|
||||
|
||||
|
||||
class UIMessage(TypedDict):
|
||||
@@ -87,6 +96,8 @@ def push_ui_message(
|
||||
)
|
||||
|
||||
"""
|
||||
from langgraph._internal._constants import CONFIG_KEY_SEND
|
||||
|
||||
writer = get_stream_writer()
|
||||
config = get_config()
|
||||
|
||||
@@ -139,6 +150,8 @@ def delete_ui_message(id: str, *, state_key: str = "ui") -> RemoveUIMessage:
|
||||
delete_ui_message("message-123")
|
||||
|
||||
"""
|
||||
from langgraph._internal._constants import CONFIG_KEY_SEND
|
||||
|
||||
writer = get_stream_writer()
|
||||
config = get_config()
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from langgraph.managed.is_last_step import IsLastStep, RemainingSteps
|
||||
|
||||
__all__ = ["IsLastStep", "RemainingSteps"]
|
||||
__all__ = ("IsLastStep", "RemainingSteps")
|
||||
|
||||
@@ -8,11 +8,13 @@ from typing import (
|
||||
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
from langgraph.types import PregelScratchpad
|
||||
from langgraph.pregel._scratchpad import PregelScratchpad
|
||||
|
||||
V = TypeVar("V")
|
||||
U = TypeVar("U")
|
||||
|
||||
__all__ = ("ManagedValueSpec", "ManagedValueMapping")
|
||||
|
||||
|
||||
class ManagedValue(ABC, Generic[V]):
|
||||
@staticmethod
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from typing import Annotated
|
||||
|
||||
from langgraph.managed.base import ManagedValue
|
||||
from langgraph.types import PregelScratchpad
|
||||
from langgraph.pregel._scratchpad import PregelScratchpad
|
||||
|
||||
__all__ = ("IsLastStep", "RemainingStepsManager")
|
||||
|
||||
|
||||
class IsLastStepManager(ManagedValue[bool]):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+41
-33
@@ -25,33 +25,22 @@ from langchain_core.callbacks.manager import AsyncParentRunManager, ParentRunMan
|
||||
from langchain_core.runnables.config import RunnableConfig
|
||||
from xxhash import xxh3_128_hexdigest
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.checkpoint.base import (
|
||||
BaseCheckpointSaver,
|
||||
ChannelVersions,
|
||||
Checkpoint,
|
||||
PendingWrite,
|
||||
V,
|
||||
)
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._config import merge_configs, patch_config
|
||||
from langgraph._internal._constants import (
|
||||
CACHE_NS_WRITES,
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
CONFIG_KEY_PREVIOUS,
|
||||
CONFIG_KEY_READ,
|
||||
CONFIG_KEY_RESUME_MAP,
|
||||
CONFIG_KEY_RUNTIME,
|
||||
CONFIG_KEY_SCRATCHPAD,
|
||||
CONFIG_KEY_SEND,
|
||||
CONFIG_KEY_STORE,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
EMPTY_SEQ,
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NO_WRITES,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
@@ -62,26 +51,36 @@ from langgraph.constants import (
|
||||
RESERVED,
|
||||
RESUME,
|
||||
RETURN,
|
||||
TAG_HIDDEN,
|
||||
TASKS,
|
||||
Send,
|
||||
)
|
||||
from langgraph._internal._typing import EMPTY_SEQ, MISSING
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.checkpoint.base import (
|
||||
BaseCheckpointSaver,
|
||||
ChannelVersions,
|
||||
Checkpoint,
|
||||
PendingWrite,
|
||||
V,
|
||||
)
|
||||
from langgraph.constants import TAG_HIDDEN
|
||||
from langgraph.managed.base import ManagedValueMapping
|
||||
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._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 (
|
||||
All,
|
||||
CacheKey,
|
||||
CachePolicy,
|
||||
PregelExecutableTask,
|
||||
PregelScratchpad,
|
||||
PregelTask,
|
||||
RetryPolicy,
|
||||
Send,
|
||||
)
|
||||
from langgraph.utils.config import merge_configs, patch_config
|
||||
|
||||
GetNextVersion = Callable[[Optional[V], None], V]
|
||||
SUPPORTS_EXC_NOTES = sys.version_info >= (3, 11)
|
||||
@@ -583,6 +582,10 @@ def prepare_single_task(
|
||||
step,
|
||||
stop,
|
||||
)
|
||||
runtime = cast(
|
||||
Runtime, configurable.get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
|
||||
)
|
||||
runtime = runtime.override(store=store)
|
||||
return PregelExecutableTask(
|
||||
name,
|
||||
call.input,
|
||||
@@ -604,7 +607,6 @@ def prepare_single_task(
|
||||
managed,
|
||||
PregelTaskWrites(task_path, name, writes, triggers),
|
||||
),
|
||||
CONFIG_KEY_STORE: (store or configurable.get(CONFIG_KEY_STORE)),
|
||||
CONFIG_KEY_CHECKPOINTER: (
|
||||
checkpointer or configurable.get(CONFIG_KEY_CHECKPOINTER)
|
||||
),
|
||||
@@ -615,6 +617,7 @@ def prepare_single_task(
|
||||
CONFIG_KEY_CHECKPOINT_ID: None,
|
||||
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
|
||||
CONFIG_KEY_SCRATCHPAD: scratchpad,
|
||||
CONFIG_KEY_RUNTIME: runtime,
|
||||
},
|
||||
),
|
||||
triggers,
|
||||
@@ -709,6 +712,12 @@ def prepare_single_task(
|
||||
step,
|
||||
stop,
|
||||
)
|
||||
runtime = cast(
|
||||
Runtime, configurable.get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
|
||||
)
|
||||
runtime = runtime.override(
|
||||
store=store, previous=checkpoint["channel_values"].get(PREVIOUS, None)
|
||||
)
|
||||
return PregelExecutableTask(
|
||||
packet.node,
|
||||
packet.arg,
|
||||
@@ -731,7 +740,6 @@ def prepare_single_task(
|
||||
managed,
|
||||
PregelTaskWrites(task_path, packet.node, writes, triggers),
|
||||
),
|
||||
CONFIG_KEY_STORE: (store or configurable.get(CONFIG_KEY_STORE)),
|
||||
CONFIG_KEY_CHECKPOINTER: (
|
||||
checkpointer or configurable.get(CONFIG_KEY_CHECKPOINTER)
|
||||
),
|
||||
@@ -742,9 +750,7 @@ def prepare_single_task(
|
||||
CONFIG_KEY_CHECKPOINT_ID: None,
|
||||
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
|
||||
CONFIG_KEY_SCRATCHPAD: scratchpad,
|
||||
CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get(
|
||||
PREVIOUS, None
|
||||
),
|
||||
CONFIG_KEY_RUNTIME: runtime,
|
||||
},
|
||||
),
|
||||
triggers,
|
||||
@@ -846,6 +852,13 @@ def prepare_single_task(
|
||||
)
|
||||
else:
|
||||
cache_key = None
|
||||
runtime = cast(
|
||||
Runtime, configurable.get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME)
|
||||
)
|
||||
runtime = runtime.override(
|
||||
previous=checkpoint["channel_values"].get(PREVIOUS, None),
|
||||
store=store,
|
||||
)
|
||||
return PregelExecutableTask(
|
||||
name,
|
||||
val,
|
||||
@@ -877,9 +890,6 @@ def prepare_single_task(
|
||||
triggers,
|
||||
),
|
||||
),
|
||||
CONFIG_KEY_STORE: (
|
||||
store or configurable.get(CONFIG_KEY_STORE)
|
||||
),
|
||||
CONFIG_KEY_CHECKPOINTER: (
|
||||
checkpointer
|
||||
or configurable.get(CONFIG_KEY_CHECKPOINTER)
|
||||
@@ -891,9 +901,7 @@ def prepare_single_task(
|
||||
CONFIG_KEY_CHECKPOINT_ID: None,
|
||||
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
|
||||
CONFIG_KEY_SCRATCHPAD: scratchpad,
|
||||
CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get(
|
||||
PREVIOUS, None
|
||||
),
|
||||
CONFIG_KEY_RUNTIME: runtime,
|
||||
},
|
||||
),
|
||||
triggers,
|
||||
@@ -13,16 +13,16 @@ from typing import Any, Callable, Generic, TypeVar, cast
|
||||
from langchain_core.runnables import Runnable
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_CALL, RETURN
|
||||
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.types import CachePolicy, RetryPolicy
|
||||
from langgraph.utils.config import get_config
|
||||
from langgraph.utils.runnable import (
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_CALL, RETURN
|
||||
from langgraph._internal._runnable import (
|
||||
RunnableCallable,
|
||||
RunnableSeq,
|
||||
is_async_callable,
|
||||
run_in_executor,
|
||||
)
|
||||
from langgraph.config import get_config
|
||||
from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.types import CachePolicy, RetryPolicy
|
||||
|
||||
##
|
||||
# Utilities borrowed from cloudpickle.
|
||||
@@ -78,8 +78,8 @@ def _whichmodule(obj: Any, name: str) -> str | None:
|
||||
|
||||
def identifier(obj: Any, name: str | None = None) -> str | None:
|
||||
"""Return the module and name of an object."""
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.utils.runnable import RunnableCallable, RunnableSeq
|
||||
from langgraph._internal._runnable import RunnableCallable, RunnableSeq
|
||||
from langgraph.pregel._read import PregelNode
|
||||
|
||||
if isinstance(obj, PregelNode):
|
||||
obj = obj.bound
|
||||
+1
-1
@@ -3,10 +3,10 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import Checkpoint
|
||||
from langgraph.checkpoint.base.id import uuid6
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.managed.base import ManagedValueMapping, ManagedValueSpec
|
||||
|
||||
LATEST_VERSION = 4
|
||||
@@ -7,20 +7,21 @@ from typing import Any, cast
|
||||
from langchain_core.runnables.config import RunnableConfig
|
||||
from langchain_core.runnables.graph import Graph, Node
|
||||
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_SEND, INPUT
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND, END, INPUT, START
|
||||
from langgraph.constants import END, START
|
||||
from langgraph.managed.base import ManagedValueSpec
|
||||
from langgraph.pregel.algo import (
|
||||
from langgraph.pregel._algo import (
|
||||
PregelTaskWrites,
|
||||
apply_writes,
|
||||
increment,
|
||||
prepare_next_tasks,
|
||||
)
|
||||
from langgraph.pregel.checkpoint import channels_from_checkpoint, empty_checkpoint
|
||||
from langgraph.pregel.io import map_input
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.pregel.write import ChannelWrite
|
||||
from langgraph.pregel._checkpoint import channels_from_checkpoint, empty_checkpoint
|
||||
from langgraph.pregel._io import map_input
|
||||
from langgraph.pregel._read import PregelNode
|
||||
from langgraph.pregel._write import ChannelWrite
|
||||
from langgraph.types import All, Checkpointer
|
||||
|
||||
|
||||
+1
-1
@@ -18,8 +18,8 @@ from langchain_core.runnables import RunnableConfig
|
||||
from langchain_core.runnables.config import get_executor_for_config
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
from langgraph._internal._future import CONTEXT_NOT_SUPPORTED, run_coroutine_threadsafe
|
||||
from langgraph.errors import GraphBubbleUp
|
||||
from langgraph.utils.future import CONTEXT_NOT_SUPPORTED, run_coroutine_threadsafe
|
||||
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T")
|
||||
@@ -4,21 +4,19 @@ from collections import Counter
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from typing import Any, Literal
|
||||
|
||||
from langgraph.channels.base import BaseChannel, EmptyChannelError
|
||||
from langgraph.constants import (
|
||||
EMPTY_SEQ,
|
||||
from langgraph._internal._constants import (
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NULL_TASK_ID,
|
||||
RESUME,
|
||||
RETURN,
|
||||
START,
|
||||
TAG_HIDDEN,
|
||||
TASKS,
|
||||
)
|
||||
from langgraph._internal._typing import EMPTY_SEQ, MISSING
|
||||
from langgraph.channels.base import BaseChannel, EmptyChannelError
|
||||
from langgraph.constants import START, TAG_HIDDEN
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.pregel.log import logger
|
||||
from langgraph.pregel._log import logger
|
||||
from langgraph.types import Command, PregelExecutableTask, Send
|
||||
|
||||
|
||||
+47
-48
@@ -27,6 +27,28 @@ from langchain_core.callbacks import AsyncParentRunManager, ParentRunManager
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from typing_extensions import ParamSpec, Self
|
||||
|
||||
from langgraph._internal._config import patch_configurable
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUME_MAP,
|
||||
CONFIG_KEY_RESUMING,
|
||||
CONFIG_KEY_SCRATCHPAD,
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
CONFIG_KEY_THREAD_ID,
|
||||
ERROR,
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
NULL_TASK_ID,
|
||||
PUSH,
|
||||
RESUME,
|
||||
)
|
||||
from langgraph._internal._typing import EMPTY_SEQ, MISSING
|
||||
from langgraph.cache.base import BaseCache
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import (
|
||||
@@ -38,29 +60,7 @@ from langgraph.checkpoint.base import (
|
||||
CheckpointTuple,
|
||||
PendingWrite,
|
||||
)
|
||||
from langgraph.constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUME_MAP,
|
||||
CONFIG_KEY_RESUMING,
|
||||
CONFIG_KEY_SCRATCHPAD,
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
CONFIG_KEY_THREAD_ID,
|
||||
EMPTY_SEQ,
|
||||
ERROR,
|
||||
INPUT,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
NULL_TASK_ID,
|
||||
PUSH,
|
||||
RESUME,
|
||||
TAG_HIDDEN,
|
||||
)
|
||||
from langgraph.constants import TAG_HIDDEN
|
||||
from langgraph.errors import (
|
||||
EmptyInputError,
|
||||
GraphInterrupt,
|
||||
@@ -69,7 +69,7 @@ from langgraph.managed.base import (
|
||||
ManagedValueMapping,
|
||||
ManagedValueSpec,
|
||||
)
|
||||
from langgraph.pregel.algo import (
|
||||
from langgraph.pregel._algo import (
|
||||
Call,
|
||||
GetNextVersion,
|
||||
PregelTaskWrites,
|
||||
@@ -81,44 +81,43 @@ from langgraph.pregel.algo import (
|
||||
should_interrupt,
|
||||
task_path_str,
|
||||
)
|
||||
from langgraph.pregel.checkpoint import (
|
||||
from langgraph.pregel._checkpoint import (
|
||||
channels_from_checkpoint,
|
||||
copy_checkpoint,
|
||||
create_checkpoint,
|
||||
empty_checkpoint,
|
||||
)
|
||||
from langgraph.pregel.debug import (
|
||||
map_debug_checkpoint,
|
||||
map_debug_task_results,
|
||||
map_debug_tasks,
|
||||
)
|
||||
from langgraph.pregel.executor import (
|
||||
from langgraph.pregel._executor import (
|
||||
AsyncBackgroundExecutor,
|
||||
BackgroundExecutor,
|
||||
Submit,
|
||||
)
|
||||
from langgraph.pregel.io import (
|
||||
from langgraph.pregel._io import (
|
||||
map_command,
|
||||
map_input,
|
||||
map_output_updates,
|
||||
map_output_values,
|
||||
read_channels,
|
||||
)
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.pregel.utils import get_new_channel_versions, is_xxh3_128_hexdigest
|
||||
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,
|
||||
map_debug_task_results,
|
||||
map_debug_tasks,
|
||||
)
|
||||
from langgraph.pregel.protocol import StreamChunk, StreamProtocol
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import (
|
||||
All,
|
||||
CachePolicy,
|
||||
Command,
|
||||
Durability,
|
||||
PregelExecutableTask,
|
||||
PregelScratchpad,
|
||||
RetryPolicy,
|
||||
StreamChunk,
|
||||
StreamMode,
|
||||
StreamProtocol,
|
||||
)
|
||||
from langgraph.utils.config import patch_configurable
|
||||
|
||||
V = TypeVar("V")
|
||||
P = ParamSpec("P")
|
||||
@@ -156,7 +155,7 @@ class PregelLoop:
|
||||
manager: None | AsyncParentRunManager | ParentRunManager
|
||||
interrupt_after: All | Sequence[str]
|
||||
interrupt_before: All | Sequence[str]
|
||||
checkpoint_during: bool
|
||||
durability: Durability
|
||||
retry_policy: Sequence[RetryPolicy]
|
||||
cache_policy: CachePolicy | None
|
||||
|
||||
@@ -218,13 +217,13 @@ class PregelLoop:
|
||||
output_keys: str | Sequence[str],
|
||||
stream_keys: str | Sequence[str],
|
||||
trigger_to_nodes: Mapping[str, Sequence[str]],
|
||||
durability: Durability,
|
||||
interrupt_after: All | Sequence[str] = EMPTY_SEQ,
|
||||
interrupt_before: All | Sequence[str] = EMPTY_SEQ,
|
||||
manager: None | AsyncParentRunManager | ParentRunManager = None,
|
||||
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
|
||||
retry_policy: Sequence[RetryPolicy] = (),
|
||||
cache_policy: CachePolicy | None = None,
|
||||
checkpoint_during: bool = True,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.config = config
|
||||
@@ -248,7 +247,7 @@ class PregelLoop:
|
||||
self.trigger_to_nodes = trigger_to_nodes
|
||||
self.retry_policy = retry_policy
|
||||
self.cache_policy = cache_policy
|
||||
self.checkpoint_during = checkpoint_during
|
||||
self.durability = durability
|
||||
if self.stream is not None and CONFIG_KEY_STREAM in config[CONF]:
|
||||
self.stream = DuplexStream(self.stream, config[CONF][CONFIG_KEY_STREAM])
|
||||
scratchpad: PregelScratchpad | None = config[CONF].get(CONFIG_KEY_SCRATCHPAD)
|
||||
@@ -325,7 +324,7 @@ class PregelLoop:
|
||||
writes_to_save = writes
|
||||
# save writes
|
||||
self.checkpoint_pending_writes.extend((task_id, c, v) for c, v in writes)
|
||||
if self.checkpoint_during and self.checkpointer_put_writes is not None:
|
||||
if self.durability != "exit" and self.checkpointer_put_writes is not None:
|
||||
config = patch_configurable(
|
||||
self.checkpoint_config,
|
||||
{
|
||||
@@ -686,7 +685,7 @@ class PregelLoop:
|
||||
self.checkpoint_metadata = metadata
|
||||
# do checkpoint?
|
||||
do_checkpoint = self._checkpointer_put_after_previous is not None and (
|
||||
exiting or self.checkpoint_during
|
||||
exiting or self.durability != "exit"
|
||||
)
|
||||
# create new checkpoint
|
||||
self.checkpoint = create_checkpoint(
|
||||
@@ -748,7 +747,7 @@ class PregelLoop:
|
||||
traceback: TracebackType | None,
|
||||
) -> bool | None:
|
||||
# persist current checkpoint and writes
|
||||
if not self.checkpoint_during and (
|
||||
if self.durability == "exit" and (
|
||||
# if it's a top graph
|
||||
not self.is_nested
|
||||
# or a nested graph with error or interrupt
|
||||
@@ -893,6 +892,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
|
||||
nodes: Mapping[str, PregelNode],
|
||||
specs: Mapping[str, BaseChannel | ManagedValueSpec],
|
||||
trigger_to_nodes: Mapping[str, Sequence[str]],
|
||||
durability: Durability,
|
||||
manager: None | AsyncParentRunManager | ParentRunManager = None,
|
||||
interrupt_after: All | Sequence[str] = EMPTY_SEQ,
|
||||
interrupt_before: All | Sequence[str] = EMPTY_SEQ,
|
||||
@@ -902,7 +902,6 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
|
||||
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
|
||||
retry_policy: Sequence[RetryPolicy] = (),
|
||||
cache_policy: CachePolicy | None = None,
|
||||
checkpoint_during: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
input,
|
||||
@@ -923,7 +922,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
|
||||
trigger_to_nodes=trigger_to_nodes,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
checkpoint_during=checkpoint_during,
|
||||
durability=durability,
|
||||
)
|
||||
self.stack = ExitStack()
|
||||
if checkpointer:
|
||||
@@ -1064,6 +1063,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
|
||||
nodes: Mapping[str, PregelNode],
|
||||
specs: Mapping[str, BaseChannel | ManagedValueSpec],
|
||||
trigger_to_nodes: Mapping[str, Sequence[str]],
|
||||
durability: Durability,
|
||||
interrupt_after: All | Sequence[str] = EMPTY_SEQ,
|
||||
interrupt_before: All | Sequence[str] = EMPTY_SEQ,
|
||||
manager: None | AsyncParentRunManager | ParentRunManager = None,
|
||||
@@ -1073,7 +1073,6 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
|
||||
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
|
||||
retry_policy: Sequence[RetryPolicy] = (),
|
||||
cache_policy: CachePolicy | None = None,
|
||||
checkpoint_during: bool = True,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
input,
|
||||
@@ -1094,7 +1093,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
|
||||
trigger_to_nodes=trigger_to_nodes,
|
||||
retry_policy=retry_policy,
|
||||
cache_policy=cache_policy,
|
||||
checkpoint_during=checkpoint_during,
|
||||
durability=durability,
|
||||
)
|
||||
self.stack = AsyncExitStack()
|
||||
if checkpointer:
|
||||
+5
-5
@@ -13,8 +13,10 @@ from langchain_core.callbacks import BaseCallbackHandler
|
||||
from langchain_core.messages import BaseMessage
|
||||
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult
|
||||
|
||||
from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM, TAG_NOSTREAM_ALT
|
||||
from langgraph.types import Command, StreamChunk
|
||||
from langgraph._internal._constants import NS_SEP
|
||||
from langgraph.constants import TAG_HIDDEN, TAG_NOSTREAM
|
||||
from langgraph.pregel.protocol import StreamChunk
|
||||
from langgraph.types import Command
|
||||
|
||||
try:
|
||||
from langchain_core.tracers._streaming import _StreamingCallbackHandler
|
||||
@@ -94,9 +96,7 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler):
|
||||
metadata: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if metadata and (
|
||||
not tags or (TAG_NOSTREAM not in tags and TAG_NOSTREAM_ALT not in tags)
|
||||
):
|
||||
if metadata and (not tags or (TAG_NOSTREAM not in tags)):
|
||||
ns = tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP))[
|
||||
:-1
|
||||
]
|
||||
@@ -10,13 +10,13 @@ from typing import (
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_READ
|
||||
from langgraph._internal._config import merge_configs
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_READ
|
||||
from langgraph._internal._runnable import RunnableCallable, RunnableSeq
|
||||
from langgraph.pregel._utils import find_subgraph_pregel
|
||||
from langgraph.pregel._write import ChannelWrite
|
||||
from langgraph.pregel.protocol import PregelProtocol
|
||||
from langgraph.pregel.utils import find_subgraph_pregel
|
||||
from langgraph.pregel.write import ChannelWrite
|
||||
from langgraph.types import CachePolicy, RetryPolicy
|
||||
from langgraph.utils.config import merge_configs
|
||||
from langgraph.utils.runnable import RunnableCallable, RunnableSeq
|
||||
|
||||
READ_TYPE = Callable[[Union[str, Sequence[str]], bool], Union[Any, dict[str, Any]]]
|
||||
INPUT_CACHE_KEY_TYPE = tuple[Callable[..., Any], tuple[str, ...]]
|
||||
@@ -46,7 +46,6 @@ class ChannelRead(RunnableCallable):
|
||||
tags=tags,
|
||||
name=None,
|
||||
trace=False,
|
||||
func_accepts_config=True,
|
||||
)
|
||||
self.fresh = fresh
|
||||
self.mapper = mapper
|
||||
+2
-2
@@ -9,7 +9,8 @@ from collections.abc import Awaitable, Sequence
|
||||
from dataclasses import replace
|
||||
from typing import Any, Callable
|
||||
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._config import patch_configurable
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_RESUMING,
|
||||
@@ -17,7 +18,6 @@ from langgraph.constants import (
|
||||
)
|
||||
from langgraph.errors import GraphBubbleUp, ParentCommand
|
||||
from langgraph.types import Command, PregelExecutableTask, RetryPolicy
|
||||
from langgraph.utils.config import patch_configurable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
SUPPORTS_EXC_NOTES = sys.version_info >= (3, 11)
|
||||
+8
-8
@@ -19,29 +19,29 @@ from typing import (
|
||||
|
||||
from langchain_core.callbacks import Callbacks
|
||||
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CALL,
|
||||
CONFIG_KEY_SCRATCHPAD,
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NO_WRITES,
|
||||
RESUME,
|
||||
RETURN,
|
||||
TAG_HIDDEN,
|
||||
)
|
||||
from langgraph._internal._future import chain_future, run_coroutine_threadsafe
|
||||
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._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,
|
||||
PregelScratchpad,
|
||||
RetryPolicy,
|
||||
)
|
||||
from langgraph.utils.future import chain_future, run_coroutine_threadsafe
|
||||
|
||||
F = TypeVar("F", concurrent.futures.Future, asyncio.Future)
|
||||
E = TypeVar("E", threading.Event, asyncio.Event)
|
||||
@@ -0,0 +1,18 @@
|
||||
import dataclasses
|
||||
from typing import Any, Callable
|
||||
|
||||
from langgraph.types import _DC_KWARGS
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
class PregelScratchpad:
|
||||
step: int
|
||||
stop: int
|
||||
# call
|
||||
call_counter: Callable[[], int]
|
||||
# interrupt
|
||||
interrupt_counter: Callable[[], int]
|
||||
get_null_resume: Callable[[bool], Any]
|
||||
resume: list[Any]
|
||||
# subgraph
|
||||
subgraph_counter: Callable[[], int]
|
||||
+2
-2
@@ -6,12 +6,12 @@ import re
|
||||
import textwrap
|
||||
from typing import Any, Callable
|
||||
|
||||
from langchain_core.runnables import RunnableLambda, RunnableSequence
|
||||
from langchain_core.runnables import Runnable, RunnableLambda, RunnableSequence
|
||||
from typing_extensions import override
|
||||
|
||||
from langgraph._internal._runnable import RunnableCallable, RunnableSeq
|
||||
from langgraph.checkpoint.base import ChannelVersions
|
||||
from langgraph.pregel.protocol import PregelProtocol
|
||||
from langgraph.utils.runnable import Runnable, RunnableCallable, RunnableSeq
|
||||
|
||||
|
||||
def get_new_channel_versions(
|
||||
+2
-2
@@ -3,10 +3,10 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from langgraph._internal._constants import RESERVED
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.constants import RESERVED
|
||||
from langgraph.managed.base import ManagedValueMapping
|
||||
from langgraph.pregel.read import PregelNode
|
||||
from langgraph.pregel._read import PregelNode
|
||||
from langgraph.types import All
|
||||
|
||||
|
||||
+4
-3
@@ -13,9 +13,11 @@ from typing import (
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
|
||||
from langgraph.constants import CONF, CONFIG_KEY_SEND, MISSING, TASKS, Send
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_SEND, TASKS
|
||||
from langgraph._internal._runnable import RunnableCallable
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.errors import InvalidUpdateError
|
||||
from langgraph.utils.runnable import RunnableCallable
|
||||
from langgraph.types import Send
|
||||
|
||||
TYPE_SEND = Callable[[Sequence[tuple[str, Any]]], None]
|
||||
R = TypeVar("R", bound=Runnable)
|
||||
@@ -63,7 +65,6 @@ class ChannelWrite(RunnableCallable):
|
||||
name=None,
|
||||
tags=tags,
|
||||
trace=False,
|
||||
func_accepts_config=True,
|
||||
)
|
||||
self.writes = cast(
|
||||
list[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], writes
|
||||
@@ -5,24 +5,27 @@ from dataclasses import asdict
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import CheckpointMetadata, PendingWrite
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._config import patch_checkpoint_map
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
MISSING,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
RETURN,
|
||||
TAG_HIDDEN,
|
||||
)
|
||||
from langgraph.pregel.io import read_channels
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.base import BaseChannel
|
||||
from langgraph.checkpoint.base import CheckpointMetadata, PendingWrite
|
||||
from langgraph.constants import TAG_HIDDEN
|
||||
from langgraph.pregel._io import read_channels
|
||||
from langgraph.types import PregelExecutableTask, PregelTask, StateSnapshot
|
||||
from langgraph.utils.config import RunnableConfig, patch_checkpoint_map
|
||||
|
||||
__all__ = ("TaskPayload", "TaskResultPayload", "CheckpointTask", "CheckpointPayload")
|
||||
|
||||
|
||||
class TaskPayload(TypedDict):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,19 +2,19 @@ from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import AsyncIterator, Iterator, Sequence
|
||||
from typing import Any, Generic
|
||||
from typing import Any, Callable, Generic, cast
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from langchain_core.runnables.graph import Graph as DrawableGraph
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.pregel.types import All, StateSnapshot, StateUpdate, StreamMode
|
||||
from langgraph.types import Command
|
||||
from langgraph.typing import InputT, OutputT, StateT
|
||||
from langgraph.types import All, Command, StateSnapshot, StateUpdate, StreamMode
|
||||
from langgraph.typing import ContextT, InputT, OutputT, StateT
|
||||
|
||||
__all__ = ("PregelProtocol", "StreamProtocol")
|
||||
|
||||
|
||||
# TODO: remove Runnable inheritance here!
|
||||
class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT]):
|
||||
class PregelProtocol(Runnable[InputT, Any], Generic[StateT, ContextT, InputT, OutputT]):
|
||||
@abstractmethod
|
||||
def with_config(
|
||||
self, config: RunnableConfig | None = None, **kwargs: Any
|
||||
@@ -102,6 +102,7 @@ class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT]):
|
||||
input: InputT | Command | None,
|
||||
config: RunnableConfig | None = None,
|
||||
*,
|
||||
context: ContextT | None = None,
|
||||
stream_mode: StreamMode | list[StreamMode] | None = None,
|
||||
interrupt_before: All | Sequence[str] | None = None,
|
||||
interrupt_after: All | Sequence[str] | None = None,
|
||||
@@ -114,6 +115,7 @@ class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT]):
|
||||
input: InputT | Command | None,
|
||||
config: RunnableConfig | None = None,
|
||||
*,
|
||||
context: ContextT | None = None,
|
||||
stream_mode: StreamMode | list[StreamMode] | None = None,
|
||||
interrupt_before: All | Sequence[str] | None = None,
|
||||
interrupt_after: All | Sequence[str] | None = None,
|
||||
@@ -126,6 +128,7 @@ class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT]):
|
||||
input: InputT | Command | None,
|
||||
config: RunnableConfig | None = None,
|
||||
*,
|
||||
context: ContextT | None = None,
|
||||
interrupt_before: All | Sequence[str] | None = None,
|
||||
interrupt_after: All | Sequence[str] | None = None,
|
||||
) -> dict[str, Any] | Any: ...
|
||||
@@ -136,6 +139,26 @@ class PregelProtocol(Runnable[InputT, Any], Generic[StateT, InputT, OutputT]):
|
||||
input: InputT | Command | None,
|
||||
config: RunnableConfig | None = None,
|
||||
*,
|
||||
context: ContextT | None = None,
|
||||
interrupt_before: All | Sequence[str] | None = None,
|
||||
interrupt_after: All | Sequence[str] | None = None,
|
||||
) -> dict[str, Any] | Any: ...
|
||||
|
||||
|
||||
StreamChunk = tuple[tuple[str, ...], str, Any]
|
||||
|
||||
|
||||
class StreamProtocol:
|
||||
__slots__ = ("modes", "__call__")
|
||||
|
||||
modes: set[StreamMode]
|
||||
|
||||
__call__: Callable[[Self, StreamChunk], None]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
__call__: Callable[[StreamChunk], None],
|
||||
modes: set[StreamMode],
|
||||
) -> None:
|
||||
self.__call__ = cast(Callable[[Self, StreamChunk], None], __call__)
|
||||
self.modes = modes
|
||||
|
||||
@@ -29,8 +29,8 @@ from langgraph_sdk.schema import Command as CommandSDK
|
||||
from langgraph_sdk.schema import StreamMode as StreamModeSDK
|
||||
from typing_extensions import Self
|
||||
|
||||
from langgraph.checkpoint.base import CheckpointMetadata
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._config import merge_configs
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
@@ -40,13 +40,21 @@ from langgraph.constants import (
|
||||
INTERRUPT,
|
||||
NS_SEP,
|
||||
)
|
||||
from langgraph.checkpoint.base import CheckpointMetadata
|
||||
from langgraph.errors import GraphInterrupt, ParentCommand
|
||||
from langgraph.pregel.protocol import PregelProtocol
|
||||
from langgraph.pregel.types import All, PregelTask, StateSnapshot, StreamMode
|
||||
from langgraph.types import Command, Interrupt, StreamProtocol
|
||||
from langgraph.utils.config import merge_configs
|
||||
from langgraph.pregel.protocol import PregelProtocol, StreamProtocol
|
||||
from langgraph.types import (
|
||||
All,
|
||||
Command,
|
||||
Interrupt,
|
||||
PregelTask,
|
||||
StateSnapshot,
|
||||
StreamMode,
|
||||
)
|
||||
|
||||
CONF_DROPLIST = frozenset(
|
||||
__all__ = ("RemoteGraph", "RemoteException")
|
||||
|
||||
_CONF_DROPLIST = frozenset(
|
||||
(
|
||||
CONFIG_KEY_CHECKPOINT_MAP,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
@@ -56,7 +64,7 @@ CONF_DROPLIST = frozenset(
|
||||
)
|
||||
|
||||
|
||||
def sanitize_config_value(v: Any) -> Any:
|
||||
def _sanitize_config_value(v: Any) -> Any:
|
||||
"""Recursively sanitize a config value to ensure it contains only primitives."""
|
||||
if isinstance(v, (str, int, float, bool)):
|
||||
return v
|
||||
@@ -64,14 +72,14 @@ def sanitize_config_value(v: Any) -> Any:
|
||||
sanitized_dict = {}
|
||||
for k, val in v.items():
|
||||
if isinstance(k, str):
|
||||
sanitized_value = sanitize_config_value(val)
|
||||
sanitized_value = _sanitize_config_value(val)
|
||||
if sanitized_value is not None:
|
||||
sanitized_dict[k] = sanitized_value
|
||||
return sanitized_dict
|
||||
elif isinstance(v, (list, tuple)):
|
||||
sanitized_list = []
|
||||
for item in v:
|
||||
sanitized_item = sanitize_config_value(item)
|
||||
sanitized_item = _sanitize_config_value(item)
|
||||
if sanitized_item is not None:
|
||||
sanitized_list.append(sanitized_item)
|
||||
return sanitized_list
|
||||
@@ -252,9 +260,9 @@ class RemoteGraph(PregelProtocol):
|
||||
def _create_state_snapshot(self, state: ThreadState) -> StateSnapshot:
|
||||
tasks: list[PregelTask] = []
|
||||
for task in state["tasks"]:
|
||||
interrupts = []
|
||||
for interrupt in task["interrupts"]:
|
||||
interrupts.append(Interrupt(**interrupt))
|
||||
interrupts = tuple(
|
||||
Interrupt(**interrupt) for interrupt in task["interrupts"]
|
||||
)
|
||||
|
||||
tasks.append(
|
||||
PregelTask(
|
||||
@@ -262,7 +270,7 @@ class RemoteGraph(PregelProtocol):
|
||||
name=task["name"],
|
||||
path=tuple(),
|
||||
error=Exception(task["error"]) if task["error"] else None,
|
||||
interrupts=tuple(interrupts),
|
||||
interrupts=interrupts,
|
||||
state=(
|
||||
self._create_state_snapshot(task["state"])
|
||||
if task["state"]
|
||||
@@ -347,7 +355,7 @@ class RemoteGraph(PregelProtocol):
|
||||
for k, v in config["metadata"].items():
|
||||
if (
|
||||
isinstance(k, str)
|
||||
and (sanitized_value := sanitize_config_value(v)) is not None
|
||||
and (sanitized_value := _sanitize_config_value(v)) is not None
|
||||
):
|
||||
sanitized["metadata"][k] = sanitized_value
|
||||
|
||||
@@ -356,8 +364,8 @@ class RemoteGraph(PregelProtocol):
|
||||
for k, v in config["configurable"].items():
|
||||
if (
|
||||
isinstance(k, str)
|
||||
and k not in CONF_DROPLIST
|
||||
and (sanitized_value := sanitize_config_value(v)) is not None
|
||||
and k not in _CONF_DROPLIST
|
||||
and (sanitized_value := _sanitize_config_value(v)) is not None
|
||||
):
|
||||
sanitized["configurable"][k] = sanitized_value
|
||||
|
||||
|
||||
@@ -25,3 +25,14 @@ __all__ = [
|
||||
"StreamWriter",
|
||||
"default_retry_on",
|
||||
]
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
|
||||
warn(
|
||||
"Importing from langgraph.pregel.types is deprecated. "
|
||||
"Please use 'from langgraph.types import ...' instead.",
|
||||
LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field, replace
|
||||
from typing import Any, Generic, cast
|
||||
|
||||
from typing_extensions import TypedDict, Unpack
|
||||
|
||||
from langgraph._internal._constants import CONF, CONFIG_KEY_RUNTIME
|
||||
from langgraph.config import get_config
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import _DC_KWARGS, StreamWriter
|
||||
from langgraph.typing import ContextT
|
||||
|
||||
|
||||
def _no_op_stream_writer(_: Any) -> None: ...
|
||||
|
||||
|
||||
class _RuntimeOverrides(TypedDict, Generic[ContextT], total=False):
|
||||
context: ContextT
|
||||
store: BaseStore | None
|
||||
stream_writer: StreamWriter
|
||||
previous: Any
|
||||
|
||||
|
||||
@dataclass(**_DC_KWARGS)
|
||||
class Runtime(Generic[ContextT]):
|
||||
"""Convenience class that bundles run-scoped context and graph configuration.
|
||||
|
||||
!!! version-added "Added in version 1.0.0."
|
||||
"""
|
||||
|
||||
context: ContextT = field(default=None) # type: ignore[assignment]
|
||||
"""Static context for the graph run, like user_id, db_conn, etc.
|
||||
|
||||
Can also be thought of as 'run dependencies'."""
|
||||
|
||||
store: BaseStore | None = field(default=None)
|
||||
"""Store for the graph run, enabling persistence and memory."""
|
||||
|
||||
stream_writer: StreamWriter = field(default=_no_op_stream_writer)
|
||||
"""Function that writes to the custom stream."""
|
||||
|
||||
previous: Any = field(default=None)
|
||||
"""The previous return value for the given thread.
|
||||
|
||||
Only available with the functional API when a checkpointer is provided.
|
||||
"""
|
||||
|
||||
def merge(self, other: Runtime[ContextT]) -> Runtime[ContextT]:
|
||||
"""Merge two runtimes together.
|
||||
|
||||
If a value is not provided in the other runtime, the value from the current runtime is used.
|
||||
"""
|
||||
return Runtime(
|
||||
context=other.context or self.context,
|
||||
store=other.store or self.store,
|
||||
stream_writer=other.stream_writer
|
||||
if other.stream_writer is not _no_op_stream_writer
|
||||
else self.stream_writer,
|
||||
previous=other.previous or self.previous,
|
||||
)
|
||||
|
||||
def override(
|
||||
self, **overrides: Unpack[_RuntimeOverrides[ContextT]]
|
||||
) -> Runtime[ContextT]:
|
||||
"""Replace the runtime with a new runtime with the given overrides."""
|
||||
return replace(self, **overrides)
|
||||
|
||||
|
||||
DEFAULT_RUNTIME = Runtime(
|
||||
context=None,
|
||||
store=None,
|
||||
stream_writer=_no_op_stream_writer,
|
||||
previous=None,
|
||||
)
|
||||
|
||||
|
||||
def get_runtime(context_schema: type[ContextT] | None = None) -> Runtime[ContextT]:
|
||||
"""Get the runtime for the current graph run."""
|
||||
|
||||
# TODO: in an ideal world, we would have a context manager for
|
||||
# the runtime that's independent of the config. this will follow
|
||||
# from the removal of the configurable packing
|
||||
runtime = cast(Runtime[ContextT], get_config()[CONF].get(CONFIG_KEY_RUNTIME))
|
||||
return runtime
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import sys
|
||||
from collections import deque
|
||||
from collections.abc import Hashable, Sequence
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -14,16 +14,20 @@ from typing import (
|
||||
NamedTuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
final,
|
||||
)
|
||||
from warnings import warn
|
||||
|
||||
from langchain_core.runnables import Runnable, RunnableConfig
|
||||
from typing_extensions import Self
|
||||
from typing_extensions import Unpack, deprecated
|
||||
from xxhash import xxh3_128_hexdigest
|
||||
|
||||
from langgraph._internal._cache import default_cache_key
|
||||
from langgraph._internal._fields import get_cached_annotated_keys, get_update_as_tuples
|
||||
from langgraph._internal._retry import default_retry_on
|
||||
from langgraph._internal._typing import MISSING, DeprecatedKwargs
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata
|
||||
from langgraph.utils.cache import default_cache_key
|
||||
from langgraph.utils.fields import get_cached_annotated_keys, get_update_as_tuples
|
||||
from langgraph.warnings import LangGraphDeprecatedSinceV10
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langgraph.pregel.protocol import PregelProtocol
|
||||
@@ -37,6 +41,30 @@ except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
__all__ = (
|
||||
"All",
|
||||
"Checkpointer",
|
||||
"StreamMode",
|
||||
"StreamWriter",
|
||||
"RetryPolicy",
|
||||
"CachePolicy",
|
||||
"Interrupt",
|
||||
"StateUpdate",
|
||||
"PregelTask",
|
||||
"PregelExecutableTask",
|
||||
"StateSnapshot",
|
||||
"Send",
|
||||
"Command",
|
||||
"Durability",
|
||||
"interrupt",
|
||||
)
|
||||
|
||||
Durability = Literal["sync", "async", "exit"]
|
||||
"""Durability mode for the graph execution.
|
||||
- `"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."""
|
||||
|
||||
All = Literal["*"]
|
||||
"""Special value to indicate that graph should interrupt on all nodes."""
|
||||
|
||||
@@ -68,42 +96,13 @@ Always injected into nodes if requested as a keyword argument, but it's a no-op
|
||||
when not using stream_mode="custom"."""
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
_DC_SLOTS = {"slots": True}
|
||||
_DC_KWARGS = {"kw_only": True, "slots": True, "frozen": True}
|
||||
else:
|
||||
_DC_SLOTS = {}
|
||||
_DC_KWARGS = {"frozen": True}
|
||||
|
||||
|
||||
def default_retry_on(exc: Exception) -> bool:
|
||||
import httpx
|
||||
import requests
|
||||
|
||||
if isinstance(exc, ConnectionError):
|
||||
return True
|
||||
if isinstance(exc, httpx.HTTPStatusError):
|
||||
return 500 <= exc.response.status_code < 600
|
||||
if isinstance(exc, requests.HTTPError):
|
||||
return 500 <= exc.response.status_code < 600 if exc.response else True
|
||||
if isinstance(
|
||||
exc,
|
||||
(
|
||||
ValueError,
|
||||
TypeError,
|
||||
ArithmeticError,
|
||||
ImportError,
|
||||
LookupError,
|
||||
NameError,
|
||||
SyntaxError,
|
||||
RuntimeError,
|
||||
ReferenceError,
|
||||
StopIteration,
|
||||
StopAsyncIteration,
|
||||
OSError,
|
||||
),
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class RetryPolicy(NamedTuple):
|
||||
"""Configuration for retrying nodes.
|
||||
|
||||
@@ -129,7 +128,7 @@ class RetryPolicy(NamedTuple):
|
||||
KeyFuncT = TypeVar("KeyFuncT", bound=Callable[..., Union[str, bytes]])
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
@dataclass(**_DC_KWARGS)
|
||||
class CachePolicy(Generic[KeyFuncT]):
|
||||
"""Configuration for caching nodes."""
|
||||
|
||||
@@ -141,7 +140,11 @@ class CachePolicy(Generic[KeyFuncT]):
|
||||
"""Time to live for the cache entry in seconds. If None, the entry never expires."""
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
_DEFAULT_INTERRUPT_ID = "placeholder-id"
|
||||
|
||||
|
||||
@final
|
||||
@dataclass(init=False, **_DC_SLOTS)
|
||||
class Interrupt:
|
||||
"""Information about an interrupt that occurred in a node.
|
||||
|
||||
@@ -149,16 +152,41 @@ class Interrupt:
|
||||
"""
|
||||
|
||||
value: Any
|
||||
resumable: bool = False
|
||||
ns: Sequence[str] | None = None
|
||||
when: Literal["during"] = dataclasses.field(default="during", repr=False)
|
||||
id: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: Any,
|
||||
id: str = _DEFAULT_INTERRUPT_ID,
|
||||
**deprecated_kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> None:
|
||||
self.value = value
|
||||
|
||||
if (
|
||||
(ns := deprecated_kwargs.get("ns", MISSING)) is not MISSING
|
||||
and (id == _DEFAULT_INTERRUPT_ID)
|
||||
and (isinstance(ns, Sequence))
|
||||
):
|
||||
self.id = xxh3_128_hexdigest("|".join(ns).encode())
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
@classmethod
|
||||
def from_ns(cls, value: Any, ns: str) -> Interrupt:
|
||||
return cls(value=value, id=xxh3_128_hexdigest(ns.encode()))
|
||||
|
||||
@property
|
||||
@deprecated(
|
||||
"`interrupt_id` is deprecated. Use `id` instead.",
|
||||
stacklevel=2,
|
||||
)
|
||||
def interrupt_id(self) -> str:
|
||||
"""Generate a unique ID for the interrupt based on its namespace."""
|
||||
if self.ns is None:
|
||||
return "placeholder-id"
|
||||
return xxh3_128_hexdigest("|".join(self.ns).encode())
|
||||
warn(
|
||||
"`interrupt_id` is deprecated. Use `id` instead.",
|
||||
LangGraphDeprecatedSinceV10,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.id
|
||||
|
||||
|
||||
class StateUpdate(NamedTuple):
|
||||
@@ -196,7 +224,7 @@ class CacheKey(NamedTuple):
|
||||
"""Time to live for the cache entry in seconds."""
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_T_DC_KWARGS)
|
||||
@dataclass(**_T_DC_KWARGS)
|
||||
class PregelExecutableTask:
|
||||
name: str
|
||||
input: Any
|
||||
@@ -307,7 +335,7 @@ class Send:
|
||||
N = TypeVar("N", bound=Hashable)
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
@dataclass(**_DC_KWARGS)
|
||||
class Command(Generic[N], ToolOutputMixin):
|
||||
"""One or more commands to update the graph's state and send messages to nodes.
|
||||
|
||||
@@ -340,9 +368,7 @@ class Command(Generic[N], ToolOutputMixin):
|
||||
def __repr__(self) -> str:
|
||||
# get all non-None values
|
||||
contents = ", ".join(
|
||||
f"{key}={value!r}"
|
||||
for key, value in dataclasses.asdict(self).items()
|
||||
if value
|
||||
f"{key}={value!r}" for key, value in asdict(self).items() if value
|
||||
)
|
||||
return f"Command({contents})"
|
||||
|
||||
@@ -364,39 +390,6 @@ class Command(Generic[N], ToolOutputMixin):
|
||||
PARENT: ClassVar[Literal["__parent__"]] = "__parent__"
|
||||
|
||||
|
||||
StreamChunk = tuple[tuple[str, ...], str, Any]
|
||||
|
||||
|
||||
class StreamProtocol:
|
||||
__slots__ = ("modes", "__call__")
|
||||
|
||||
modes: set[StreamMode]
|
||||
|
||||
__call__: Callable[[Self, StreamChunk], None]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
__call__: Callable[[StreamChunk], None],
|
||||
modes: set[StreamMode],
|
||||
) -> None:
|
||||
self.__call__ = cast(Callable[[Self, StreamChunk], None], __call__)
|
||||
self.modes = modes
|
||||
|
||||
|
||||
@dataclasses.dataclass(**_DC_KWARGS)
|
||||
class PregelScratchpad:
|
||||
step: int
|
||||
stop: int
|
||||
# call
|
||||
call_counter: Callable[[], int]
|
||||
# interrupt
|
||||
interrupt_counter: Callable[[], int]
|
||||
get_null_resume: Callable[[bool], Any]
|
||||
resume: list[Any]
|
||||
# subgraph
|
||||
subgraph_counter: Callable[[], int]
|
||||
|
||||
|
||||
def interrupt(value: Any) -> Any:
|
||||
"""Interrupt the graph with a resumable exception from within a node.
|
||||
|
||||
@@ -465,22 +458,16 @@ def interrupt(value: Any) -> Any:
|
||||
|
||||
for chunk in graph.stream({\"foo\": \"abc\"}, config):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'__interrupt__': (Interrupt(value='what is your age?', resumable=True, ns=['node:62e598fa-8653-9d6d-2046-a70203020e37'], when='during'),)}
|
||||
```
|
||||
# > {'__interrupt__': (Interrupt(value='what is your age?', id='45fda8478b2ef754419799e10992af06'),)}
|
||||
|
||||
```python
|
||||
command = Command(resume=\"some input from a human!!!\")
|
||||
|
||||
for chunk in graph.stream(Command(resume=\"some input from a human!!!\"), config):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
```pycon
|
||||
Received an input from the interrupt: some input from a human!!!
|
||||
{'node': {'human_value': 'some input from a human!!!'}}
|
||||
# > Received an input from the interrupt: some input from a human!!!
|
||||
# > {'node': {'human_value': 'some input from a human!!!'}}
|
||||
```
|
||||
|
||||
Args:
|
||||
@@ -492,19 +479,18 @@ def interrupt(value: Any) -> Any:
|
||||
Raises:
|
||||
GraphInterrupt: On the first invocation within the node, halts execution and surfaces the provided value to the client.
|
||||
"""
|
||||
from langgraph.config import get_config
|
||||
from langgraph.constants import (
|
||||
from langgraph._internal._constants import (
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_SCRATCHPAD,
|
||||
CONFIG_KEY_SEND,
|
||||
NS_SEP,
|
||||
RESUME,
|
||||
)
|
||||
from langgraph.config import get_config
|
||||
from langgraph.errors import GraphInterrupt
|
||||
|
||||
conf = get_config()["configurable"]
|
||||
# track interrupt index
|
||||
scratchpad: PregelScratchpad = conf[CONFIG_KEY_SCRATCHPAD]
|
||||
scratchpad = conf[CONFIG_KEY_SCRATCHPAD]
|
||||
idx = scratchpad.interrupt_counter()
|
||||
# find previous resume values
|
||||
if scratchpad.resume:
|
||||
@@ -520,10 +506,9 @@ def interrupt(value: Any) -> Any:
|
||||
# no resume value found
|
||||
raise GraphInterrupt(
|
||||
(
|
||||
Interrupt(
|
||||
Interrupt.from_ns(
|
||||
value=value,
|
||||
resumable=True,
|
||||
ns=cast(str, conf[CONFIG_KEY_CHECKPOINT_NS]).split(NS_SEP),
|
||||
ns=conf[CONFIG_KEY_CHECKPOINT_NS],
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,7 +4,16 @@ from typing import Union
|
||||
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
from langgraph._typing import StateLike
|
||||
from langgraph._internal._typing import StateLike
|
||||
|
||||
__all__ = (
|
||||
"StateT",
|
||||
"StateT_co",
|
||||
"StateT_contra",
|
||||
"InputT",
|
||||
"OutputT",
|
||||
"ContextT",
|
||||
)
|
||||
|
||||
StateT = TypeVar("StateT", bound=StateLike)
|
||||
"""Type variable used to represent the state in a graph."""
|
||||
@@ -13,18 +22,29 @@ StateT_co = TypeVar("StateT_co", bound=StateLike, covariant=True)
|
||||
|
||||
StateT_contra = TypeVar("StateT_contra", bound=StateLike, contravariant=True)
|
||||
|
||||
ContextT = TypeVar("ContextT", bound=Union[StateLike, None], default=None)
|
||||
"""Type variable used to represent graph run scoped context.
|
||||
|
||||
Defaults to `None`.
|
||||
"""
|
||||
|
||||
ContextT_contra = TypeVar(
|
||||
"ContextT_contra", bound=Union[StateLike, None], contravariant=True, default=None
|
||||
)
|
||||
|
||||
InputT = TypeVar("InputT", bound=StateLike, default=StateT)
|
||||
"""Type variable used to represent the input to a state graph.
|
||||
|
||||
Defaults to `StateT`.
|
||||
"""
|
||||
|
||||
ResolvedInputT = TypeVar("ResolvedInputT", bound=StateLike)
|
||||
"""Type variable used to represent the resolved input to a state graph.
|
||||
OutputT = TypeVar("OutputT", bound=StateLike, default=StateT)
|
||||
"""Type variable used to represent the output of a state graph.
|
||||
|
||||
No default.
|
||||
Defaults to `StateT`.
|
||||
"""
|
||||
|
||||
NodeInputT = TypeVar("NodeInputT", bound=StateLike)
|
||||
"""Type variable used to represent the input to a node."""
|
||||
|
||||
OutputT = TypeVar("OutputT", bound=Union[StateLike, None], default=StateT)
|
||||
"""Type variable used to represent the output of a state graph."""
|
||||
NodeInputT_contra = TypeVar("NodeInputT_contra", bound=StateLike, contravariant=True)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from importlib import metadata
|
||||
|
||||
__all__ = ("__version__",)
|
||||
|
||||
try:
|
||||
__version__ = metadata.version(__package__)
|
||||
except metadata.PackageNotFoundError:
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = (
|
||||
"LangGraphDeprecationWarning",
|
||||
"LangGraphDeprecatedSinceV05",
|
||||
"LangGraphDeprecatedSinceV10",
|
||||
)
|
||||
|
||||
|
||||
class LangGraphDeprecationWarning(DeprecationWarning):
|
||||
"""A LangGraph specific deprecation warning.
|
||||
@@ -46,3 +52,10 @@ class LangGraphDeprecatedSinceV05(LangGraphDeprecationWarning):
|
||||
|
||||
def __init__(self, message: str, *args: object) -> None:
|
||||
super().__init__(message, *args, since=(0, 5), expected_removal=(2, 0))
|
||||
|
||||
|
||||
class LangGraphDeprecatedSinceV10(LangGraphDeprecationWarning):
|
||||
"""A specific `LangGraphDeprecationWarning` subclass defining functionality deprecated since LangGraph v1.0.0"""
|
||||
|
||||
def __init__(self, message: str, *args: object) -> None:
|
||||
super().__init__(message, *args, since=(1, 0), expected_removal=(2, 0))
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0a1"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
@@ -14,7 +14,7 @@ license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langchain-core>=0.1",
|
||||
"langgraph-checkpoint>=2.1.0,<3.0.0",
|
||||
"langgraph-sdk>=0.1.42,<0.2.0",
|
||||
"langgraph-sdk>=0.2.0,<0.3.0",
|
||||
"langgraph-prebuilt>=0.5.0,<0.6.0",
|
||||
"xxhash>=3.5.0",
|
||||
"pydantic>=2.7.4",
|
||||
@@ -60,6 +60,7 @@ langgraph-checkpoint = { path = "../checkpoint", editable = true }
|
||||
langgraph-checkpoint-sqlite = { path = "../checkpoint-sqlite", editable = true }
|
||||
langgraph-checkpoint-postgres = { path = "../checkpoint-postgres", editable = true }
|
||||
langgraph-sdk = { path = "../sdk-py", editable = true }
|
||||
langgraph-cli = { path = "../cli", editable = true }
|
||||
|
||||
[tool.ruff]
|
||||
lint.select = [ "E", "F", "I", "TID251", "UP" ]
|
||||
|
||||
@@ -1,105 +1,4 @@
|
||||
# serializer version: 1
|
||||
# name: test_conditional_graph[memory]
|
||||
'''
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent",
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": [
|
||||
"langchain",
|
||||
"schema",
|
||||
"runnable",
|
||||
"RunnableAssign"
|
||||
],
|
||||
"name": "agent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tools",
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "tools"
|
||||
},
|
||||
"metadata": {
|
||||
"parents": {},
|
||||
"version": 2,
|
||||
"variant": "b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "__start__"
|
||||
},
|
||||
{
|
||||
"id": "__end__"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "__start__",
|
||||
"target": "agent"
|
||||
},
|
||||
{
|
||||
"source": "agent",
|
||||
"target": "__end__",
|
||||
"data": "exit",
|
||||
"conditional": true
|
||||
},
|
||||
{
|
||||
"source": "agent",
|
||||
"target": "tools",
|
||||
"data": "continue",
|
||||
"conditional": true
|
||||
},
|
||||
{
|
||||
"source": "tools",
|
||||
"target": "agent"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_graph[memory].1
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> agent;
|
||||
agent -. exit .-> __end__;
|
||||
agent -. continue .-> tools;
|
||||
tools --> agent;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_graph[memory].2
|
||||
'''
|
||||
---
|
||||
config:
|
||||
flowchart:
|
||||
curve: linear
|
||||
---
|
||||
graph TD;
|
||||
agent(agent)
|
||||
tools(tools<hr/><small><em>parents = {}
|
||||
version = 2
|
||||
variant = b</em></small>)
|
||||
__start__([<p>__start__</p>]):::first
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> agent;
|
||||
agent -. exit .-> __end__;
|
||||
agent -. continue .-> tools;
|
||||
tools --> agent;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_state_graph[memory]
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"additionalProperties": true, "title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "title": "Agent Outcome"}, "intermediate_steps": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "title": "AgentState", "type": "object"}'
|
||||
# ---
|
||||
@@ -116,8 +15,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -142,8 +41,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "tools"
|
||||
@@ -204,8 +103,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -291,8 +190,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -304,8 +203,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "agent"
|
||||
|
||||
@@ -1,93 +1,4 @@
|
||||
# serializer version: 1
|
||||
# name: test_conditional_entrypoint_graph
|
||||
'{"title": "LangGraphInput"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph.1
|
||||
'{"title": "LangGraphOutput"}'
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph.2
|
||||
'''
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "left",
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "left"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "right",
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "right"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "__start__",
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "__end__"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "__start__",
|
||||
"target": "left",
|
||||
"data": "go-left",
|
||||
"conditional": true
|
||||
},
|
||||
{
|
||||
"source": "__start__",
|
||||
"target": "right",
|
||||
"data": "go-right",
|
||||
"conditional": true
|
||||
},
|
||||
{
|
||||
"source": "left",
|
||||
"target": "__end__",
|
||||
"conditional": true
|
||||
},
|
||||
{
|
||||
"source": "right",
|
||||
"target": "__end__"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph.3
|
||||
'''
|
||||
graph TD;
|
||||
__start__ -. go-left .-> left;
|
||||
__start__ -. go-right .-> right;
|
||||
left -.-> __end__;
|
||||
right --> __end__;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph_state
|
||||
'{"properties": {"input": {"title": "Input", "type": "string"}, "output": {"title": "Output", "type": "string"}, "steps": {"items": {"type": "string"}, "title": "Steps", "type": "array"}}, "title": "AgentState", "type": "object"}'
|
||||
# ---
|
||||
@@ -104,8 +15,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -117,8 +28,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "left"
|
||||
@@ -130,8 +41,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "right"
|
||||
@@ -193,8 +104,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -206,8 +117,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "get_weather"
|
||||
@@ -249,8 +160,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -262,8 +173,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "A"
|
||||
@@ -275,8 +186,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "B"
|
||||
@@ -327,8 +238,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -340,8 +251,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "human"
|
||||
@@ -353,8 +264,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "agent"
|
||||
@@ -406,8 +317,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -461,8 +372,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "__start__"
|
||||
@@ -474,8 +385,8 @@
|
||||
"data": {
|
||||
"id": [
|
||||
"langgraph",
|
||||
"utils",
|
||||
"runnable",
|
||||
"_internal",
|
||||
"_runnable",
|
||||
"RunnableCallable"
|
||||
],
|
||||
"name": "worker_node"
|
||||
@@ -767,8 +678,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': '__start__',
|
||||
@@ -780,8 +691,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'tool_one',
|
||||
@@ -793,8 +704,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'tool_three',
|
||||
@@ -809,8 +720,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'tool_two:__start__',
|
||||
@@ -822,8 +733,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'tool_two:tool_two_slow',
|
||||
@@ -835,8 +746,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'tool_two:tool_two_fast',
|
||||
@@ -914,7 +825,7 @@
|
||||
'''
|
||||
# ---
|
||||
# name: test_state_graph_w_config_inherited_state_keys
|
||||
'{"$defs": {"Config": {"properties": {"tools": {"items": {"type": "string"}, "title": "Tools", "type": "array"}}, "title": "Config", "type": "object"}}, "properties": {"configurable": {"$ref": "#/$defs/Config", "default": null}}, "title": "LangGraphConfig", "type": "object"}'
|
||||
'{"properties": {"tools": {"items": {"type": "string"}, "title": "Tools", "type": "array"}}, "title": "Context", "type": "object"}'
|
||||
# ---
|
||||
# name: test_state_graph_w_config_inherited_state_keys.1
|
||||
'{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"additionalProperties": true, "type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"additionalProperties": true, "title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "title": "Agent Outcome"}, "intermediate_steps": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input", "agent_outcome"], "title": "AgentState", "type": "object"}'
|
||||
@@ -1020,8 +931,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': '__start__',
|
||||
@@ -1033,8 +944,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'ask_question',
|
||||
@@ -1046,8 +957,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'answer_question',
|
||||
@@ -1087,8 +998,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': '__start__',
|
||||
@@ -1100,8 +1011,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'generate_analysts',
|
||||
@@ -1126,8 +1037,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'generate_sections',
|
||||
@@ -1185,8 +1096,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': '__start__',
|
||||
@@ -1198,8 +1109,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'generate_analysts',
|
||||
@@ -1211,8 +1122,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'generate_sections',
|
||||
@@ -1227,8 +1138,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'conduct_interview:__start__',
|
||||
@@ -1240,8 +1151,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'conduct_interview:ask_question',
|
||||
@@ -1253,8 +1164,8 @@
|
||||
'data': dict({
|
||||
'id': list([
|
||||
'langgraph',
|
||||
'utils',
|
||||
'runnable',
|
||||
'_internal',
|
||||
'_runnable',
|
||||
'RunnableCallable',
|
||||
]),
|
||||
'name': 'conduct_interview:answer_question',
|
||||
|
||||
@@ -10,6 +10,7 @@ from langgraph.cache.memory import InMemoryCache
|
||||
from langgraph.cache.sqlite import SqliteCache
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import Durability
|
||||
from tests.conftest_checkpointer import (
|
||||
_checkpointer_memory,
|
||||
_checkpointer_memory_migrate_sends,
|
||||
@@ -49,8 +50,8 @@ def deterministic_uuids(mocker: MockerFixture) -> MockerFixture:
|
||||
return mocker.patch("uuid.uuid4", side_effect=side_effect)
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False])
|
||||
def checkpoint_during(request: pytest.FixtureRequest) -> bool:
|
||||
@pytest.fixture(params=["sync", "async", "exit"])
|
||||
def durability(request: pytest.FixtureRequest) -> Durability:
|
||||
return request.param
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from langgraph.constants import PULL, PUSH
|
||||
from langgraph.pregel.algo import prepare_next_tasks, task_path_str
|
||||
from langgraph.pregel.checkpoint import channels_from_checkpoint, empty_checkpoint
|
||||
from langgraph._internal._constants import PULL, PUSH
|
||||
from langgraph.pregel._algo import prepare_next_tasks, task_path_str
|
||||
from langgraph.pregel._checkpoint import channels_from_checkpoint, empty_checkpoint
|
||||
|
||||
|
||||
def test_prepare_next_tasks() -> None:
|
||||
|
||||
@@ -4,10 +4,10 @@ from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
from langgraph._internal._typing import MISSING
|
||||
from langgraph.channels.binop import BinaryOperatorAggregate
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.topic import Topic
|
||||
from langgraph.constants import MISSING
|
||||
from langgraph.errors import EmptyChannelError, InvalidUpdateError
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user