langgraph-api 0.4.1__py3-none-any.whl → 0.7.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.
Files changed (135) hide show
  1. langgraph_api/__init__.py +1 -1
  2. langgraph_api/api/__init__.py +111 -51
  3. langgraph_api/api/a2a.py +1610 -0
  4. langgraph_api/api/assistants.py +212 -89
  5. langgraph_api/api/mcp.py +3 -3
  6. langgraph_api/api/meta.py +52 -28
  7. langgraph_api/api/openapi.py +27 -17
  8. langgraph_api/api/profile.py +108 -0
  9. langgraph_api/api/runs.py +342 -195
  10. langgraph_api/api/store.py +19 -2
  11. langgraph_api/api/threads.py +209 -27
  12. langgraph_api/asgi_transport.py +14 -9
  13. langgraph_api/asyncio.py +14 -4
  14. langgraph_api/auth/custom.py +52 -37
  15. langgraph_api/auth/langsmith/backend.py +4 -3
  16. langgraph_api/auth/langsmith/client.py +13 -8
  17. langgraph_api/cli.py +230 -133
  18. langgraph_api/command.py +5 -3
  19. langgraph_api/config/__init__.py +532 -0
  20. langgraph_api/config/_parse.py +58 -0
  21. langgraph_api/config/schemas.py +431 -0
  22. langgraph_api/cron_scheduler.py +17 -1
  23. langgraph_api/encryption/__init__.py +15 -0
  24. langgraph_api/encryption/aes_json.py +158 -0
  25. langgraph_api/encryption/context.py +35 -0
  26. langgraph_api/encryption/custom.py +280 -0
  27. langgraph_api/encryption/middleware.py +632 -0
  28. langgraph_api/encryption/shared.py +63 -0
  29. langgraph_api/errors.py +12 -1
  30. langgraph_api/executor_entrypoint.py +11 -6
  31. langgraph_api/feature_flags.py +29 -0
  32. langgraph_api/graph.py +176 -76
  33. langgraph_api/grpc/client.py +313 -0
  34. langgraph_api/grpc/config_conversion.py +231 -0
  35. langgraph_api/grpc/generated/__init__.py +29 -0
  36. langgraph_api/grpc/generated/checkpointer_pb2.py +63 -0
  37. langgraph_api/grpc/generated/checkpointer_pb2.pyi +99 -0
  38. langgraph_api/grpc/generated/checkpointer_pb2_grpc.py +329 -0
  39. langgraph_api/grpc/generated/core_api_pb2.py +216 -0
  40. langgraph_api/grpc/generated/core_api_pb2.pyi +905 -0
  41. langgraph_api/grpc/generated/core_api_pb2_grpc.py +1621 -0
  42. langgraph_api/grpc/generated/engine_common_pb2.py +219 -0
  43. langgraph_api/grpc/generated/engine_common_pb2.pyi +722 -0
  44. langgraph_api/grpc/generated/engine_common_pb2_grpc.py +24 -0
  45. langgraph_api/grpc/generated/enum_cancel_run_action_pb2.py +37 -0
  46. langgraph_api/grpc/generated/enum_cancel_run_action_pb2.pyi +12 -0
  47. langgraph_api/grpc/generated/enum_cancel_run_action_pb2_grpc.py +24 -0
  48. langgraph_api/grpc/generated/enum_control_signal_pb2.py +37 -0
  49. langgraph_api/grpc/generated/enum_control_signal_pb2.pyi +16 -0
  50. langgraph_api/grpc/generated/enum_control_signal_pb2_grpc.py +24 -0
  51. langgraph_api/grpc/generated/enum_durability_pb2.py +37 -0
  52. langgraph_api/grpc/generated/enum_durability_pb2.pyi +16 -0
  53. langgraph_api/grpc/generated/enum_durability_pb2_grpc.py +24 -0
  54. langgraph_api/grpc/generated/enum_multitask_strategy_pb2.py +37 -0
  55. langgraph_api/grpc/generated/enum_multitask_strategy_pb2.pyi +16 -0
  56. langgraph_api/grpc/generated/enum_multitask_strategy_pb2_grpc.py +24 -0
  57. langgraph_api/grpc/generated/enum_run_status_pb2.py +37 -0
  58. langgraph_api/grpc/generated/enum_run_status_pb2.pyi +22 -0
  59. langgraph_api/grpc/generated/enum_run_status_pb2_grpc.py +24 -0
  60. langgraph_api/grpc/generated/enum_stream_mode_pb2.py +37 -0
  61. langgraph_api/grpc/generated/enum_stream_mode_pb2.pyi +28 -0
  62. langgraph_api/grpc/generated/enum_stream_mode_pb2_grpc.py +24 -0
  63. langgraph_api/grpc/generated/enum_thread_status_pb2.py +37 -0
  64. langgraph_api/grpc/generated/enum_thread_status_pb2.pyi +16 -0
  65. langgraph_api/grpc/generated/enum_thread_status_pb2_grpc.py +24 -0
  66. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.py +37 -0
  67. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.pyi +16 -0
  68. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2_grpc.py +24 -0
  69. langgraph_api/grpc/generated/errors_pb2.py +39 -0
  70. langgraph_api/grpc/generated/errors_pb2.pyi +21 -0
  71. langgraph_api/grpc/generated/errors_pb2_grpc.py +24 -0
  72. langgraph_api/grpc/ops/__init__.py +370 -0
  73. langgraph_api/grpc/ops/assistants.py +424 -0
  74. langgraph_api/grpc/ops/runs.py +792 -0
  75. langgraph_api/grpc/ops/threads.py +1013 -0
  76. langgraph_api/http.py +16 -5
  77. langgraph_api/http_metrics.py +15 -35
  78. langgraph_api/http_metrics_utils.py +38 -0
  79. langgraph_api/js/build.mts +1 -1
  80. langgraph_api/js/client.http.mts +13 -7
  81. langgraph_api/js/client.mts +2 -5
  82. langgraph_api/js/package.json +29 -28
  83. langgraph_api/js/remote.py +56 -30
  84. langgraph_api/js/src/graph.mts +20 -0
  85. langgraph_api/js/sse.py +2 -2
  86. langgraph_api/js/ui.py +1 -1
  87. langgraph_api/js/yarn.lock +1204 -1006
  88. langgraph_api/logging.py +29 -2
  89. langgraph_api/metadata.py +99 -28
  90. langgraph_api/middleware/http_logger.py +7 -2
  91. langgraph_api/middleware/private_network.py +7 -7
  92. langgraph_api/models/run.py +54 -93
  93. langgraph_api/otel_context.py +205 -0
  94. langgraph_api/patch.py +5 -3
  95. langgraph_api/queue_entrypoint.py +154 -65
  96. langgraph_api/route.py +47 -5
  97. langgraph_api/schema.py +88 -10
  98. langgraph_api/self_hosted_logs.py +124 -0
  99. langgraph_api/self_hosted_metrics.py +450 -0
  100. langgraph_api/serde.py +79 -37
  101. langgraph_api/server.py +138 -60
  102. langgraph_api/state.py +4 -3
  103. langgraph_api/store.py +25 -16
  104. langgraph_api/stream.py +80 -29
  105. langgraph_api/thread_ttl.py +31 -13
  106. langgraph_api/timing/__init__.py +25 -0
  107. langgraph_api/timing/profiler.py +200 -0
  108. langgraph_api/timing/timer.py +318 -0
  109. langgraph_api/utils/__init__.py +53 -8
  110. langgraph_api/utils/cache.py +47 -10
  111. langgraph_api/utils/config.py +2 -1
  112. langgraph_api/utils/errors.py +77 -0
  113. langgraph_api/utils/future.py +10 -6
  114. langgraph_api/utils/headers.py +76 -2
  115. langgraph_api/utils/retriable_client.py +74 -0
  116. langgraph_api/utils/stream_codec.py +315 -0
  117. langgraph_api/utils/uuids.py +29 -62
  118. langgraph_api/validation.py +9 -0
  119. langgraph_api/webhook.py +120 -6
  120. langgraph_api/worker.py +55 -24
  121. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/METADATA +16 -8
  122. langgraph_api-0.7.3.dist-info/RECORD +168 -0
  123. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/WHEEL +1 -1
  124. langgraph_runtime/__init__.py +1 -0
  125. langgraph_runtime/routes.py +11 -0
  126. logging.json +1 -3
  127. openapi.json +839 -478
  128. langgraph_api/config.py +0 -387
  129. langgraph_api/js/isolate-0x130008000-46649-46649-v8.log +0 -4430
  130. langgraph_api/js/isolate-0x138008000-44681-44681-v8.log +0 -4430
  131. langgraph_api/js/package-lock.json +0 -3308
  132. langgraph_api-0.4.1.dist-info/RECORD +0 -107
  133. /langgraph_api/{utils.py → grpc/__init__.py} +0 -0
  134. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/entry_points.txt +0 -0
  135. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/licenses/LICENSE +0 -0
@@ -23,10 +23,12 @@ from starlette.exceptions import HTTPException
23
23
  from starlette.requests import HTTPConnection, Request
24
24
  from starlette.responses import Response
25
25
 
26
+ from langgraph_api import timing
26
27
  from langgraph_api.auth.langsmith.backend import LangsmithAuthBackend
27
28
  from langgraph_api.auth.studio_user import StudioUser
28
29
  from langgraph_api.config import LANGGRAPH_AUTH, LANGGRAPH_AUTH_TYPE
29
30
  from langgraph_api.js.base import is_js_path
31
+ from langgraph_api.timing import profiled_import
30
32
 
31
33
  logger = structlog.stdlib.get_logger(__name__)
32
34
 
@@ -233,7 +235,7 @@ def _get_custom_auth_middleware(
233
235
  auth_instance._authenticate_handler,
234
236
  disable_studio_auth,
235
237
  )
236
- logger.info(f"Loaded custom auth middleware: {str(result)}")
238
+ logger.info(f"Loaded custom auth middleware: {result!s}")
237
239
  return result
238
240
 
239
241
 
@@ -355,34 +357,39 @@ def _solve_fastapi_dependencies(
355
357
  }
356
358
 
357
359
  async def decorator(scope: dict, request: Request):
358
- async with AsyncExitStack() as stack:
359
- all_solved = await asyncio.gather(
360
- *(
361
- solve_dependencies(
362
- request=request,
363
- dependant=dependent,
364
- async_exit_stack=stack,
365
- embed_body_fields=False,
360
+ async with AsyncExitStack() as request_stack:
361
+ scope["fastapi_inner_astack"] = request_stack
362
+ async with AsyncExitStack() as stack:
363
+ scope["fastapi_function_astack"] = stack
364
+ all_solved = await asyncio.gather(
365
+ *(
366
+ solve_dependencies(
367
+ request=request,
368
+ dependant=dependent,
369
+ async_exit_stack=stack,
370
+ embed_body_fields=False,
371
+ )
372
+ for dependent in dependents.values()
366
373
  )
367
- for dependent in dependents.values()
368
374
  )
369
- )
370
- all_injected = await asyncio.gather(
371
- *(
372
- _run_async(dependent.call, solved.values, is_async)
373
- for dependent, solved in zip(
374
- dependents.values(), all_solved, strict=False
375
+ all_injected = await asyncio.gather(
376
+ *(
377
+ _run_async(dependent.call, solved.values, is_async)
378
+ for dependent, solved in zip(
379
+ dependents.values(), all_solved, strict=False
380
+ )
375
381
  )
376
382
  )
377
- )
378
- kwargs = {
379
- name: value
380
- for name, value in zip(dependents.keys(), all_injected, strict=False)
381
- }
382
- other_params = _extract_arguments_from_scope(
383
- scope, _param_names, request=request
384
- )
385
- return await fn(**(kwargs | other_params))
383
+ kwargs = {
384
+ name: value
385
+ for name, value in zip(
386
+ dependents.keys(), all_injected, strict=False
387
+ )
388
+ }
389
+ other_params = _extract_arguments_from_scope(
390
+ scope, _param_names, request=request
391
+ )
392
+ return await fn(**(kwargs | other_params))
386
393
 
387
394
  return decorator
388
395
 
@@ -580,6 +587,13 @@ def normalize_user(user: Any) -> BaseUser:
580
587
  )
581
588
 
582
589
 
590
+ @timing.timer(
591
+ message="Loading custom auth {auth_path}",
592
+ metadata_fn=lambda auth_path: {"auth_path": auth_path},
593
+ warn_threshold_secs=5,
594
+ warn_message="Loading custom auth '{auth_path}' took longer than expected",
595
+ error_threshold_secs=10,
596
+ )
583
597
  def _load_auth_obj(path: str) -> Auth | Literal["js"]:
584
598
  """Load an object from a path string."""
585
599
  if ":" not in path:
@@ -595,18 +609,19 @@ def _load_auth_obj(path: str) -> Auth | Literal["js"]:
595
609
  return "js"
596
610
 
597
611
  try:
598
- if "/" in module_name or ".py" in module_name:
599
- # Load from file path
600
- modname = f"dynamic_module_{hash(module_name)}"
601
- modspec = importlib.util.spec_from_file_location(modname, module_name)
602
- if modspec is None or modspec.loader is None:
603
- raise ValueError(f"Could not load file: {module_name}")
604
- module = importlib.util.module_from_spec(modspec)
605
- sys.modules[modname] = module
606
- modspec.loader.exec_module(module) # type: ignore[possibly-unbound-attribute]
607
- else:
608
- # Load from Python module
609
- module = importlib.import_module(module_name)
612
+ with profiled_import(path):
613
+ if "/" in module_name or ".py" in module_name:
614
+ # Load from file path
615
+ modname = f"dynamic_module_{hash(module_name)}"
616
+ modspec = importlib.util.spec_from_file_location(modname, module_name)
617
+ if modspec is None or modspec.loader is None:
618
+ raise ValueError(f"Could not load file: {module_name}")
619
+ module = importlib.util.module_from_spec(modspec)
620
+ sys.modules[modname] = module
621
+ modspec.loader.exec_module(module) # type: ignore[possibly-unbound-attribute]
622
+ else:
623
+ # Load from Python module
624
+ module = importlib.import_module(module_name)
610
625
 
611
626
  loaded_auth = getattr(module, callable_name, None)
612
627
  if loaded_auth is None:
@@ -30,10 +30,11 @@ class AuthCacheEntry(TypedDict):
30
30
 
31
31
 
32
32
  class LangsmithAuthBackend(AuthenticationBackend):
33
- def __init__(self):
33
+ def __init__(self, *, base_url: str | None = None):
34
34
  from langgraph_api.utils.cache import LRUCache
35
35
 
36
36
  self._cache = LRUCache[AuthCacheEntry](max_size=1000, ttl=60)
37
+ self._base_url = base_url
37
38
 
38
39
  def _get_cache_key(self, headers):
39
40
  """Generate cache key from authentication headers"""
@@ -58,10 +59,10 @@ class LangsmithAuthBackend(AuthenticationBackend):
58
59
 
59
60
  # Check cache first
60
61
  cache_key = self._get_cache_key(headers)
61
- if cached_entry := self._cache.get(cache_key):
62
+ if cached_entry := await self._cache.get(cache_key):
62
63
  return cached_entry["credentials"], cached_entry["user"]
63
64
 
64
- async with auth_client() as auth:
65
+ async with auth_client(base_url=self._base_url) as auth:
65
66
  if not LANGSMITH_AUTH_VERIFY_TENANT_ID and not conn.headers.get(
66
67
  "x-api-key"
67
68
  ):
@@ -85,8 +85,10 @@ class JsonHttpClient:
85
85
  )
86
86
 
87
87
 
88
- def create_client() -> JsonHttpClient:
88
+ def create_client(base_url: str | None = None) -> JsonHttpClient:
89
89
  """Create the auth http client."""
90
+ url = base_url if base_url is not None else LANGSMITH_AUTH_ENDPOINT
91
+
90
92
  return JsonHttpClient(
91
93
  httpx.AsyncClient(
92
94
  transport=httpx.AsyncHTTPTransport(
@@ -97,7 +99,7 @@ def create_client() -> JsonHttpClient:
97
99
  ),
98
100
  ),
99
101
  timeout=httpx.Timeout(2.0),
100
- base_url=LANGSMITH_AUTH_ENDPOINT,
102
+ base_url=url,
101
103
  )
102
104
  )
103
105
 
@@ -109,20 +111,23 @@ async def close_auth_client() -> None:
109
111
  await _client.client.aclose()
110
112
 
111
113
 
112
- async def initialize_auth_client() -> None:
114
+ async def initialize_auth_client(base_url: str | None = None) -> None:
113
115
  """Initialize the auth http client."""
114
116
  await close_auth_client()
115
117
  global _client
116
- _client = create_client()
118
+ _client = create_client(base_url=base_url)
117
119
 
118
120
 
119
121
  @asynccontextmanager
120
- async def auth_client() -> AsyncGenerator[JsonHttpClient, None]:
122
+ async def auth_client(
123
+ base_url: str | None = None,
124
+ ) -> AsyncGenerator[JsonHttpClient, None]:
121
125
  """Get the auth http client."""
126
+ url = base_url if base_url is not None else LANGSMITH_AUTH_ENDPOINT
122
127
  # pytest does something funny with event loops,
123
128
  # so we can't use a global pool for tests
124
- if LANGSMITH_AUTH_ENDPOINT.startswith("http://localhost"):
125
- client = create_client()
129
+ if url.startswith("http://localhost"):
130
+ client = create_client(base_url=url)
126
131
  try:
127
132
  yield client
128
133
  finally:
@@ -135,5 +140,5 @@ async def auth_client() -> AsyncGenerator[JsonHttpClient, None]:
135
140
  if found:
136
141
  yield _client
137
142
  else:
138
- await initialize_auth_client()
143
+ await initialize_auth_client(base_url=url)
139
144
  yield _client