From ed0ad742e92c94c0285cebec7f5957efcddd72dc Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 21 Aug 2026 14:33:30 -0400 Subject: [PATCH] feat(core): propagate gateway information on error path (#39829) This PR plumbs through gateway metadata information for exceptions image --- .../language_models/chat_models.py | 17 ++++++++++++++--- .../language_models/chat_models/test_base.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/libs/core/langchain_core/language_models/chat_models.py b/libs/core/langchain_core/language_models/chat_models.py index 07303a3ac3..3058ddaa8e 100644 --- a/libs/core/langchain_core/language_models/chat_models.py +++ b/libs/core/langchain_core/language_models/chat_models.py @@ -87,7 +87,10 @@ from langchain_core.tracers._streaming import ( _StreamingCallbackHandler, _V2StreamingCallbackHandler, ) -from langchain_core.utils._gateway import GATEWAY_METADATA_RESPONSE_KEY +from langchain_core.utils._gateway import ( + GATEWAY_METADATA_RESPONSE_KEY, + _parse_gateway_metadata, +) from langchain_core.utils.function_calling import ( convert_to_json_schema, convert_to_openai_tool, @@ -110,6 +113,7 @@ def _generate_response_from_error(error: BaseException) -> list[ChatGeneration]: if hasattr(error, "response"): response = error.response metadata: dict[str, Any] = {} + generation_info: dict[str, Any] = {} if hasattr(response, "json"): try: metadata["body"] = response.json() @@ -120,7 +124,11 @@ def _generate_response_from_error(error: BaseException) -> list[ChatGeneration]: metadata["body"] = None if hasattr(response, "headers"): try: - metadata["headers"] = dict(response.headers) + headers = response.headers + metadata["headers"] = dict(headers) + gateway_metadata = _parse_gateway_metadata(headers) + if gateway_metadata is not None: + generation_info[GATEWAY_METADATA_RESPONSE_KEY] = gateway_metadata except Exception: metadata["headers"] = None if hasattr(response, "status_code"): @@ -128,7 +136,10 @@ def _generate_response_from_error(error: BaseException) -> list[ChatGeneration]: if hasattr(error, "request_id"): metadata["request_id"] = error.request_id generations = [ - ChatGeneration(message=AIMessage(content="", response_metadata=metadata)) + ChatGeneration( + message=AIMessage(content="", response_metadata=metadata), + generation_info=generation_info or None, + ) ] else: generations = [] diff --git a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py index 51f09f609b..2d9c2bee79 100644 --- a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py +++ b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py @@ -54,6 +54,7 @@ from langchain_core.tracers.context import collect_runs from langchain_core.tracers.event_stream import _AstreamEventsCallbackHandler from langchain_core.tracers.langchain import LangChainTracer from langchain_core.tracers.schemas import Run +from langchain_core.utils._gateway import GATEWAY_METADATA_RESPONSE_KEY from langchain_core.version import VERSION from tests.unit_tests.fake.callbacks import ( BaseFakeCallbackHandler, @@ -1730,6 +1731,21 @@ def test_generate_response_from_error_with_valid_json() -> None: assert metadata["status_code"] == 400 +def test_generate_response_from_error_surfaces_gateway_metadata() -> None: + """Gateway metadata from an error response is available for tracing.""" + response = MockResponse( + headers={"X-LangSmith-Gateway-Metadata": '{"outcome": "blocked"}'}, + ) + + generations = _generate_response_from_error( + MockAPIError("API Error", response=response) + ) + + assert generations[0].generation_info == { + GATEWAY_METADATA_RESPONSE_KEY: {"outcome": "blocked"} + } + + def test_generate_response_from_error_handles_streaming_response_failure() -> None: # Simulates scenario where accessing response.json() or response.text # raises ResponseNotRead on streaming responses