codekingpro/portable-devtools
114k
1from __future__ import annotations2 3from collections.abc import Sequence4from dataclasses import dataclass5from enum import Enum6from typing import Any, Literal7from warnings import warn8 9# EmptyChannelError is re-exported from langgraph.channels.base10from langgraph.checkpoint.base import EmptyChannelError # noqa: F40111from typing_extensions import deprecated12 13from langgraph.types import Command, Interrupt14from langgraph.warnings import LangGraphDeprecatedSinceV1015 16__all__ = (17 "EmptyChannelError",18 "ErrorCode",19 "GraphDrained",20 "GraphRecursionError",21 "InvalidUpdateError",22 "GraphBubbleUp",23 "GraphInterrupt",24 "NodeError",25 "NodeInterrupt",26 "NodeTimeoutError",27 "ParentCommand",28 "EmptyInputError",29 "TaskNotFound",30)31 32 33class ErrorCode(Enum):34 GRAPH_RECURSION_LIMIT = "GRAPH_RECURSION_LIMIT"35 INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE"36 INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE"37 MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS"38 INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY"39 40 41def create_error_message(*, message: str, error_code: ErrorCode) -> str:42 return (43 f"{message}\n"44 "For troubleshooting, visit: https://docs.langchain.com/oss/python/langgraph/"45 f"errors/{error_code.value}"46 )47 48 49class GraphBubbleUp(Exception):50 pass51 52 53class GraphDrained(GraphBubbleUp):54 """Raised when a graph run exits early due to a drain request.55 56 This indicates the graph stopped cooperatively at a superstep boundary57 because `RunControl.request_drain()` was called (e.g., in response to58 SIGTERM). The checkpoint is saved and the run can be resumed later.59 """60 61 def __init__(self, reason: str = "shutdown") -> None:62 self.reason = reason63 super().__init__(f"Graph drained: {reason}")64 65 66class GraphRecursionError(RecursionError):67 """Raised when the graph has exhausted the maximum number of steps.68 69 This prevents infinite loops. To increase the maximum number of steps,70 run your graph with a config specifying a higher `recursion_limit`.71 72 Troubleshooting guides:73 74 - [`GRAPH_RECURSION_LIMIT`](https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT)75 76 Examples:77 78 graph = builder.compile()79 graph.invoke(80 {"messages": [("user", "Hello, world!")]},81 # The config is the second positional argument82 {"recursion_limit": 1000},83 )84 """85 86 pass87 88 89class InvalidUpdateError(Exception):90 """Raised when attempting to update a channel with an invalid set of updates.91 92 Troubleshooting guides:93 94 - [`INVALID_CONCURRENT_GRAPH_UPDATE`](https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE)95 - [`INVALID_GRAPH_NODE_RETURN_VALUE`](https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE)96 """97 98 pass99 100 101class GraphInterrupt(GraphBubbleUp):102 """Raised when a subgraph is interrupted, suppressed by the root graph.103 Never raised directly, or surfaced to the user."""104 105 def __init__(self, interrupts: Sequence[Interrupt] = ()) -> None:106 super().__init__(interrupts)107 108 109@deprecated(110 "NodeInterrupt is deprecated. Please use [`interrupt`][langgraph.types.interrupt] instead.",111 category=None,112)113class NodeInterrupt(GraphInterrupt):114 """Raised by a node to interrupt execution."""115 116 def __init__(self, value: Any, id: str | None = None) -> None:117 warn(118 "NodeInterrupt is deprecated. Please use `langgraph.types.interrupt` instead.",119 LangGraphDeprecatedSinceV10,120 stacklevel=2,121 )122 if id is None:123 super().__init__([Interrupt(value=value)])124 else:125 super().__init__([Interrupt(value=value, id=id)])126 127 128class ParentCommand(GraphBubbleUp):129 args: tuple[Command]130 131 def __init__(self, command: Command) -> None:132 super().__init__(command)133 134 135class EmptyInputError(Exception):136 """Raised when graph receives an empty input."""137 138 pass139 140 141class TaskNotFound(Exception):142 """Raised when the executor is unable to find a task (for distributed mode)."""143 144 pass145 146 147@dataclass(frozen=True, slots=True)148class NodeError:149 """Failure context passed to a node-level error handler.150 151 Inject by adding a parameter typed `NodeError` to a handler registered via152 `StateGraph.add_node(..., error_handler=...)`:153 154 ```python155 def handler(state: State, error: NodeError) -> Command:156 return Command(update={"status": f"recovered from {error.node}: {error.error}"})157 ```158 """159 160 node: str161 """Name of the node whose execution failed."""162 163 error: BaseException164 """Exception raised by the failed node."""165 166 167class NodeTimeoutError(Exception):168 """Raised when a node invocation exceeds one of its configured timeouts.169 170 Does **not** inherit from the built-in `TimeoutError` (a subclass of171 `OSError`) so that the default `RetryPolicy` treats it as retryable.172 173 Both `idle_timeout` and `run_timeout` reflect the configured policy at the174 time of the failure (each is `None` if not configured). `kind` and175 `timeout` identify which one fired.176 """177 178 node: str179 timeout: float180 run_timeout: float | None181 idle_timeout: float | None182 elapsed: float183 kind: Literal["idle", "run"]184 185 def __init__(186 self,187 node: str,188 elapsed: float,189 *,190 kind: Literal["idle", "run"],191 idle_timeout: float | None = None,192 run_timeout: float | None = None,193 ) -> None:194 if kind == "idle":195 if idle_timeout is None:196 raise ValueError("idle_timeout is required when kind='idle'")197 message = (198 f"Node '{node}' exceeded its idle timeout of "199 f"{idle_timeout:.3f}s without making progress "200 f"(elapsed: {elapsed:.3f}s)."201 )202 self.timeout = idle_timeout203 elif kind == "run":204 if run_timeout is None:205 raise ValueError("run_timeout is required when kind='run'")206 message = (207 f"Node '{node}' exceeded its run timeout of "208 f"{run_timeout:.3f}s (elapsed: {elapsed:.3f}s)."209 )210 self.timeout = run_timeout211 else:212 raise ValueError("kind must be 'idle' or 'run'")213 super().__init__(message)214 self.node = node215 self.elapsed = elapsed216 self.kind = kind217 self.idle_timeout = idle_timeout218 self.run_timeout = run_timeout219 