langgraph-executor 0.0.1a0__tar.gz → 0.0.1a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/PKG-INFO +1 -1
  2. langgraph_executor-0.0.1a2/langgraph_executor/__init__.py +1 -0
  3. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/execute_task.py +24 -14
  4. langgraph_executor-0.0.1a2/langgraph_executor/executor.py +162 -0
  5. langgraph_executor-0.0.1a2/langgraph_executor/executor_base.py +473 -0
  6. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/extract_graph.py +2 -5
  7. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/info_logger.py +3 -3
  8. langgraph_executor-0.0.1a2/langgraph_executor/pb/executor_pb2.py +84 -0
  9. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/executor_pb2.pyi +49 -5
  10. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/executor_pb2_grpc.py +44 -0
  11. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/executor_pb2_grpc.pyi +20 -0
  12. langgraph_executor-0.0.1a2/langgraph_executor/pb/graph_pb2.py +51 -0
  13. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/graph_pb2.pyi +2 -20
  14. langgraph_executor-0.0.1a2/langgraph_executor/pb/runtime_pb2.py +68 -0
  15. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/runtime_pb2.pyi +4 -4
  16. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/server.py +22 -25
  17. langgraph_executor-0.0.1a0/langgraph_executor/__init__.py +0 -1
  18. langgraph_executor-0.0.1a0/langgraph_executor/executor.py +0 -341
  19. langgraph_executor-0.0.1a0/langgraph_executor/pb/executor_pb2.py +0 -79
  20. langgraph_executor-0.0.1a0/langgraph_executor/pb/graph_pb2.py +0 -55
  21. langgraph_executor-0.0.1a0/langgraph_executor/pb/runtime_pb2.py +0 -68
  22. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/.gitignore +0 -0
  23. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/README.md +0 -0
  24. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/common.py +0 -0
  25. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/example.py +0 -0
  26. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/__init__.py +0 -0
  27. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/graph_pb2_grpc.py +0 -0
  28. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/graph_pb2_grpc.pyi +0 -0
  29. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/runtime_pb2_grpc.py +0 -0
  30. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/runtime_pb2_grpc.pyi +0 -0
  31. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/types_pb2.py +0 -0
  32. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/types_pb2.pyi +0 -0
  33. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/types_pb2_grpc.py +0 -0
  34. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/pb/types_pb2_grpc.pyi +0 -0
  35. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/py.typed +0 -0
  36. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/setup.sh +0 -0
  37. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/langgraph_executor/stream_utils.py +0 -0
  38. {langgraph_executor-0.0.1a0 → langgraph_executor-0.0.1a2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-executor
3
- Version: 0.0.1a0
3
+ Version: 0.0.1a2
4
4
  Summary: LangGraph python RPC server executable by the langgraph-go orchestrator.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: grpcio>=1.73.1
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1a2"
@@ -43,21 +43,13 @@ from langgraph_executor.common import (
43
43
  from langgraph_executor.pb import types_pb2
44
44
 
45
45
 
46
- def get_init_request(request_iterator):
47
- request = next(request_iterator)
48
-
49
- if not hasattr(request, "init"):
50
- raise ValueError("First message must be init")
51
-
52
- return request.init
53
-
54
-
55
46
  def reconstruct_task(
56
47
  request,
57
48
  graph: Pregel,
58
49
  *,
59
50
  store: BaseStore | None = None,
60
51
  config: RunnableConfig | None = None,
52
+ custom_stream_writer=None,
61
53
  ) -> PregelExecutableTask:
62
54
  pb_task = request.task
63
55
 
@@ -91,7 +83,9 @@ def reconstruct_task(
91
83
  val = pb_to_val(pb_task.input["PUSH_INPUT"])
92
84
 
93
85
  writes = deque()
94
- runtime = ensure_runtime(configurable, store, graph)
86
+ runtime = ensure_runtime(
87
+ configurable, store, graph, custom_stream_writer=custom_stream_writer
88
+ )
95
89
 
96
90
  # Generate cache key if cache policy exists
97
91
  cache_policy = getattr(proc, "cache_policy", None)
@@ -184,15 +178,31 @@ def create_scratchpad(
184
178
  return scratchpad
185
179
 
186
180
 
187
- def ensure_runtime(configurable, store, graph):
181
+ def ensure_runtime(configurable, store, graph, custom_stream_writer=None):
188
182
  runtime = configurable.get(CONFIG_KEY_RUNTIME)
183
+
184
+ # Prepare runtime overrides
185
+ overrides = {"store": store}
186
+ if custom_stream_writer is not None:
187
+ overrides["stream_writer"] = custom_stream_writer
188
+
189
189
  if runtime is None:
190
- return DEFAULT_RUNTIME.override(store=store)
190
+ return DEFAULT_RUNTIME.override(**overrides)
191
191
  if isinstance(runtime, Runtime):
192
- return runtime.override(store=store)
192
+ return runtime.override(**overrides)
193
193
  if isinstance(runtime, dict):
194
194
  context = _coerce_context(graph, runtime.get("context"))
195
- return Runtime(**(runtime | {"store": store, "context": context}))
195
+ return Runtime(
196
+ **(
197
+ runtime
198
+ | {"store": store, "context": context}
199
+ | (
200
+ {"stream_writer": custom_stream_writer}
201
+ if custom_stream_writer
202
+ else {}
203
+ )
204
+ )
205
+ )
196
206
  raise ValueError("Invalid runtime")
197
207
 
198
208
 
@@ -0,0 +1,162 @@
1
+ import contextlib
2
+ import functools
3
+ import logging
4
+ from typing import Any
5
+
6
+ import grpc
7
+ import grpc.aio
8
+ from langgraph._internal._constants import NS_SEP
9
+ from langgraph.pregel import Pregel
10
+
11
+ from langgraph_executor.executor_base import LangGraphExecutorServicer
12
+ from langgraph_executor.pb.executor_pb2_grpc import (
13
+ add_LangGraphExecutorServicer_to_server,
14
+ )
15
+
16
+ # Internal helpers
17
+ LOGGER = logging.getLogger(__name__)
18
+
19
+
20
+ def create_server(graphs: dict[str, Pregel], address: str) -> grpc.aio.Server:
21
+ graphs, subgraph_map = _load_graphs(graphs)
22
+ server = grpc.aio.server(
23
+ # Be permissive: allow client pings without active RPCs and accept intervals
24
+ # as low as 50s. Our clients still default to ~5m, but this avoids penalizing
25
+ # other, more frequent clients.
26
+ options=[
27
+ ("grpc.keepalive_permit_without_calls", 1),
28
+ ("grpc.http2.min_recv_ping_interval_without_data_ms", 50000), # 50s
29
+ ("grpc.http2.max_ping_strikes", 2),
30
+ ]
31
+ )
32
+ getter = functools.partial(get_graph, graphs=graphs)
33
+ add_LangGraphExecutorServicer_to_server(
34
+ LangGraphExecutorServicer(graphs, subgraph_map=subgraph_map, get_graph=getter),
35
+ server,
36
+ )
37
+ server.add_insecure_port(address)
38
+ return server
39
+
40
+
41
+ @contextlib.asynccontextmanager
42
+ async def get_graph(graph_name: str, config: Any, *, graphs: dict[str, Pregel]):
43
+ yield graphs[graph_name]
44
+
45
+
46
+ def _load_graphs(graphs: dict[str, Pregel]) -> tuple[dict[str, Pregel], dict[str, str]]:
47
+ """Load graphs and their subgraphs recursively in hierarchical order.
48
+
49
+ Args:
50
+ graphs: Dictionary of root graphs to load
51
+ """
52
+ # First, ensure all root graphs have unique names
53
+ _ensure_unique_root_names(graphs)
54
+ subgraph_map: dict[str, str] = {}
55
+
56
+ # Then, collect all subgraphs and mappings
57
+ all_subgraphs: dict[str, Pregel] = {}
58
+ subgraph_to_parent: dict[str, str] = {}
59
+
60
+ for root_graph in graphs.values():
61
+ subgraphs, mappings = _collect_subgraphs(root_graph, root_graph.name)
62
+ all_subgraphs.update(subgraphs)
63
+ subgraph_to_parent.update(mappings)
64
+
65
+ subgraph_map.update(subgraph_to_parent)
66
+
67
+ # Now build self.graphs in hierarchical order (parents before children)
68
+ for root_name in sorted(graphs.keys()):
69
+ _load_graph_and_children(
70
+ root_name, graphs, {**graphs, **all_subgraphs}, subgraph_map
71
+ )
72
+
73
+ _log_supported_graphs(graphs, subgraph_map)
74
+ return graphs, subgraph_map
75
+
76
+
77
+ def _ensure_unique_root_names(graphs: dict[str, Pregel]) -> None:
78
+ """Ensure all root graphs have unique names"""
79
+ seen_names = set()
80
+
81
+ for name in graphs:
82
+ if name in seen_names:
83
+ raise ValueError(
84
+ f"Root graph name conflict detected: {name}. Root graphs must have unique names"
85
+ )
86
+ seen_names.add(name)
87
+
88
+
89
+ def _collect_subgraphs(
90
+ graph: Pregel, namespace: str
91
+ ) -> tuple[dict[str, Pregel], dict[str, str]]:
92
+ """Recursively collect all subgraphs from a root graph"""
93
+ subgraphs = {}
94
+ mappings = {}
95
+
96
+ for idx, (node_name, subgraph) in enumerate(graph.get_subgraphs(recurse=False)):
97
+ # Generate subgraph name
98
+ subgraph.name = f"{namespace}{NS_SEP}{node_name}{NS_SEP}{idx}"
99
+
100
+ # Add this subgraph
101
+ subgraphs[subgraph.name] = subgraph
102
+ mappings[subgraph.name] = graph.name
103
+
104
+ # Recursively process this subgraph's children
105
+ nested_subgraphs, nested_mappings = _collect_subgraphs(subgraph, namespace)
106
+
107
+ subgraphs.update(nested_subgraphs)
108
+ mappings.update(nested_mappings)
109
+
110
+ return subgraphs, mappings
111
+
112
+
113
+ def _load_graph_and_children(
114
+ graph_name: str,
115
+ graphs: dict[str, Pregel],
116
+ all_graphs: dict[str, Pregel],
117
+ subgraph_map: dict[str, str],
118
+ ) -> None:
119
+ """Recursively add a graph and its children to self.graphs in order"""
120
+
121
+ # Add this graph to self.graphs (maintaining insertion order)
122
+ graphs[graph_name] = all_graphs[graph_name]
123
+
124
+ # Get direct children of this graph
125
+ children = [
126
+ child_name
127
+ for child_name, parent_name in subgraph_map.items()
128
+ if parent_name == graph_name
129
+ ]
130
+
131
+ # Add children in sorted order (for deterministic output)
132
+ for child_name in sorted(children):
133
+ _load_graph_and_children(child_name, graphs, all_graphs, subgraph_map)
134
+
135
+
136
+ def _log_supported_graphs(
137
+ graphs: dict[str, Pregel], subgraph_map: dict[str, str]
138
+ ) -> None:
139
+ """Log the complete graph hierarchy in a tree-like format."""
140
+ LOGGER.info("Loaded graphs:")
141
+
142
+ # Get root graphs
143
+ root_graphs = {name for name in graphs if name not in subgraph_map}
144
+
145
+ for root_name in sorted(root_graphs):
146
+ LOGGER.info(f" {root_name}")
147
+ _log_graph_children(root_name, subgraph_map, indent=2)
148
+
149
+
150
+ def _log_graph_children(
151
+ parent_name: str, subgraph_map: dict[str, str], *, indent: int = 0
152
+ ) -> None:
153
+ """Recursively log children of a graph with proper indentation."""
154
+ children = [
155
+ child for child, parent in subgraph_map.items() if parent == parent_name
156
+ ]
157
+
158
+ for child in sorted(children):
159
+ prefix = " " * indent + "└─ "
160
+ LOGGER.info(f"{prefix}{child}")
161
+ # Recursively log this child's children
162
+ _log_graph_children(child, subgraph_map, indent=indent + 1)