langgraph-api 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl

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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.5.2"
1
+ __version__ = "0.5.3"
@@ -0,0 +1,225 @@
1
+ """Conversion utils for the RunnableConfig."""
2
+
3
+ # THIS IS DUPLICATED
4
+ # TODO: WFH - Deduplicate with the executor logic by moving into a separate package
5
+ # Sequencing in the next PR.
6
+ from typing import Any, cast
7
+
8
+ import orjson
9
+ from langchain_core.runnables.config import RunnableConfig
10
+
11
+ from langgraph_api.grpc_ops.generated import engine_common_pb2
12
+
13
+ CONFIG_KEY_SEND = "__pregel_send"
14
+ CONFIG_KEY_READ = "__pregel_read"
15
+ CONFIG_KEY_RESUMING = "__pregel_resuming"
16
+ CONFIG_KEY_TASK_ID = "__pregel_task_id"
17
+ CONFIG_KEY_THREAD_ID = "thread_id"
18
+ CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map"
19
+ CONFIG_KEY_CHECKPOINT_ID = "checkpoint_id"
20
+ CONFIG_KEY_CHECKPOINT_NS = "checkpoint_ns"
21
+ CONFIG_KEY_SCRATCHPAD = "__pregel_scratchpad"
22
+ CONFIG_KEY_DURABILITY = "__pregel_durability"
23
+ CONFIG_KEY_GRAPH_ID = "graph_id"
24
+
25
+
26
+ def _durability_to_proto(
27
+ durability: str,
28
+ ) -> engine_common_pb2.Durability:
29
+ match durability:
30
+ case "async":
31
+ return engine_common_pb2.Durability.ASYNC
32
+ case "sync":
33
+ return engine_common_pb2.Durability.SYNC
34
+ case "exit":
35
+ return engine_common_pb2.Durability.EXIT
36
+ case _:
37
+ raise ValueError(f"invalid durability: {durability}")
38
+
39
+
40
+ def _durability_from_proto(
41
+ durability: engine_common_pb2.Durability,
42
+ ) -> str:
43
+ match durability:
44
+ case engine_common_pb2.Durability.ASYNC:
45
+ return "async"
46
+ case engine_common_pb2.Durability.SYNC:
47
+ return "sync"
48
+ case engine_common_pb2.Durability.EXIT:
49
+ return "exit"
50
+ case _:
51
+ raise ValueError(f"invalid durability: {durability}")
52
+
53
+
54
+ def config_to_proto(
55
+ config: RunnableConfig,
56
+ ) -> engine_common_pb2.EngineRunnableConfig | None:
57
+ # Prepare kwargs for construction
58
+ if not config:
59
+ return None
60
+ cp = {**config}
61
+ pb_config = engine_common_pb2.EngineRunnableConfig()
62
+ for k, v in (cp.pop("metadata", None) or {}).items():
63
+ if k == "run_attempt":
64
+ pb_config.run_attempt = v
65
+ elif k == "run_id":
66
+ pb_config.server_run_id = str(v)
67
+ else:
68
+ pb_config.metadata_json[k] = orjson.dumps(v)
69
+ if run_name := cp.pop("run_name", None):
70
+ pb_config.run_name = run_name
71
+
72
+ if run_id := cp.pop("run_id", None):
73
+ pb_config.run_id = str(run_id)
74
+
75
+ if (max_concurrency := cp.pop("max_concurrency", None)) and isinstance(
76
+ max_concurrency, int
77
+ ):
78
+ pb_config.max_concurrency = max_concurrency
79
+
80
+ if (recursion_limit := cp.pop("recursion_limit", None)) and isinstance(
81
+ recursion_limit, int
82
+ ):
83
+ pb_config.recursion_limit = recursion_limit
84
+
85
+ # Handle collections after construction
86
+ if (tags := cp.pop("tags", None)) and isinstance(tags, list):
87
+ pb_config.tags.extend(tags)
88
+
89
+ if (configurable := cp.pop("configurable", None)) and isinstance(
90
+ configurable, dict
91
+ ):
92
+ _inject_configurable_into_proto(configurable, pb_config)
93
+ if cp:
94
+ pb_config.extra_json.update({k: orjson.dumps(v) for k, v in cp.items()})
95
+
96
+ return pb_config
97
+
98
+
99
+ RESTRICTED_RESERVED_CONFIGURABLE_KEYS = {
100
+ CONFIG_KEY_SEND,
101
+ CONFIG_KEY_READ,
102
+ CONFIG_KEY_SCRATCHPAD,
103
+ }
104
+
105
+
106
+ def _inject_configurable_into_proto(
107
+ configurable: dict[str, Any], proto: engine_common_pb2.EngineRunnableConfig
108
+ ) -> None:
109
+ extra = {}
110
+ for key, value in configurable.items():
111
+ if key == CONFIG_KEY_RESUMING:
112
+ proto.resuming = bool(value)
113
+ elif key == CONFIG_KEY_TASK_ID:
114
+ proto.task_id = str(value)
115
+ elif key == CONFIG_KEY_THREAD_ID:
116
+ proto.thread_id = str(value)
117
+ elif key == CONFIG_KEY_CHECKPOINT_MAP:
118
+ proto.checkpoint_map.update(cast(dict[str, str], value))
119
+ elif key == CONFIG_KEY_CHECKPOINT_ID:
120
+ proto.checkpoint_id = str(value)
121
+ elif key == CONFIG_KEY_CHECKPOINT_NS:
122
+ proto.checkpoint_ns = str(value)
123
+ elif key == CONFIG_KEY_DURABILITY and value:
124
+ proto.durability = _durability_to_proto(value)
125
+ elif key not in RESTRICTED_RESERVED_CONFIGURABLE_KEYS:
126
+ extra[key] = value
127
+ if extra:
128
+ proto.extra_configurable_json.update(
129
+ {k: orjson.dumps(v) for k, v in extra.items()}
130
+ )
131
+
132
+
133
+ def context_to_json_bytes(context: dict[str, Any] | Any) -> bytes | None:
134
+ """Convert context to JSON bytes for proto serialization."""
135
+ if context is None:
136
+ return None
137
+
138
+ # Convert dataclass or other objects to dict if needed
139
+ if hasattr(context, "__dict__") and not hasattr(context, "items"):
140
+ # Convert dataclass to dict
141
+ context_dict = context.__dict__
142
+ elif hasattr(context, "items"):
143
+ # Already a dict-like object
144
+ context_dict = dict(context)
145
+ else:
146
+ # Try to convert to dict using vars()
147
+ context_dict = vars(context) if hasattr(context, "__dict__") else {}
148
+
149
+ return orjson.dumps(context_dict)
150
+
151
+
152
+ def config_from_proto(
153
+ config_proto: engine_common_pb2.EngineRunnableConfig | None,
154
+ ) -> RunnableConfig:
155
+ if not config_proto:
156
+ return RunnableConfig(tags=[], metadata={}, configurable={})
157
+
158
+ configurable = _configurable_from_proto(config_proto)
159
+
160
+ metadata = {}
161
+ for k, v in config_proto.metadata_json.items():
162
+ metadata[k] = orjson.loads(v)
163
+ if config_proto.HasField("run_attempt"):
164
+ metadata["run_attempt"] = config_proto.run_attempt
165
+ if config_proto.HasField("server_run_id"):
166
+ metadata["run_id"] = config_proto.server_run_id
167
+
168
+ config = RunnableConfig()
169
+ if config_proto.extra_json:
170
+ for k, v in config_proto.extra_json.items():
171
+ config[k] = orjson.loads(v) # type: ignore[invalid-key]
172
+ if config_proto.tags:
173
+ config["tags"] = list(config_proto.tags)
174
+ if metadata:
175
+ config["metadata"] = metadata
176
+ if configurable:
177
+ config["configurable"] = configurable
178
+ if config_proto.HasField("run_name"):
179
+ config["run_name"] = config_proto.run_name
180
+
181
+ if config_proto.HasField("max_concurrency"):
182
+ config["max_concurrency"] = config_proto.max_concurrency
183
+
184
+ if config_proto.HasField("recursion_limit"):
185
+ config["recursion_limit"] = config_proto.recursion_limit
186
+
187
+ return config
188
+
189
+
190
+ def _configurable_from_proto(
191
+ config_proto: engine_common_pb2.EngineRunnableConfig,
192
+ ) -> dict[str, Any]:
193
+ configurable = {}
194
+
195
+ if config_proto.HasField("resuming"):
196
+ configurable[CONFIG_KEY_RESUMING] = config_proto.resuming
197
+
198
+ if config_proto.HasField("task_id"):
199
+ configurable[CONFIG_KEY_TASK_ID] = config_proto.task_id
200
+
201
+ if config_proto.HasField("thread_id"):
202
+ configurable[CONFIG_KEY_THREAD_ID] = config_proto.thread_id
203
+
204
+ if config_proto.HasField("checkpoint_id"):
205
+ configurable[CONFIG_KEY_CHECKPOINT_ID] = config_proto.checkpoint_id
206
+
207
+ if config_proto.HasField("checkpoint_ns"):
208
+ configurable[CONFIG_KEY_CHECKPOINT_NS] = config_proto.checkpoint_ns
209
+
210
+ if config_proto.HasField("durability"):
211
+ durability = _durability_from_proto(config_proto.durability)
212
+ if durability:
213
+ configurable[CONFIG_KEY_DURABILITY] = durability
214
+
215
+ if config_proto.HasField("graph_id"):
216
+ configurable[CONFIG_KEY_GRAPH_ID] = config_proto.graph_id
217
+
218
+ if len(config_proto.checkpoint_map) > 0:
219
+ configurable[CONFIG_KEY_CHECKPOINT_MAP] = dict(config_proto.checkpoint_map)
220
+
221
+ if len(config_proto.extra_configurable_json) > 0:
222
+ for k, v in config_proto.extra_configurable_json.items():
223
+ configurable[k] = orjson.loads(v)
224
+
225
+ return configurable