From 3cc58f959b5f27eb48fb86daa6b3da079ce8ab1f Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Tue, 18 Aug 2026 12:30:24 -0400 Subject: [PATCH] x --- libs/core/langchain_core/tracers/core.py | 18 ++++- .../tracers/test_automatic_metadata.py | 81 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/libs/core/langchain_core/tracers/core.py b/libs/core/langchain_core/tracers/core.py index 6af82ea9d9..e4a3954362 100644 --- a/libs/core/langchain_core/tracers/core.py +++ b/libs/core/langchain_core/tracers/core.py @@ -350,13 +350,29 @@ class _TracerCore(ABC): return llm_run def _attach_gateway_metadata(self, run: Run, response: LLMResult) -> None: - """Promote LangSmith gateway metadata from the response to run metadata.""" + """Promote LangSmith gateway metadata from the response to run metadata. + + When a request is routed through the LangSmith gateway, the resolved + provider and model may differ from the static values captured in the + run's `ls_provider` / `ls_model_name` at start time. When the gateway + reports them, they take precedence so the trace reflects what actually + served the request. + """ gateway_metadata = _extract_gateway_metadata(response) if gateway_metadata is None: return metadata = run.extra.setdefault("metadata", {}) metadata[_GATEWAY_RUN_METADATA_KEY] = gateway_metadata + provider = gateway_metadata.get("provider") + if provider: + metadata["ls_provider"] = provider + # Route-based calls report the served model as `selected_model`; simple + # calls report it as `model`. + model = gateway_metadata.get("selected_model") or gateway_metadata.get("model") + if model: + metadata["ls_model_name"] = model + def _errored_llm_run( self, error: BaseException, run_id: UUID, response: LLMResult | None = None ) -> Run: diff --git a/libs/core/tests/unit_tests/tracers/test_automatic_metadata.py b/libs/core/tests/unit_tests/tracers/test_automatic_metadata.py index 47f8362811..22875ce421 100644 --- a/libs/core/tests/unit_tests/tracers/test_automatic_metadata.py +++ b/libs/core/tests/unit_tests/tracers/test_automatic_metadata.py @@ -192,6 +192,87 @@ def test_complete_llm_run_attaches_gateway_metadata() -> None: assert completed_run.extra["metadata"]["ls_gateway_info"] == gateway_info +def test_complete_llm_run_gateway_metadata_overrides_ls_params() -> None: + """Gateway provider/model override the static `ls_*` run metadata.""" + tracer = MockTracerCore() + run = _make_run("test-gateway-override-run-id") + run.extra = {"metadata": {"ls_provider": "openai", "ls_model_name": "gpt-5.6"}} + tracer.run_map[str(run.id)] = run + + gateway_info = {"provider": "anthropic", "model": "claude-opus-5"} + response = LLMResult( + generations=[ + [ + ChatGeneration( + message=AIMessage(content="Test"), + generation_info={GATEWAY_METADATA_RESPONSE_KEY: gateway_info}, + ) + ] + ] + ) + + completed_run = tracer._complete_llm_run(response=response, run_id=run.id) + + metadata = completed_run.extra["metadata"] + assert metadata["ls_provider"] == "anthropic" + assert metadata["ls_model_name"] == "claude-opus-5" + assert metadata["ls_gateway_info"] == gateway_info + + +def test_complete_llm_run_gateway_metadata_prefers_selected_model() -> None: + """`selected_model` (route-based served model) wins over `model`.""" + tracer = MockTracerCore() + run = _make_run("test-gateway-selected-model-run-id") + run.extra = {"metadata": {"ls_model_name": "gpt-5.6"}} + tracer.run_map[str(run.id)] = run + + gateway_info = { + "provider": "openai", + "model": "gpt-5.6", + "selected_model": "gpt-5.6-mini", + } + response = LLMResult( + generations=[ + [ + ChatGeneration( + message=AIMessage(content="Test"), + generation_info={GATEWAY_METADATA_RESPONSE_KEY: gateway_info}, + ) + ] + ] + ) + + completed_run = tracer._complete_llm_run(response=response, run_id=run.id) + + assert completed_run.extra["metadata"]["ls_model_name"] == "gpt-5.6-mini" + + +def test_complete_llm_run_gateway_metadata_preserves_ls_params_when_absent() -> None: + """Static `ls_*` values are kept when the gateway omits provider/model.""" + tracer = MockTracerCore() + run = _make_run("test-gateway-partial-run-id") + run.extra = {"metadata": {"ls_provider": "openai", "ls_model_name": "gpt-5.6"}} + tracer.run_map[str(run.id)] = run + + gateway_info = {"outcome": "success"} + response = LLMResult( + generations=[ + [ + ChatGeneration( + message=AIMessage(content="Test"), + generation_info={GATEWAY_METADATA_RESPONSE_KEY: gateway_info}, + ) + ] + ] + ) + + completed_run = tracer._complete_llm_run(response=response, run_id=run.id) + + metadata = completed_run.extra["metadata"] + assert metadata["ls_provider"] == "openai" + assert metadata["ls_model_name"] == "gpt-5.6" + + def test_complete_llm_run_no_gateway_metadata() -> None: """No gateway metadata is attached when the response carries none.""" tracer = MockTracerCore()