stop overriding caller's ls_integration in ls trace

This commit is contained in:
Hari Harish
2026-06-24 14:22:29 -07:00
parent 57c83d44bc
commit d7bca50323
4 changed files with 65 additions and 27 deletions

View File

@@ -738,10 +738,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **kwargs, **ls_structured_output_format_dict}
inheritable_metadata = {
**(config.get("metadata") or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
config.get("metadata") or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = CallbackManager.configure(
config.get("callbacks"),
self.callbacks,
@@ -867,10 +867,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **kwargs, **ls_structured_output_format_dict}
inheritable_metadata = {
**(config.get("metadata") or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
config.get("metadata") or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = AsyncCallbackManager.configure(
config.get("callbacks"),
self.callbacks,
@@ -1002,10 +1002,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **kwargs, **ls_structured_output_format_dict}
inheritable_metadata = {
**(config.get("metadata") or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
config.get("metadata") or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = CallbackManager.configure(
config.get("callbacks"),
self.callbacks,
@@ -1136,10 +1136,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **kwargs, **ls_structured_output_format_dict}
inheritable_metadata = {
**(config.get("metadata") or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
config.get("metadata") or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = AsyncCallbackManager.configure(
config.get("callbacks"),
self.callbacks,
@@ -1547,6 +1547,30 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
ls_params["ls_integration"] = "langchain_chat_model"
return ls_params
@staticmethod
def _merge_inheritable_metadata(
inherited: dict[str, Any], ls_params: LangSmithParams
) -> dict[str, Any]:
"""Merge model `ls_params` into inherited run metadata.
Replacing "last-writer-wins" policy for `ls_integration`.
If a caller already set one on the run metadata, keep it instead of
overriding with the chat model default.
Mirrors langgraph, which sets `ls_integration` only when it is absent.
Args:
inherited: Run metadata inherited from the invocation config.
ls_params: LangSmith params for this chat model, including the
default `ls_integration`.
Returns:
The merged metadata, preserving any inherited `ls_integration`.
"""
merged: dict[str, Any] = {**inherited, **ls_params}
if inherited.get("ls_integration"):
merged["ls_integration"] = inherited["ls_integration"]
return merged
def _get_llm_string(self, stop: list[str] | None = None, **kwargs: Any) -> str:
if self.is_lc_serializable():
params = {**kwargs, "stop": stop}
@@ -1617,10 +1641,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **ls_structured_output_format_dict}
inheritable_metadata = {
**(metadata or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
metadata or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = CallbackManager.configure(
callbacks,
@@ -1743,10 +1767,10 @@ class BaseChatModel(BaseLanguageModel[AIMessage], ABC):
params = self._get_invocation_params(stop=stop, **kwargs)
options = {"stop": stop, **ls_structured_output_format_dict}
inheritable_metadata = {
**(metadata or {}),
**self._get_ls_params_with_defaults(stop=stop, **kwargs),
}
inheritable_metadata = self._merge_inheritable_metadata(
metadata or {},
self._get_ls_params_with_defaults(stop=stop, **kwargs),
)
callback_manager = AsyncCallbackManager.configure(
callbacks,

View File

@@ -1362,6 +1362,18 @@ async def test_user_lc_versions_metadata_survives_merge_async() -> None:
assert "langchain-core" in run_metadata["lc_versions"]
def test_caller_ls_integration_survives_merge() -> None:
"""A caller-supplied `ls_integration` is not overridden by the model default."""
llm = FakeListChatModel(responses=["hello"])
user_config: RunnableConfig = {"metadata": {"ls_integration": "my-integration"}}
with collect_runs() as cb:
llm.invoke([HumanMessage(content="hi")], config=user_config)
assert len(cb.traced_runs) == 1
run_metadata = cb.traced_runs[0].extra["metadata"]
assert run_metadata["ls_integration"] == "my-integration"
def test_user_lc_versions_metadata_survives_merge_stream() -> None:
"""Stream variant: user-provided `lc_versions` metadata merges with model's."""
llm = _VersionedFakeModel(responses=["hello"])

View File

@@ -1778,9 +1778,10 @@ def create_agent(
# Set recursion limit to 9_999
# https://github.com/langchain-ai/langgraph/issues/7313
config: RunnableConfig = {"recursion_limit": 9_999}
config["metadata"] = {"ls_integration": "langchain_create_agent"}
# Don't bake ls_integration here: it would override an outer integration's
# value inherited ambiently through the parent run tree.
if name:
config["metadata"]["lc_agent_name"] = name
config["metadata"] = {"lc_agent_name": name}
middleware_transformers = [t for m in middleware for t in getattr(m, "transformers", ())]

View File

@@ -279,7 +279,8 @@ def test_tool_runtime_config_access() -> None:
assert config_data["config_exists"] is True
assert "config_keys" in config_data
assert config_data["recursion_limit"] == 9999
assert config_data["metadata"]["ls_integration"] == "langchain_create_agent"
# create_agent no longer bakes ls_integration; it defers to the caller.
assert "ls_integration" not in (config_data["metadata"] or {})
tool_message = result["messages"][2]
assert isinstance(tool_message, ToolMessage)