Skip to content

Commit f97b349

Browse files
hinthornweyurtsev
authored andcommitted
x
1 parent deb85b6 commit f97b349

File tree

9 files changed

+994
-18
lines changed

9 files changed

+994
-18
lines changed

libs/core/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test tests:
1717
-u LANGSMITH_API_KEY \
1818
-u LANGSMITH_TRACING \
1919
-u LANGCHAIN_PROJECT \
20-
uv run --group test pytest -n auto $(PYTEST_EXTRA) --disable-socket --allow-unix-socket $(TEST_FILE)
20+
uv run --group test pytest -n auto --benchmark-disable $(PYTEST_EXTRA)--disable-socket --allow-unix-socket $(TEST_FILE)
2121

2222
test_watch:
2323
env \

libs/core/langchain_core/callbacks/manager.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import functools
88
import logging
99
from abc import ABC, abstractmethod
10-
from collections.abc import Callable
10+
from collections.abc import Callable, Mapping
1111
from concurrent.futures import ThreadPoolExecutor
1212
from contextlib import asynccontextmanager, contextmanager
1313
from contextvars import copy_context
@@ -1614,6 +1614,9 @@ def configure(
16141614
local_tags: list[str] | None = None,
16151615
inheritable_metadata: dict[str, Any] | None = None,
16161616
local_metadata: dict[str, Any] | None = None,
1617+
*,
1618+
langsmith_inheritable_metadata: Mapping[str, str] | None = None,
1619+
langsmith_inheritable_tags: list[str] | None = None,
16171620
) -> CallbackManager:
16181621
"""Configure the callback manager.
16191622
@@ -1625,6 +1628,10 @@ def configure(
16251628
local_tags: The local tags.
16261629
inheritable_metadata: The inheritable metadata.
16271630
local_metadata: The local metadata.
1631+
langsmith_inheritable_metadata: Default inheritable metadata applied
1632+
to any `LangChainTracer` handlers via `set_defaults`.
1633+
langsmith_inheritable_tags: Default inheritable tags applied to any
1634+
`LangChainTracer` handlers via `set_defaults`.
16281635
16291636
Returns:
16301637
The configured callback manager.
@@ -1638,6 +1645,8 @@ def configure(
16381645
inheritable_metadata,
16391646
local_metadata,
16401647
verbose=verbose,
1648+
langsmith_inheritable_metadata=langsmith_inheritable_metadata,
1649+
langsmith_inheritable_tags=langsmith_inheritable_tags,
16411650
)
16421651

16431652

@@ -2134,6 +2143,9 @@ def configure(
21342143
local_tags: list[str] | None = None,
21352144
inheritable_metadata: dict[str, Any] | None = None,
21362145
local_metadata: dict[str, Any] | None = None,
2146+
*,
2147+
langsmith_inheritable_metadata: Mapping[str, str] | None = None,
2148+
langsmith_inheritable_tags: list[str] | None = None,
21372149
) -> AsyncCallbackManager:
21382150
"""Configure the async callback manager.
21392151
@@ -2145,6 +2157,10 @@ def configure(
21452157
local_tags: The local tags.
21462158
inheritable_metadata: The inheritable metadata.
21472159
local_metadata: The local metadata.
2160+
langsmith_inheritable_metadata: Default inheritable metadata applied
2161+
to any `LangChainTracer` handlers via `set_defaults`.
2162+
langsmith_inheritable_tags: Default inheritable tags applied to any
2163+
`LangChainTracer` handlers via `set_defaults`.
21482164
21492165
Returns:
21502166
The configured async callback manager.
@@ -2158,6 +2174,8 @@ def configure(
21582174
inheritable_metadata,
21592175
local_metadata,
21602176
verbose=verbose,
2177+
langsmith_inheritable_metadata=langsmith_inheritable_metadata,
2178+
langsmith_inheritable_tags=langsmith_inheritable_tags,
21612179
)
21622180

21632181

@@ -2304,6 +2322,8 @@ def _configure(
23042322
local_metadata: dict[str, Any] | None = None,
23052323
*,
23062324
verbose: bool = False,
2325+
langsmith_inheritable_metadata: Mapping[str, str] | None = None,
2326+
langsmith_inheritable_tags: list[str] | None = None,
23072327
) -> T:
23082328
"""Configure the callback manager.
23092329
@@ -2316,6 +2336,10 @@ def _configure(
23162336
inheritable_metadata: The inheritable metadata.
23172337
local_metadata: The local metadata.
23182338
verbose: Whether to enable verbose mode.
2339+
langsmith_inheritable_metadata: Default inheritable metadata applied to
2340+
any `LangChainTracer` handlers via `set_defaults`.
2341+
langsmith_inheritable_tags: Default inheritable tags applied to any
2342+
`LangChainTracer` handlers via `set_defaults`.
23192343
23202344
Raises:
23212345
RuntimeError: If `LANGCHAIN_TRACING` is set but `LANGCHAIN_TRACING_V2` is not.
@@ -2440,6 +2464,7 @@ def _configure(
24402464
else tracing_context["client"]
24412465
),
24422466
tags=tracing_tags,
2467+
metadata=tracing_metadata,
24432468
)
24442469
callback_manager.add_handler(handler)
24452470
except Exception as e:
@@ -2479,6 +2504,25 @@ def _configure(
24792504
for handler in callback_manager.handlers
24802505
):
24812506
callback_manager.add_handler(var_handler, inheritable)
2507+
if langsmith_inheritable_metadata or langsmith_inheritable_tags:
2508+
callback_manager.handlers = [
2509+
handler.copy_with_metadata_defaults(
2510+
metadata=langsmith_inheritable_metadata,
2511+
tags=langsmith_inheritable_tags,
2512+
)
2513+
if isinstance(handler, LangChainTracer)
2514+
else handler
2515+
for handler in callback_manager.handlers
2516+
]
2517+
callback_manager.inheritable_handlers = [
2518+
handler.copy_with_metadata_defaults(
2519+
metadata=langsmith_inheritable_metadata,
2520+
tags=langsmith_inheritable_tags,
2521+
)
2522+
if isinstance(handler, LangChainTracer)
2523+
else handler
2524+
for handler in callback_manager.inheritable_handlers
2525+
]
24822526
return callback_manager
24832527

24842528

libs/core/langchain_core/runnables/config.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,36 @@ class RunnableConfig(TypedDict, total=False):
138138
"configurable",
139139
]
140140

141+
CONFIGURABLE_TO_METADATA_KEYS = frozenset(
142+
(
143+
"thread_id",
144+
"run_id",
145+
"task_id",
146+
"checkpoint_id",
147+
"checkpoint_ns",
148+
"assistant_id",
149+
"graph_id",
150+
"model",
151+
"user_id",
152+
"cron_id",
153+
"langgraph_auth_user_id",
154+
)
155+
)
156+
157+
158+
def _get_langsmith_inheritable_metadata_from_config(
159+
config: RunnableConfig,
160+
) -> dict[str, str] | None:
161+
"""Get LangSmith-only inheritable metadata defaults derived from config."""
162+
configurable = config.get("configurable") or {}
163+
metadata = {
164+
key: value
165+
for key in CONFIGURABLE_TO_METADATA_KEYS
166+
if isinstance((value := configurable.get(key)), str)
167+
}
168+
return metadata or None
169+
170+
141171
DEFAULT_RECURSION_LIMIT = 25
142172

143173

@@ -264,14 +294,6 @@ def ensure_config(config: RunnableConfig | None = None) -> RunnableConfig:
264294
for k, v in config.items():
265295
if k not in CONFIG_KEYS and v is not None:
266296
empty["configurable"][k] = v
267-
for key, value in empty.get("configurable", {}).items():
268-
if (
269-
not key.startswith("__")
270-
and isinstance(value, (str, int, float, bool))
271-
and key not in empty["metadata"]
272-
and key != "api_key"
273-
):
274-
empty["metadata"][key] = value
275297
return empty
276298

277299

@@ -508,6 +530,9 @@ def get_callback_manager_for_config(config: RunnableConfig) -> CallbackManager:
508530
inheritable_callbacks=config.get("callbacks"),
509531
inheritable_tags=config.get("tags"),
510532
inheritable_metadata=config.get("metadata"),
533+
langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
534+
config
535+
),
511536
)
512537

513538

@@ -526,6 +551,9 @@ def get_async_callback_manager_for_config(
526551
inheritable_callbacks=config.get("callbacks"),
527552
inheritable_tags=config.get("tags"),
528553
inheritable_metadata=config.get("metadata"),
554+
langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
555+
config
556+
),
529557
)
530558

531559

libs/core/langchain_core/tracers/core.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ def __init__(
5151
_schema_format: Literal[
5252
"original", "streaming_events", "original+chat"
5353
] = "original",
54+
run_map: dict[str, Run] | None = None,
55+
order_map: dict[UUID, tuple[UUID, str]] | None = None,
5456
**kwargs: Any,
5557
) -> None:
5658
"""Initialize the tracer.
@@ -70,17 +72,19 @@ def __init__(
7072
streaming events.
7173
- `'original+chat'` is a format that is the same as `'original'` except
7274
it does NOT raise an attribute error `on_chat_model_start`
75+
run_map: Optional shared map of run ID to run.
76+
order_map: Optional shared map of run ID to trace ordering data.
7377
**kwargs: Additional keyword arguments that will be passed to the
7478
superclass.
7579
"""
7680
super().__init__(**kwargs)
7781

7882
self._schema_format = _schema_format # For internal use only API will change.
7983

80-
self.run_map: dict[str, Run] = {}
84+
self.run_map = run_map if run_map is not None else {}
8185
"""Map of run ID to run. Cleared on run end."""
8286

83-
self.order_map: dict[UUID, tuple[UUID, str]] = {}
87+
self.order_map = order_map if order_map is not None else {}
8488
"""Map of run ID to (trace_id, dotted_order). Cleared when tracer GCed."""
8589

8690
@abstractmethod

libs/core/langchain_core/tracers/langchain.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from langchain_core.tracers.schemas import Run
2828

2929
if TYPE_CHECKING:
30+
from collections.abc import Mapping
31+
3032
from langchain_core.messages import BaseMessage
3133
from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
3234

@@ -124,6 +126,8 @@ def __init__(
124126
project_name: str | None = None,
125127
client: Client | None = None,
126128
tags: list[str] | None = None,
129+
*,
130+
metadata: Mapping[str, str] | None = None,
127131
**kwargs: Any,
128132
) -> None:
129133
"""Initialize the LangChain tracer.
@@ -139,6 +143,9 @@ def __init__(
139143
tags: The tags.
140144
141145
Defaults to an empty list.
146+
metadata: Additional metadata to include if it isn't already in the run.
147+
148+
Defaults to None.
142149
**kwargs: Additional keyword arguments.
143150
"""
144151
super().__init__(**kwargs)
@@ -150,6 +157,39 @@ def __init__(
150157
self.tags = tags or []
151158
self.latest_run: Run | None = None
152159
self.run_has_token_event_map: dict[str, bool] = {}
160+
self.tracing_metadata: dict[str, str] | None = (
161+
dict(metadata) if metadata is not None else None
162+
)
163+
164+
def copy_with_metadata_defaults(
165+
self,
166+
*,
167+
metadata: Mapping[str, str] | None = None,
168+
tags: list[str] | None = None,
169+
) -> LangChainTracer:
170+
"""Return a new tracer with merged tracer-only defaults."""
171+
base_metadata = self.tracing_metadata
172+
if metadata is None:
173+
merged_metadata = dict(base_metadata) if base_metadata is not None else None
174+
elif base_metadata is None:
175+
merged_metadata = dict(metadata)
176+
else:
177+
merged_metadata = dict(base_metadata)
178+
for key, value in metadata.items():
179+
if key not in merged_metadata:
180+
merged_metadata[key] = value
181+
182+
merged_tags = sorted(set(self.tags + tags)) if tags else self.tags
183+
184+
return self.__class__(
185+
example_id=self.example_id,
186+
project_name=self.project_name,
187+
client=self.client,
188+
tags=merged_tags,
189+
metadata=merged_metadata,
190+
run_map=self.run_map,
191+
order_map=self.order_map,
192+
)
153193

154194
def _start_trace(self, run: Run) -> None:
155195
if self.project_name:
@@ -263,6 +303,7 @@ def _persist_run_single(self, run: Run) -> None:
263303
try:
264304
run.extra["runtime"] = get_runtime_environment()
265305
run.tags = self._get_tags(run)
306+
_patch_missing_metadata(self, run)
266307
if run.ls_client is not self.client:
267308
run.ls_client = self.client
268309
run.post()
@@ -398,3 +439,17 @@ def wait_for_futures(self) -> None:
398439
"""Wait for the given futures to complete."""
399440
if self.client is not None:
400441
self.client.flush()
442+
443+
444+
def _patch_missing_metadata(self: LangChainTracer, run: Run) -> None:
445+
if not self.tracing_metadata:
446+
return
447+
metadata = run.metadata
448+
patched = None
449+
for k, v in self.tracing_metadata.items():
450+
if k not in metadata:
451+
if patched is None:
452+
# Copy on first miss to avoid mutating the shared dict.
453+
patched = {**metadata}
454+
run.extra["metadata"] = patched
455+
patched[k] = v

0 commit comments

Comments
 (0)