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.
- langgraph_api/__init__.py +1 -1
- langgraph_api/api/__init__.py +111 -51
- langgraph_api/api/a2a.py +1610 -0
- langgraph_api/api/assistants.py +212 -89
- langgraph_api/api/mcp.py +3 -3
- langgraph_api/api/meta.py +52 -28
- langgraph_api/api/openapi.py +27 -17
- langgraph_api/api/profile.py +108 -0
- langgraph_api/api/runs.py +342 -195
- langgraph_api/api/store.py +19 -2
- langgraph_api/api/threads.py +209 -27
- langgraph_api/asgi_transport.py +14 -9
- langgraph_api/asyncio.py +14 -4
- langgraph_api/auth/custom.py +52 -37
- langgraph_api/auth/langsmith/backend.py +4 -3
- langgraph_api/auth/langsmith/client.py +13 -8
- langgraph_api/cli.py +230 -133
- langgraph_api/command.py +5 -3
- langgraph_api/config/__init__.py +532 -0
- langgraph_api/config/_parse.py +58 -0
- langgraph_api/config/schemas.py +431 -0
- langgraph_api/cron_scheduler.py +17 -1
- langgraph_api/encryption/__init__.py +15 -0
- langgraph_api/encryption/aes_json.py +158 -0
- langgraph_api/encryption/context.py +35 -0
- langgraph_api/encryption/custom.py +280 -0
- langgraph_api/encryption/middleware.py +632 -0
- langgraph_api/encryption/shared.py +63 -0
- langgraph_api/errors.py +12 -1
- langgraph_api/executor_entrypoint.py +11 -6
- langgraph_api/feature_flags.py +29 -0
- langgraph_api/graph.py +176 -76
- langgraph_api/grpc/client.py +313 -0
- langgraph_api/grpc/config_conversion.py +231 -0
- langgraph_api/grpc/generated/__init__.py +29 -0
- langgraph_api/grpc/generated/checkpointer_pb2.py +63 -0
- langgraph_api/grpc/generated/checkpointer_pb2.pyi +99 -0
- langgraph_api/grpc/generated/checkpointer_pb2_grpc.py +329 -0
- langgraph_api/grpc/generated/core_api_pb2.py +216 -0
- langgraph_api/grpc/generated/core_api_pb2.pyi +905 -0
- langgraph_api/grpc/generated/core_api_pb2_grpc.py +1621 -0
- langgraph_api/grpc/generated/engine_common_pb2.py +219 -0
- langgraph_api/grpc/generated/engine_common_pb2.pyi +722 -0
- langgraph_api/grpc/generated/engine_common_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2.pyi +12 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_durability_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_durability_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_durability_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_run_status_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_run_status_pb2.pyi +22 -0
- langgraph_api/grpc/generated/enum_run_status_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2.pyi +28 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/errors_pb2.py +39 -0
- langgraph_api/grpc/generated/errors_pb2.pyi +21 -0
- langgraph_api/grpc/generated/errors_pb2_grpc.py +24 -0
- langgraph_api/grpc/ops/__init__.py +370 -0
- langgraph_api/grpc/ops/assistants.py +424 -0
- langgraph_api/grpc/ops/runs.py +792 -0
- langgraph_api/grpc/ops/threads.py +1013 -0
- langgraph_api/http.py +16 -5
- langgraph_api/http_metrics.py +15 -35
- langgraph_api/http_metrics_utils.py +38 -0
- langgraph_api/js/build.mts +1 -1
- langgraph_api/js/client.http.mts +13 -7
- langgraph_api/js/client.mts +2 -5
- langgraph_api/js/package.json +29 -28
- langgraph_api/js/remote.py +56 -30
- langgraph_api/js/src/graph.mts +20 -0
- langgraph_api/js/sse.py +2 -2
- langgraph_api/js/ui.py +1 -1
- langgraph_api/js/yarn.lock +1204 -1006
- langgraph_api/logging.py +29 -2
- langgraph_api/metadata.py +99 -28
- langgraph_api/middleware/http_logger.py +7 -2
- langgraph_api/middleware/private_network.py +7 -7
- langgraph_api/models/run.py +54 -93
- langgraph_api/otel_context.py +205 -0
- langgraph_api/patch.py +5 -3
- langgraph_api/queue_entrypoint.py +154 -65
- langgraph_api/route.py +47 -5
- langgraph_api/schema.py +88 -10
- langgraph_api/self_hosted_logs.py +124 -0
- langgraph_api/self_hosted_metrics.py +450 -0
- langgraph_api/serde.py +79 -37
- langgraph_api/server.py +138 -60
- langgraph_api/state.py +4 -3
- langgraph_api/store.py +25 -16
- langgraph_api/stream.py +80 -29
- langgraph_api/thread_ttl.py +31 -13
- langgraph_api/timing/__init__.py +25 -0
- langgraph_api/timing/profiler.py +200 -0
- langgraph_api/timing/timer.py +318 -0
- langgraph_api/utils/__init__.py +53 -8
- langgraph_api/utils/cache.py +47 -10
- langgraph_api/utils/config.py +2 -1
- langgraph_api/utils/errors.py +77 -0
- langgraph_api/utils/future.py +10 -6
- langgraph_api/utils/headers.py +76 -2
- langgraph_api/utils/retriable_client.py +74 -0
- langgraph_api/utils/stream_codec.py +315 -0
- langgraph_api/utils/uuids.py +29 -62
- langgraph_api/validation.py +9 -0
- langgraph_api/webhook.py +120 -6
- langgraph_api/worker.py +55 -24
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/METADATA +16 -8
- langgraph_api-0.7.3.dist-info/RECORD +168 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/WHEEL +1 -1
- langgraph_runtime/__init__.py +1 -0
- langgraph_runtime/routes.py +11 -0
- logging.json +1 -3
- openapi.json +839 -478
- langgraph_api/config.py +0 -387
- langgraph_api/js/isolate-0x130008000-46649-46649-v8.log +0 -4430
- langgraph_api/js/isolate-0x138008000-44681-44681-v8.log +0 -4430
- langgraph_api/js/package-lock.json +0 -3308
- langgraph_api-0.4.1.dist-info/RECORD +0 -107
- /langgraph_api/{utils.py → grpc/__init__.py} +0 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/entry_points.txt +0 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/licenses/LICENSE +0 -0
langgraph_api/schema.py
CHANGED
|
@@ -16,9 +16,19 @@ RunStatus = Literal["pending", "running", "error", "success", "timeout", "interr
|
|
|
16
16
|
ThreadStatus = Literal["idle", "busy", "interrupted", "error"]
|
|
17
17
|
|
|
18
18
|
StreamMode = Literal[
|
|
19
|
-
"values",
|
|
19
|
+
"values",
|
|
20
|
+
"messages",
|
|
21
|
+
"updates",
|
|
22
|
+
"events",
|
|
23
|
+
"debug",
|
|
24
|
+
"tasks",
|
|
25
|
+
"checkpoints",
|
|
26
|
+
"custom",
|
|
27
|
+
"messages-tuple",
|
|
20
28
|
]
|
|
21
29
|
|
|
30
|
+
ThreadStreamMode = Literal["lifecycle", "run_modes", "state_update"]
|
|
31
|
+
|
|
22
32
|
MultitaskStrategy = Literal["reject", "rollback", "interrupt", "enqueue"]
|
|
23
33
|
|
|
24
34
|
OnConflictBehavior = Literal["raise", "do_nothing"]
|
|
@@ -48,10 +58,16 @@ class Config(TypedDict, total=False):
|
|
|
48
58
|
"""
|
|
49
59
|
Runtime values for attributes previously made configurable on this Runnable,
|
|
50
60
|
or sub-Runnables, through .configurable_fields() or .configurable_alternatives().
|
|
51
|
-
Check .output_schema() for a description of the attributes that have been made
|
|
61
|
+
Check .output_schema() for a description of the attributes that have been made
|
|
52
62
|
configurable.
|
|
53
63
|
"""
|
|
54
64
|
|
|
65
|
+
__encryption_context__: dict[str, Any]
|
|
66
|
+
"""
|
|
67
|
+
Internal: Encryption context for encryption/decryption operations.
|
|
68
|
+
Not exposed to users.
|
|
69
|
+
"""
|
|
70
|
+
|
|
55
71
|
|
|
56
72
|
class Checkpoint(TypedDict):
|
|
57
73
|
thread_id: str
|
|
@@ -60,7 +76,7 @@ class Checkpoint(TypedDict):
|
|
|
60
76
|
checkpoint_map: dict[str, Any] | None
|
|
61
77
|
|
|
62
78
|
|
|
63
|
-
class Assistant(TypedDict):
|
|
79
|
+
class Assistant(TypedDict, total=False):
|
|
64
80
|
"""Assistant model."""
|
|
65
81
|
|
|
66
82
|
assistant_id: UUID
|
|
@@ -110,6 +126,17 @@ class DeprecatedInterrupt(TypedDict, total=False):
|
|
|
110
126
|
"""When the interrupt occurred, always "during"."""
|
|
111
127
|
|
|
112
128
|
|
|
129
|
+
class ThreadTTLInfo(TypedDict, total=False):
|
|
130
|
+
"""TTL information for a thread. Only present when ?include=ttl is passed."""
|
|
131
|
+
|
|
132
|
+
strategy: Literal["delete", "keep_latest"]
|
|
133
|
+
"""The TTL strategy."""
|
|
134
|
+
ttl_minutes: float
|
|
135
|
+
"""The TTL in minutes."""
|
|
136
|
+
expires_at: datetime
|
|
137
|
+
"""When the thread will expire."""
|
|
138
|
+
|
|
139
|
+
|
|
113
140
|
class Thread(TypedDict):
|
|
114
141
|
thread_id: UUID
|
|
115
142
|
"""The ID of the thread."""
|
|
@@ -121,14 +148,14 @@ class Thread(TypedDict):
|
|
|
121
148
|
"""The thread metadata."""
|
|
122
149
|
config: Fragment
|
|
123
150
|
"""The thread config."""
|
|
124
|
-
context: Fragment
|
|
125
|
-
"""The thread context."""
|
|
126
151
|
status: ThreadStatus
|
|
127
152
|
"""The status of the thread. One of 'idle', 'busy', 'interrupted', "error"."""
|
|
128
153
|
values: Fragment
|
|
129
154
|
"""The current state of the thread."""
|
|
130
155
|
interrupts: dict[str, list[Interrupt]]
|
|
131
156
|
"""The current interrupts of the thread, a map of task_id to list of interrupts."""
|
|
157
|
+
ttl: NotRequired[ThreadTTLInfo]
|
|
158
|
+
"""TTL information if set for this thread. Only present when ?include=ttl is passed."""
|
|
132
159
|
|
|
133
160
|
|
|
134
161
|
class ThreadTask(TypedDict):
|
|
@@ -146,7 +173,7 @@ class ThreadState(TypedDict):
|
|
|
146
173
|
next: Sequence[str]
|
|
147
174
|
"""The name of the node to execute in each task for this step."""
|
|
148
175
|
checkpoint: Checkpoint
|
|
149
|
-
"""The checkpoint keys. This object can be passed to the /threads and /runs
|
|
176
|
+
"""The checkpoint keys. This object can be passed to the /threads and /runs
|
|
150
177
|
endpoints to resume execution or update state."""
|
|
151
178
|
metadata: Fragment
|
|
152
179
|
"""Metadata for this state"""
|
|
@@ -174,6 +201,7 @@ class RunKwargs(TypedDict):
|
|
|
174
201
|
subgraphs: bool
|
|
175
202
|
resumable: bool
|
|
176
203
|
checkpoint_during: bool
|
|
204
|
+
durability: str | None
|
|
177
205
|
|
|
178
206
|
|
|
179
207
|
class Run(TypedDict):
|
|
@@ -217,6 +245,8 @@ class Cron(TypedDict):
|
|
|
217
245
|
"""The ID of the assistant."""
|
|
218
246
|
thread_id: UUID | None
|
|
219
247
|
"""The ID of the thread."""
|
|
248
|
+
on_run_completed: NotRequired[Literal["delete", "keep"] | None]
|
|
249
|
+
"""What to do with the thread after the run completes."""
|
|
220
250
|
end_time: datetime | None
|
|
221
251
|
"""The end date to stop running the cron."""
|
|
222
252
|
schedule: str
|
|
@@ -246,8 +276,9 @@ class ThreadUpdateResponse(TypedDict):
|
|
|
246
276
|
class QueueStats(TypedDict):
|
|
247
277
|
n_pending: int
|
|
248
278
|
n_running: int
|
|
249
|
-
|
|
250
|
-
|
|
279
|
+
pending_runs_wait_time_max_secs: float | None
|
|
280
|
+
pending_runs_wait_time_med_secs: float | None
|
|
281
|
+
pending_unblocked_runs_wait_time_max_secs: float | None
|
|
251
282
|
|
|
252
283
|
|
|
253
284
|
# Canonical field sets for select= validation and type aliases for ops
|
|
@@ -274,7 +305,6 @@ ThreadSelectField = Literal[
|
|
|
274
305
|
"updated_at",
|
|
275
306
|
"metadata",
|
|
276
307
|
"config",
|
|
277
|
-
"context",
|
|
278
308
|
"status",
|
|
279
309
|
"values",
|
|
280
310
|
"interrupts",
|
|
@@ -300,6 +330,7 @@ CronSelectField = Literal[
|
|
|
300
330
|
"cron_id",
|
|
301
331
|
"assistant_id",
|
|
302
332
|
"thread_id",
|
|
333
|
+
"on_run_completed",
|
|
303
334
|
"end_time",
|
|
304
335
|
"schedule",
|
|
305
336
|
"created_at",
|
|
@@ -308,6 +339,53 @@ CronSelectField = Literal[
|
|
|
308
339
|
"payload",
|
|
309
340
|
"next_run_date",
|
|
310
341
|
"metadata",
|
|
311
|
-
"now",
|
|
312
342
|
]
|
|
313
343
|
CRON_FIELDS: set[str] = set(CronSelectField.__args__) # type: ignore[attr-defined]
|
|
344
|
+
|
|
345
|
+
# Encryption field constants
|
|
346
|
+
# These define which fields are encrypted for each model type.
|
|
347
|
+
#
|
|
348
|
+
# Note: Checkpoint encryption (checkpoint, metadata columns in checkpoints table, plus
|
|
349
|
+
# blob data in checkpoint_blobs and checkpoint_writes) is handled directly by the
|
|
350
|
+
# Checkpointer class in storage_postgres/langgraph_runtime_postgres/checkpoint.py.
|
|
351
|
+
# The checkpointer uses encrypt_json_if_needed/decrypt_json_if_needed directly rather
|
|
352
|
+
# than the field list pattern used by the API middleware. This is because checkpoints
|
|
353
|
+
# are only accessed via the checkpointer's internal methods (aget_tuple, aput, etc.),
|
|
354
|
+
# not through generic API CRUD operations.
|
|
355
|
+
|
|
356
|
+
THREAD_ENCRYPTION_FIELDS = ["metadata", "config", "values", "interrupts", "error"]
|
|
357
|
+
|
|
358
|
+
# kwargs is a nested blob - its subfields are decrypted automatically by the middleware
|
|
359
|
+
RUN_ENCRYPTION_FIELDS = ["metadata", "kwargs"]
|
|
360
|
+
|
|
361
|
+
ASSISTANT_ENCRYPTION_FIELDS = ["metadata", "config", "context"]
|
|
362
|
+
|
|
363
|
+
# payload is a nested blob - its subfields are decrypted automatically by the middleware
|
|
364
|
+
CRON_ENCRYPTION_FIELDS = ["metadata", "payload"]
|
|
365
|
+
|
|
366
|
+
# Store encryption - only the value field contains user data
|
|
367
|
+
STORE_ENCRYPTION_FIELDS = ["value"]
|
|
368
|
+
|
|
369
|
+
# The middleware automatically decrypts these subfields when decrypting the parent field.
|
|
370
|
+
# This is recursive: if a subfield is also in NESTED_ENCRYPTED_SUBFIELDS, its subfields
|
|
371
|
+
# are decrypted too (e.g., run.kwargs.config.configurable).
|
|
372
|
+
NESTED_ENCRYPTED_SUBFIELDS: dict[tuple[str, str], list[str]] = {
|
|
373
|
+
("run", "kwargs"): ["input", "config", "context", "command"],
|
|
374
|
+
("run", "config"): ["configurable", "metadata"],
|
|
375
|
+
("cron", "payload"): ["metadata", "context", "input", "config"],
|
|
376
|
+
("cron", "config"): ["configurable", "metadata"],
|
|
377
|
+
("assistant", "config"): ["configurable"],
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# Convenience alias for cron payload subfields.
|
|
381
|
+
#
|
|
382
|
+
# This is a reflection of an unfortunate asymmetry in cron's data model.
|
|
383
|
+
#
|
|
384
|
+
# The cron API requests have payload fields (metadata, input, config, context) at the
|
|
385
|
+
# top level, but at rest they're nested inside the `payload` JSONB column (with
|
|
386
|
+
# metadata also duplicated as a top-level column). This alias is used to encrypt
|
|
387
|
+
# those fields in the flat request before storage.
|
|
388
|
+
CRON_PAYLOAD_ENCRYPTION_SUBFIELDS = NESTED_ENCRYPTED_SUBFIELDS[("cron", "payload")]
|
|
389
|
+
|
|
390
|
+
# Convenience alias for run kwargs subfields, used by the worker for decryption.
|
|
391
|
+
RUN_KWARGS_ENCRYPTION_SUBFIELDS = NESTED_ENCRYPTED_SUBFIELDS[("run", "kwargs")]
|
|
@@ -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)
|