langgraph-api 0.4.37__py3-none-any.whl → 0.4.39__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 +1 -1
- langgraph_api/api/meta.py +2 -1
- langgraph_api/auth/langsmith/backend.py +3 -2
- langgraph_api/auth/langsmith/client.py +13 -8
- langgraph_api/config.py +25 -11
- langgraph_api/http_metrics.py +1 -1
- langgraph_api/js/client.http.mts +12 -6
- langgraph_api/logging.py +24 -0
- langgraph_api/metadata.py +5 -5
- langgraph_api/self_hosted_logs.py +124 -0
- langgraph_api/self_hosted_metrics.py +9 -9
- {langgraph_api-0.4.37.dist-info → langgraph_api-0.4.39.dist-info}/METADATA +1 -1
- {langgraph_api-0.4.37.dist-info → langgraph_api-0.4.39.dist-info}/RECORD +16 -15
- {langgraph_api-0.4.37.dist-info → langgraph_api-0.4.39.dist-info}/WHEEL +0 -0
- {langgraph_api-0.4.37.dist-info → langgraph_api-0.4.39.dist-info}/entry_points.txt +0 -0
- {langgraph_api-0.4.37.dist-info → langgraph_api-0.4.39.dist-info}/licenses/LICENSE +0 -0
langgraph_api/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.4.
|
|
1
|
+
__version__ = "0.4.39"
|
langgraph_api/api/meta.py
CHANGED
|
@@ -24,7 +24,8 @@ async def meta_info(request: ApiRequest):
|
|
|
24
24
|
"flags": {
|
|
25
25
|
"assistants": True,
|
|
26
26
|
"crons": plus and config.FF_CRONS_ENABLED,
|
|
27
|
-
"langsmith": bool(config.
|
|
27
|
+
"langsmith": bool(config.LANGSMITH_CONTROL_PLANE_API_KEY)
|
|
28
|
+
and bool(config.TRACING),
|
|
28
29
|
"langsmith_tracing_replicas": True,
|
|
29
30
|
},
|
|
30
31
|
"host": {
|
|
@@ -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"""
|
|
@@ -61,7 +62,7 @@ class LangsmithAuthBackend(AuthenticationBackend):
|
|
|
61
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=
|
|
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(
|
|
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
|
|
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
|
langgraph_api/config.py
CHANGED
|
@@ -334,11 +334,21 @@ LANGGRAPH_CLOUD_LICENSE_KEY = env("LANGGRAPH_CLOUD_LICENSE_KEY", cast=str, defau
|
|
|
334
334
|
LANGSMITH_API_KEY = env(
|
|
335
335
|
"LANGSMITH_API_KEY", cast=str, default=getenv("LANGCHAIN_API_KEY", "")
|
|
336
336
|
)
|
|
337
|
+
# LANGSMITH_CONTROL_PLANE_API_KEY is used for license verification and
|
|
338
|
+
# submitting usage metadata to LangSmith SaaS.
|
|
339
|
+
#
|
|
340
|
+
# Use case: A self-hosted deployment can configure LANGSMITH_API_KEY
|
|
341
|
+
# from a self-hosted LangSmith instance (i.e. trace to self-hosted
|
|
342
|
+
# LangSmith) and configure LANGSMITH_CONTROL_PLANE_API_KEY from LangSmith SaaS
|
|
343
|
+
# to facilitate license key verification and metadata submission.
|
|
344
|
+
LANGSMITH_CONTROL_PLANE_API_KEY = env(
|
|
345
|
+
"LANGSMITH_CONTROL_PLANE_API_KEY", cast=str, default=LANGSMITH_API_KEY
|
|
346
|
+
)
|
|
337
347
|
|
|
338
348
|
# if langsmith api key is set, enable tracing unless explicitly disabled
|
|
339
349
|
|
|
340
350
|
if (
|
|
341
|
-
|
|
351
|
+
LANGSMITH_CONTROL_PLANE_API_KEY
|
|
342
352
|
and not getenv("LANGCHAIN_TRACING_V2")
|
|
343
353
|
and not getenv("LANGCHAIN_TRACING")
|
|
344
354
|
and not getenv("LANGSMITH_TRACING_V2")
|
|
@@ -353,9 +363,12 @@ TRACING = (
|
|
|
353
363
|
or env("LANGSMITH_TRACING", cast=bool, default=None)
|
|
354
364
|
)
|
|
355
365
|
|
|
356
|
-
# if variant is "licensed", update to "local" if using
|
|
366
|
+
# if variant is "licensed", update to "local" if using LANGSMITH_CONTROL_PLANE_API_KEY instead
|
|
357
367
|
|
|
358
|
-
if
|
|
368
|
+
if (
|
|
369
|
+
getenv("LANGSMITH_LANGGRAPH_API_VARIANT") == "licensed"
|
|
370
|
+
and LANGSMITH_CONTROL_PLANE_API_KEY
|
|
371
|
+
):
|
|
359
372
|
environ["LANGSMITH_LANGGRAPH_API_VARIANT"] = "local"
|
|
360
373
|
|
|
361
374
|
|
|
@@ -375,15 +388,16 @@ API_VARIANT = env("LANGSMITH_LANGGRAPH_API_VARIANT", cast=str, default="")
|
|
|
375
388
|
# UI
|
|
376
389
|
UI_USE_BUNDLER = env("LANGGRAPH_UI_BUNDLER", cast=bool, default=False)
|
|
377
390
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
"SELF_HOSTED_METRICS_ENDPOINT", cast=str, default=None
|
|
383
|
-
)
|
|
384
|
-
SELF_HOSTED_METRICS_EXPORT_INTERVAL_MS = env(
|
|
385
|
-
"SELF_HOSTED_METRICS_EXPORT_INTERVAL_MS", cast=int, default=60000
|
|
391
|
+
LANGGRAPH_METRICS_ENABLED = env("LANGGRAPH_METRICS_ENABLED", cast=bool, default=False)
|
|
392
|
+
LANGGRAPH_METRICS_ENDPOINT = env("LANGGRAPH_METRICS_ENDPOINT", cast=str, default=None)
|
|
393
|
+
LANGGRAPH_METRICS_EXPORT_INTERVAL_MS = env(
|
|
394
|
+
"LANGGRAPH_METRICS_EXPORT_INTERVAL_MS", cast=int, default=60000
|
|
386
395
|
)
|
|
396
|
+
LANGGRAPH_LOGS_ENDPOINT = env("LANGGRAPH_LOGS_ENDPOINT", cast=str, default=None)
|
|
397
|
+
LANGGRAPH_LOGS_ENABLED = env("LANGGRAPH_LOGS_ENABLED", cast=bool, default=False)
|
|
398
|
+
|
|
399
|
+
SELF_HOSTED_OBSERVABILITY_SERVICE_NAME = "LGP_Self_Hosted"
|
|
400
|
+
|
|
387
401
|
IS_QUEUE_ENTRYPOINT = False
|
|
388
402
|
IS_EXECUTOR_ENTRYPOINT = False
|
|
389
403
|
ref_sha = None
|
langgraph_api/http_metrics.py
CHANGED
|
@@ -70,7 +70,7 @@ class HTTPMetricsCollector:
|
|
|
70
70
|
hist_data["count"] += 1
|
|
71
71
|
|
|
72
72
|
try:
|
|
73
|
-
if config.
|
|
73
|
+
if config.LANGGRAPH_METRICS_ENABLED:
|
|
74
74
|
from langgraph_api.self_hosted_metrics import record_http_request
|
|
75
75
|
|
|
76
76
|
record_http_request(method, route_path, status, latency_seconds)
|
langgraph_api/js/client.http.mts
CHANGED
|
@@ -47,7 +47,9 @@ const wrapHonoApp = (app: Hono) => {
|
|
|
47
47
|
// b/c the user's Hono version might be different than ours.
|
|
48
48
|
// See warning here: https://hono.dev/docs/guides/middleware#built-in-middleware
|
|
49
49
|
const newApp = new (Object.getPrototypeOf(app).constructor)() as Hono<{
|
|
50
|
-
Variables: {
|
|
50
|
+
Variables: {
|
|
51
|
+
"langgraph:body": string | ArrayBuffer | ReadableStream | null;
|
|
52
|
+
};
|
|
51
53
|
}>;
|
|
52
54
|
|
|
53
55
|
// This endpoint is used to check if we can yield the routing to the Python server early.
|
|
@@ -71,11 +73,15 @@ const wrapHonoApp = (app: Hono) => {
|
|
|
71
73
|
newApp.notFound(async (c) => {
|
|
72
74
|
// Send the request body back to the Python server
|
|
73
75
|
// Use the cached body in-case the user mutated the body
|
|
74
|
-
let payload:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
let payload: string | ArrayBuffer | ReadableStream | null =
|
|
77
|
+
c.get("langgraph:body");
|
|
78
|
+
|
|
79
|
+
if (payload == null) {
|
|
80
|
+
try {
|
|
81
|
+
payload = JSON.stringify(await c.req.json()) ?? null;
|
|
82
|
+
} catch {
|
|
83
|
+
// pass
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
return c.body(payload, {
|
langgraph_api/logging.py
CHANGED
|
@@ -120,6 +120,30 @@ class JSONRenderer:
|
|
|
120
120
|
return json_dumpb(event_dict).decode()
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
# same as Formatter, but always uses JSONRenderer. Used by OTLP log handler for self hosted logging
|
|
124
|
+
class OTLPFormatter(structlog.stdlib.ProcessorFormatter):
|
|
125
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
126
|
+
if len(args) == 3:
|
|
127
|
+
fmt, datefmt, style = args
|
|
128
|
+
kwargs["fmt"] = fmt
|
|
129
|
+
kwargs["datefmt"] = datefmt
|
|
130
|
+
kwargs["style"] = style
|
|
131
|
+
else:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
f"OTLPFormatter expected 3 positional arguments (fmt, datefmt, style), "
|
|
134
|
+
f"but got {len(args)} arguments."
|
|
135
|
+
)
|
|
136
|
+
super().__init__(
|
|
137
|
+
processors=[
|
|
138
|
+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|
139
|
+
AddLoggingContext(),
|
|
140
|
+
JSONRenderer(),
|
|
141
|
+
],
|
|
142
|
+
foreign_pre_chain=shared_processors,
|
|
143
|
+
**kwargs,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
123
147
|
LEVELS = logging.getLevelNamesMapping()
|
|
124
148
|
|
|
125
149
|
|
langgraph_api/metadata.py
CHANGED
|
@@ -11,8 +11,8 @@ import langgraph_api.config as config
|
|
|
11
11
|
from langgraph_api.auth.custom import get_auth_instance
|
|
12
12
|
from langgraph_api.config import (
|
|
13
13
|
LANGGRAPH_CLOUD_LICENSE_KEY,
|
|
14
|
-
LANGSMITH_API_KEY,
|
|
15
14
|
LANGSMITH_AUTH_ENDPOINT,
|
|
15
|
+
LANGSMITH_CONTROL_PLANE_API_KEY,
|
|
16
16
|
USES_CUSTOM_APP,
|
|
17
17
|
USES_CUSTOM_AUTH,
|
|
18
18
|
USES_INDEXING,
|
|
@@ -121,13 +121,13 @@ async def metadata_loop() -> None:
|
|
|
121
121
|
from langgraph_api import __version__
|
|
122
122
|
except ImportError:
|
|
123
123
|
__version__ = None
|
|
124
|
-
if not LANGGRAPH_CLOUD_LICENSE_KEY and not
|
|
124
|
+
if not LANGGRAPH_CLOUD_LICENSE_KEY and not LANGSMITH_CONTROL_PLANE_API_KEY:
|
|
125
125
|
return
|
|
126
126
|
|
|
127
127
|
if (
|
|
128
128
|
LANGGRAPH_CLOUD_LICENSE_KEY
|
|
129
129
|
and not LANGGRAPH_CLOUD_LICENSE_KEY.startswith("lcl_")
|
|
130
|
-
and not
|
|
130
|
+
and not LANGSMITH_CONTROL_PLANE_API_KEY
|
|
131
131
|
):
|
|
132
132
|
logger.info("Running in air-gapped mode, skipping metadata loop")
|
|
133
133
|
return
|
|
@@ -204,10 +204,10 @@ async def metadata_loop() -> None:
|
|
|
204
204
|
)
|
|
205
205
|
|
|
206
206
|
# 2. Send to langchain auth endpoint if API key is set
|
|
207
|
-
if
|
|
207
|
+
if LANGSMITH_CONTROL_PLANE_API_KEY and LANGCHAIN_METADATA_ENDPOINT:
|
|
208
208
|
langchain_payload = {
|
|
209
209
|
**base_payload,
|
|
210
|
-
"api_key":
|
|
210
|
+
"api_key": LANGSMITH_CONTROL_PLANE_API_KEY,
|
|
211
211
|
}
|
|
212
212
|
submissions_attempted.append("langchain")
|
|
213
213
|
try:
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
|
7
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
8
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
9
|
+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
|
10
|
+
from opentelemetry.util.types import Attributes
|
|
11
|
+
|
|
12
|
+
from langgraph_api import config
|
|
13
|
+
from langgraph_api.logging import OTLPFormatter
|
|
14
|
+
|
|
15
|
+
logger = structlog.stdlib.get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
_logger_provider = None
|
|
18
|
+
_customer_attributes = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# see https://github.com/open-telemetry/opentelemetry-python/issues/3649 for why we need this
|
|
22
|
+
class AttrFilteredLoggingHandler(LoggingHandler):
|
|
23
|
+
DROP_ATTRIBUTES = ["_logger"]
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _get_attributes(record: logging.LogRecord) -> Attributes:
|
|
27
|
+
base_attributes = LoggingHandler._get_attributes(record)
|
|
28
|
+
attributes = {
|
|
29
|
+
k: v
|
|
30
|
+
for k, v in base_attributes.items()
|
|
31
|
+
if k not in AttrFilteredLoggingHandler.DROP_ATTRIBUTES
|
|
32
|
+
}
|
|
33
|
+
if _customer_attributes:
|
|
34
|
+
attributes.update(_customer_attributes)
|
|
35
|
+
return cast(Attributes, attributes)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def initialize_self_hosted_logs() -> None:
|
|
39
|
+
global _logger_provider
|
|
40
|
+
|
|
41
|
+
if not config.LANGGRAPH_LOGS_ENABLED:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
if not config.LANGGRAPH_LOGS_ENDPOINT:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
"LANGGRAPH_LOGS_ENABLED is true but no LANGGRAPH_LOGS_ENDPOINT is configured"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# For now, this is only enabled for fully self-hosted customers
|
|
50
|
+
# We will need to update the otel collector auth model to support hybrid customers
|
|
51
|
+
if not config.LANGGRAPH_CLOUD_LICENSE_KEY:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"Self-hosted logs require a license key, and do not work with hybrid deployments yet."
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
resource_attributes = {
|
|
59
|
+
SERVICE_NAME: config.SELF_HOSTED_OBSERVABILITY_SERVICE_NAME,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if config.LANGGRAPH_CLOUD_LICENSE_KEY:
|
|
63
|
+
try:
|
|
64
|
+
from langgraph_license.validation import (
|
|
65
|
+
CUSTOMER_ID, # type: ignore[unresolved-import]
|
|
66
|
+
CUSTOMER_NAME, # type: ignore[unresolved-import]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if CUSTOMER_ID:
|
|
70
|
+
_customer_attributes["customer_id"] = CUSTOMER_ID
|
|
71
|
+
if CUSTOMER_NAME:
|
|
72
|
+
_customer_attributes["customer_name"] = CUSTOMER_NAME
|
|
73
|
+
|
|
74
|
+
# resolves to pod name in k8s, or container id in docker
|
|
75
|
+
instance_id = os.environ.get("HOSTNAME")
|
|
76
|
+
if instance_id:
|
|
77
|
+
_customer_attributes["instance_id"] = instance_id
|
|
78
|
+
except ImportError:
|
|
79
|
+
pass
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning("Failed to get customer info from license", exc_info=e)
|
|
82
|
+
|
|
83
|
+
if config.IS_QUEUE_ENTRYPOINT:
|
|
84
|
+
_customer_attributes["entrypoint"] = "queue"
|
|
85
|
+
elif config.IS_EXECUTOR_ENTRYPOINT:
|
|
86
|
+
_customer_attributes["entrypoint"] = "executor"
|
|
87
|
+
else:
|
|
88
|
+
_customer_attributes["entrypoint"] = "api"
|
|
89
|
+
|
|
90
|
+
_logger_provider = LoggerProvider(
|
|
91
|
+
resource=Resource.create(resource_attributes),
|
|
92
|
+
)
|
|
93
|
+
_logger_provider.add_log_record_processor(
|
|
94
|
+
BatchLogRecordProcessor(
|
|
95
|
+
OTLPLogExporter(
|
|
96
|
+
endpoint=config.LANGGRAPH_LOGS_ENDPOINT,
|
|
97
|
+
headers={
|
|
98
|
+
"X-Langchain-License-Key": config.LANGGRAPH_CLOUD_LICENSE_KEY,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
handler = AttrFilteredLoggingHandler(logger_provider=_logger_provider)
|
|
104
|
+
handler.setFormatter(OTLPFormatter("%(message)s", None, "%"))
|
|
105
|
+
logging.getLogger().addHandler(handler)
|
|
106
|
+
|
|
107
|
+
logger.info(
|
|
108
|
+
"Self-hosted logs initialized successfully",
|
|
109
|
+
endpoint=config.LANGGRAPH_LOGS_ENDPOINT,
|
|
110
|
+
)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.exception("Failed to initialize self-hosted logs", exc_info=e)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def shutdown_self_hosted_logs() -> None:
|
|
116
|
+
global _logger_provider
|
|
117
|
+
|
|
118
|
+
if _logger_provider:
|
|
119
|
+
try:
|
|
120
|
+
logger.info("Shutting down self-hosted logs")
|
|
121
|
+
_logger_provider.shutdown()
|
|
122
|
+
_logger_provider = None
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.exception("Failed to shutdown self-hosted logs", exc_info=e)
|
|
@@ -8,7 +8,7 @@ from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
|
|
|
8
8
|
from opentelemetry.metrics import CallbackOptions, Observation
|
|
9
9
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
10
10
|
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
|
11
|
-
from opentelemetry.sdk.resources import Resource
|
|
11
|
+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
|
12
12
|
|
|
13
13
|
from langgraph_api import asyncio as lg_asyncio
|
|
14
14
|
from langgraph_api import config, metadata
|
|
@@ -33,12 +33,12 @@ def initialize_self_hosted_metrics():
|
|
|
33
33
|
_http_latency_histogram, \
|
|
34
34
|
_customer_attributes
|
|
35
35
|
|
|
36
|
-
if not config.
|
|
36
|
+
if not config.LANGGRAPH_METRICS_ENABLED:
|
|
37
37
|
return
|
|
38
38
|
|
|
39
|
-
if not config.
|
|
39
|
+
if not config.LANGGRAPH_METRICS_ENDPOINT:
|
|
40
40
|
raise RuntimeError(
|
|
41
|
-
"
|
|
41
|
+
"LANGGRAPH_METRICS_ENABLED is true but no LANGGRAPH_METRICS_ENDPOINT is configured"
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
# for now, this is only enabled for fully self-hosted customers
|
|
@@ -51,18 +51,18 @@ def initialize_self_hosted_metrics():
|
|
|
51
51
|
|
|
52
52
|
try:
|
|
53
53
|
exporter = OTLPMetricExporter(
|
|
54
|
-
endpoint=config.
|
|
54
|
+
endpoint=config.LANGGRAPH_METRICS_ENDPOINT,
|
|
55
55
|
headers={"X-Langchain-License-Key": config.LANGGRAPH_CLOUD_LICENSE_KEY},
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
# this will periodically export metrics to our beacon lgp otel collector in a separate thread
|
|
59
59
|
metric_reader = PeriodicExportingMetricReader(
|
|
60
60
|
exporter=exporter,
|
|
61
|
-
export_interval_millis=config.
|
|
61
|
+
export_interval_millis=config.LANGGRAPH_METRICS_EXPORT_INTERVAL_MS,
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
resource_attributes = {
|
|
65
|
-
|
|
65
|
+
SERVICE_NAME: config.SELF_HOSTED_OBSERVABILITY_SERVICE_NAME,
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
resource = Resource.create(resource_attributes)
|
|
@@ -191,8 +191,8 @@ def initialize_self_hosted_metrics():
|
|
|
191
191
|
|
|
192
192
|
logger.info(
|
|
193
193
|
"Self-hosted metrics initialized successfully",
|
|
194
|
-
endpoint=config.
|
|
195
|
-
export_interval_ms=config.
|
|
194
|
+
endpoint=config.LANGGRAPH_METRICS_ENDPOINT,
|
|
195
|
+
export_interval_ms=config.LANGGRAPH_METRICS_EXPORT_INTERVAL_MS,
|
|
196
196
|
)
|
|
197
197
|
|
|
198
198
|
except Exception as e:
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
langgraph_api/__init__.py,sha256=
|
|
1
|
+
langgraph_api/__init__.py,sha256=z0N_zI_NU5oeqAtT38WjG25KpgiwC27dbVqSvLaHdfA,23
|
|
2
2
|
langgraph_api/asgi_transport.py,sha256=XtiLOu4WWsd-xizagBLzT5xUkxc9ZG9YqwvETBPjBFE,5161
|
|
3
3
|
langgraph_api/asyncio.py,sha256=FEEkLm_N-15cbElo4vQ309MkDKBZuRqAYV8VJ1DocNw,9860
|
|
4
4
|
langgraph_api/cli.py,sha256=o_zD2vkky06dzW87HQgkIR1_h3ZCSZ8tgNvFCK9rKVo,19669
|
|
5
5
|
langgraph_api/command.py,sha256=Bh-rvuTLwdHCqFWryCjB1M8oWxPBwRBUjMNj_04KPxM,852
|
|
6
|
-
langgraph_api/config.py,sha256=
|
|
6
|
+
langgraph_api/config.py,sha256=gO25XRPc19Room51P3FewE54pSFvfwuVDtvyjkDSzEs,13251
|
|
7
7
|
langgraph_api/cron_scheduler.py,sha256=25wYzEQrhPEivZrAPYOmzLPDOQa-aFogU37mTXc9TJk,2566
|
|
8
8
|
langgraph_api/errors.py,sha256=zlnl3xXIwVG0oGNKKpXf1an9Rn_SBDHSyhe53hU6aLw,1858
|
|
9
9
|
langgraph_api/executor_entrypoint.py,sha256=CaX813ygtf9CpOaBkfkQXJAHjFtmlScCkrOvTDmu4Aw,750
|
|
10
10
|
langgraph_api/feature_flags.py,sha256=taZRhukeBV8r62EmEo92rxfBwYhIw56-P_UvSzQLzt8,576
|
|
11
11
|
langgraph_api/graph.py,sha256=YDNncFFnjOjX_ylHDVY3Z4Ehj62zyHFJPaiRCkLAZus,25285
|
|
12
12
|
langgraph_api/http.py,sha256=fyK-H-0UfNy_BzuVW3aWWGvhRavmGAVMkDwDArryJ_4,5659
|
|
13
|
-
langgraph_api/http_metrics.py,sha256=
|
|
13
|
+
langgraph_api/http_metrics.py,sha256=vw3UT9uj9qgxQ_DwJq77HGZqh6LHSjyxylWhqkf2jAw,5095
|
|
14
14
|
langgraph_api/http_metrics_utils.py,sha256=sjxF7SYGTzY0Wz_G0dzatsYNnWr31S6ujej4JmBG2yo,866
|
|
15
|
-
langgraph_api/logging.py,sha256=
|
|
16
|
-
langgraph_api/metadata.py,sha256=
|
|
15
|
+
langgraph_api/logging.py,sha256=o5iVARqtFYKIcRrK2nk1ymcKEiVYKd_dHmhXLF2khFI,6090
|
|
16
|
+
langgraph_api/metadata.py,sha256=Z54bd-uf51qo3KR7_jxETbalp6vD9mWGe-UBeMw1AP4,8412
|
|
17
17
|
langgraph_api/patch.py,sha256=J0MmcfpZG15SUVaVcI0Z4x_c0-0rbbT7Pwh9fDAQOpA,1566
|
|
18
18
|
langgraph_api/queue_entrypoint.py,sha256=z3ZUBl3CpnMm0KFPqCuGvSohPAmYQbhAdyRizSJSClM,8481
|
|
19
19
|
langgraph_api/route.py,sha256=EBhELuJ1He-ZYcAnR5YTImcIeDtWthDae5CHELBxPkM,5056
|
|
20
20
|
langgraph_api/schema.py,sha256=spZ_XPT4AMJfw2YatsdnMZZLzgB9Sm3YR8n0SlgGdJ8,8480
|
|
21
|
-
langgraph_api/
|
|
21
|
+
langgraph_api/self_hosted_logs.py,sha256=9ljOz3KH3O1SwsD7eTKnreyJ80NbeR7nj7SuxBlrmCc,4422
|
|
22
|
+
langgraph_api/self_hosted_metrics.py,sha256=3FFezxjU0Vs-bsH39f4Dcwn7fporTLHV9REQ3UQ315A,14004
|
|
22
23
|
langgraph_api/serde.py,sha256=Jkww6ixP5o2YZmnXtM7ihuAYC6YSuNDNPvE-8ILoqVo,5499
|
|
23
24
|
langgraph_api/server.py,sha256=NNn9aqs74gfvL1amp_2oylamxt4UAD43QD6DQMhB6iA,9656
|
|
24
25
|
langgraph_api/sse.py,sha256=SLdtZmTdh5D8fbWrQjuY9HYLd2dg8Rmi6ZMmFMVc2iE,4204
|
|
@@ -34,7 +35,7 @@ langgraph_api/api/__init__.py,sha256=wrnxz_204b2Vhv4-N0WpiPf-ZpDDlmIQkbh-TiXPnOo
|
|
|
34
35
|
langgraph_api/api/a2a.py,sha256=HIHZkLnIcM1u1FJti-L2NH-h1I9BZ_d-QW9z3gFonn8,53995
|
|
35
36
|
langgraph_api/api/assistants.py,sha256=tRJse7Gr2BTeTZPljL05UvGkFiULpA-6hy03nBx9PF4,18177
|
|
36
37
|
langgraph_api/api/mcp.py,sha256=qe10ZRMN3f-Hli-9TI8nbQyWvMeBb72YB1PZVbyqBQw,14418
|
|
37
|
-
langgraph_api/api/meta.py,sha256=
|
|
38
|
+
langgraph_api/api/meta.py,sha256=_jG61UKs0J_alsCDgIwCAx1rX5pYuUwKrmOEpWnzR1I,4817
|
|
38
39
|
langgraph_api/api/openapi.py,sha256=If-z1ckXt-Yu5bwQytK1LWyX_T7G46UtLfixgEP8hwc,11959
|
|
39
40
|
langgraph_api/api/runs.py,sha256=keHlFu1iy-l1IICJHc6AKrSUoQA-LZi6FYsja7la9Xw,25436
|
|
40
41
|
langgraph_api/api/store.py,sha256=xGcPFx4v-VxlK6HRU9uCjzCQ0v66cvc3o_PB5_g7n0Q,5550
|
|
@@ -46,8 +47,8 @@ langgraph_api/auth/middleware.py,sha256=jDA4t41DUoAArEY_PNoXesIUBJ0nGhh85QzRdn5E
|
|
|
46
47
|
langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,586
|
|
47
48
|
langgraph_api/auth/studio_user.py,sha256=fojJpexdIZYI1w3awiqOLSwMUiK_M_3p4mlfQI0o-BE,454
|
|
48
49
|
langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
-
langgraph_api/auth/langsmith/backend.py,sha256=
|
|
50
|
-
langgraph_api/auth/langsmith/client.py,sha256=
|
|
50
|
+
langgraph_api/auth/langsmith/backend.py,sha256=JVf8-q1IvB5EeiLJge3cOtPvDg6qHzK_4cR-R8hPXXQ,3753
|
|
51
|
+
langgraph_api/auth/langsmith/client.py,sha256=79kwCVeHU64nsHsxWipfZhf44lM6vfs2nlfTxlJF6LU,4142
|
|
51
52
|
langgraph_api/grpc_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
53
|
langgraph_api/grpc_ops/client.py,sha256=VB740C9QMhJJrpAEjsADmasN-uGd0apGYtuv_ho0Rl8,2452
|
|
53
54
|
langgraph_api/grpc_ops/ops.py,sha256=VFmFIgXZmE3Xi1tGx-eZrqls6qMG0w5a2Ym7w2Wm9Iw,19733
|
|
@@ -60,7 +61,7 @@ langgraph_api/js/.prettierrc,sha256=0es3ovvyNIqIw81rPQsdt1zCQcOdBqyR_DMbFE4Ifms,
|
|
|
60
61
|
langgraph_api/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
62
|
langgraph_api/js/base.py,sha256=CJihwc51MwOVkis80f8zudRa1fQz_5jrom4rY8trww8,1133
|
|
62
63
|
langgraph_api/js/build.mts,sha256=wguMiExRjJYpnxol_IxNHuC65CnJFsasQhZiIVSZZq8,3377
|
|
63
|
-
langgraph_api/js/client.http.mts,sha256=
|
|
64
|
+
langgraph_api/js/client.http.mts,sha256=ZnikriJdcRSkBmUTBFMpLB3GKgK4xbiSqtUE-l9nqeM,4880
|
|
64
65
|
langgraph_api/js/client.mts,sha256=gDvYiW7Qfl4re2YhZ5oNqtuvffnW_Sf7DK5aUbKB3vw,32330
|
|
65
66
|
langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
|
|
66
67
|
langgraph_api/js/global.d.ts,sha256=j4GhgtQSZ5_cHzjSPcHgMJ8tfBThxrH-pUOrrJGteOU,196
|
|
@@ -109,8 +110,8 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
|
|
|
109
110
|
LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
110
111
|
logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
|
|
111
112
|
openapi.json,sha256=21wu-NxdxyTQwZctNcEfRkLMnSBi0QhGAfwq5kg8XNU,172618
|
|
112
|
-
langgraph_api-0.4.
|
|
113
|
-
langgraph_api-0.4.
|
|
114
|
-
langgraph_api-0.4.
|
|
115
|
-
langgraph_api-0.4.
|
|
116
|
-
langgraph_api-0.4.
|
|
113
|
+
langgraph_api-0.4.39.dist-info/METADATA,sha256=cXz_SwpOaWiKPu9DbKT17Te4fsDTACKiRC3IODESWiM,4156
|
|
114
|
+
langgraph_api-0.4.39.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
115
|
+
langgraph_api-0.4.39.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
|
|
116
|
+
langgraph_api-0.4.39.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
117
|
+
langgraph_api-0.4.39.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|