AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Surface LangSmith Gateway Metadata On Successful Fireworks Completions

ChatFireworks does not capture the X-LangSmith-Gateway-Metadata header from successful gateway-routed responses, causing missing or incorrect model/provider information in LangSmith traces. This fix adds gateway metadata capture conditioned on the lsv2_ API key prefix, mirroring OpenAI and Anthropic integrations.

mediumConfidence 85%Langchain-Fireworks

Origin Analysis

ChatFireworks calls client.create() directly, which discards HTTP response headers. Unlike ChatOpenAI and ChatAnthropic, it never uses with_raw_response to access the X-LangSmith-Gateway-Metadata header sent by the LangSmith gateway on success.
1. Set LANGSMITH_GATEWAY=true and use a Fireworks model through the gateway.\n2. Call ChatFireworks with streaming and non-streaming.\n3. Inspect the LangSmith trace; observe that ls_gateway_info is absent and ls_model_name/ls_provider reflect the requested model rather than the gateway-resolved one.

Fixing Code Block

```python # Add these methods and modify existing ones in ChatFireworks class from langchain_core.utils._gateway import _parse_gateway_metadata class ChatFireworks(BaseChatModel): # ... existing code ... @property def _uses_gateway(self) -> bool: """Check if the API key indicates LangSmith gateway usage.""" return self.fireworks_api_key is not None and self.fireworks_api_key.startswith("lsv2_") def _add_gateway_metadata(self, generation_info: dict, headers: dict) -> None: """Parse and attach gateway metadata to generation info.""" gateway_metadata = _parse_gateway_metadata(headers) if gateway_metadata: generation_info["ls_gateway_info"] = gateway_metadata def _completion_with_retry(self, **kwargs): """Wrap completion call to optionally capture response headers.""" if not self._uses_gateway: return self.client.create(**kwargs) response = self.client.with_raw_response.create(**kwargs) headers = getattr(response, "headers", {}) parsed_response = response.parse() return parsed_response, headers async def _acompletion_with_retry(self, **kwargs): """Async version of _completion_with_retry.""" if not self._uses_gateway: return await self.client.create(**kwargs) response = await self.client.with_raw_response.create(**kwargs) headers = getattr(response, "headers", {}) parsed_response = response.parse() return parsed_response, headers def _generate(self, messages, stop=None, run_manager=None, **kwargs): """Modify to attach gateway metadata to each generation.""" params = self._get_invocation_params(stop=stop, **kwargs) if self._uses_gateway: response, headers = self._completion_with_retry(messages=messages, **params) chat_result = self._create_chat_result(response) for generation in chat_result.generations: self._add_gateway_metadata(generation.generation_info, headers) else: response = self._completion_with_retry(messages=messages, **params) chat_result = self._create_chat_result(response) return chat_result async def _agenerate(self, messages, stop=None, run_manager=None, **kwargs): """Async version of _generate.""" params = self._get_invocation_params(stop=stop, **kwargs) if self._uses_gateway: response, headers = await self._acompletion_with_retry(messages=messages, **params) chat_result = self._create_chat_result(response) for generation in chat_result.generations: self._add_gateway_metadata(generation.generation_info, headers) else: response = await self._acompletion_with_retry(messages=messages, **params) chat_result = self._create_chat_result(response) return chat_result def _stream(self, messages, stop=None, run_manager=None, **kwargs): """Modify to attach gateway metadata to the first streamed chunk.""" params = self._get_invocation_params(stop=stop, **kwargs) if self._uses_gateway: response, headers = self._completion_with_retry(messages=messages, stream=True, **params) # response is a generator/iterator of chunks stream_iter = iter(response) try: first_chunk = next(stream_iter) except StopIteration: return iter(()) if first_chunk.generation_info is None: first_chunk.generation_info = {} self._add_gateway_metadata(first_chunk.generation_info, headers) def gen(): yield first_chunk yield from stream_iter return gen() else: return self._completion_with_retry(messages=messages, stream=True, **params) async def _astream(self, messages, stop=None, run_manager=None, **kwargs): """Async version of _stream.""" params = self._get_invocation_params(stop=stop, **kwargs) if self._uses_gateway: response, headers = await self._acompletion_with_retry(messages=messages, stream=True, **params) stream_iter = response.__aiter__() try: first_chunk = await stream_iter.__anext__() except StopAsyncIteration: return _empty_async_iter() if first_chunk.generation_info is None: first_chunk.generation_info = {} self._add_gateway_metadata(first_chunk.generation_info, headers) async def agen(): yield first_chunk async for chunk in stream_iter: yield chunk return agen() else: return await self._acompletion_with_retry(messages=messages, stream=True, **params) ```
The fix adds a `_uses_gateway` property to detect the lsv2_ API key prefix. When gateway is active, it uses `client.with_raw_response.create()` to preserve headers, parses them with the shared `_parse_gateway_metadata`, and returns the parsed body plus headers. Non-streaming paths attach metadata to each generation's `generation_info`; streaming paths attach it to the first chunk. Non-gateway code paths remain unchanged.

Edge Case Audit

This change introduces a new tuple return type from `_completion_with_retry` when gateway is active, which could break subclasses or external code that overrides these methods expecting a single return value. The `with_raw_response` API availability depends on the installed fireworks-ai SDK version; older versions may not support it, leading to AttributeError. The async stream path assumes the returned object is an async iterator; if the SDK returns a sync iterator in async mode, it will fail. Custom clients passed by the user that do not provide `with_raw_response` will cause issues when the gateway key is used, but the `_uses_gateway` check should prevent this unless a user erroneously uses a gateway key with a custom client. To rollback, revert the changed methods and remove the `_uses_gateway` property; ensure all tests are rerun.

Ecosystem Topology