From e9a40ad4d2d6712812e9b9210b28d4c291cdfc0b Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:16:14 -0700 Subject: [PATCH] fix(fireworks_ai): map developer items after pydantic input items are dumped --- .../fireworks_ai/responses/transformation.py | 5 ++- ...t_fireworks_ai_responses_transformation.py | 43 ++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/litellm/llms/fireworks_ai/responses/transformation.py b/litellm/llms/fireworks_ai/responses/transformation.py index 121d3b5d9a9..fb0587553d4 100644 --- a/litellm/llms/fireworks_ai/responses/transformation.py +++ b/litellm/llms/fireworks_ai/responses/transformation.py @@ -68,6 +68,9 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): base: Final = (api_base or get_secret_str("FIREWORKS_API_BASE") or FIREWORKS_AI_DEFAULT_API_BASE).rstrip("/") return f"{base}/responses" + def _validate_input_param(self, input: str | ResponseInputParam) -> str | ResponseInputParam: + return _developer_items_as_system(super()._validate_input_param(input)) + def transform_responses_api_request( self, model: str, @@ -78,7 +81,7 @@ class FireworksAIResponsesAPIConfig(OpenAIResponsesAPIConfig): ) -> dict: # mutable-ok: overrides the base class signature return super().transform_responses_api_request( model=resolve_fireworks_resource_name(model), - input=_developer_items_as_system(input), + input=input, response_api_optional_request_params=response_api_optional_request_params, litellm_params=litellm_params, headers=headers, diff --git a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py index f948acb7bf3..b9408d44e9a 100644 --- a/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/responses/test_fireworks_ai_responses_transformation.py @@ -1,13 +1,14 @@ import json from collections.abc import Mapping from types import MappingProxyType -from typing import Final, TypedDict +from typing import Final, TypedDict, cast from unittest.mock import MagicMock, patch from urllib.parse import quote import httpx import pytest from openai.types.responses import ( + EasyInputMessage, ResponseFunctionToolCall, ResponseOutputMessage, ResponseOutputText, @@ -20,7 +21,7 @@ from typing_extensions import ReadOnly import litellm from litellm.llms.fireworks_ai.responses.transformation import FireworksAIResponsesAPIConfig from litellm.responses.file_search.emulated_handler import should_use_emulated_file_search -from litellm.types.llms.openai import InputTokensDetails, ResponseAPIUsage, ResponsesAPIResponse +from litellm.types.llms.openai import InputTokensDetails, ResponseAPIUsage, ResponseInputParam, ResponsesAPIResponse from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import LlmProviders from litellm.utils import ProviderConfigManager @@ -181,6 +182,44 @@ def test_responses_call_sends_developer_items_as_system_messages() -> None: ) +def test_responses_call_maps_pydantic_developer_items_and_replays_pydantic_output_items() -> None: + client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3")) + pydantic_input: Final = cast( + ResponseInputParam, + [ # mutable-ok: the Responses API takes input as a JSON list + EasyInputMessage(role="developer", content="Answer with exactly one word.", type="message"), + ResponseReasoningItem(id="rs_1", summary=(), type="reasoning"), + ResponseFunctionToolCall( + id="fc_1", + call_id="call_abc123", + name="get_weather", + arguments='{"city": "Paris"}', + status="completed", + type="function_call", + ), + FunctionCallOutput(type="function_call_output", call_id="call_abc123", output="21C"), + ], + ) + with patch(HTTPX_CLIENT_FACTORY, return_value=client): + litellm.responses( + model="fireworks_ai/accounts/fireworks/models/kimi-k3", input=pydantic_input, api_key="fw-test-key" + ) + _, _, body = _sent_request(client) + assert tuple(body["input"]) == ( + {"role": "system", "content": "Answer with exactly one word.", "type": "message"}, + {"id": "rs_1", "summary": [], "type": "reasoning"}, + { + "id": "fc_1", + "call_id": "call_abc123", + "name": "get_weather", + "arguments": '{"city": "Paris"}', + "status": "completed", + "type": "function_call", + }, + {"type": "function_call_output", "call_id": "call_abc123", "output": "21C"}, + ) + + def test_file_search_tools_take_litellm_emulated_search_not_fireworks() -> None: config: Final = FireworksAIResponsesAPIConfig() file_search: Final = ({"type": "file_search", "vector_store_ids": ("vs_kb",)},)