From 2c5ccb353711025465180ccb67e0fc80404011c8 Mon Sep 17 00:00:00 2001 From: AAKASH S S Date: Fri, 23 Oct 2026 14:52:35 +0530 Subject: [PATCH 1/5] fix(openai/completion): package non-standard completion kwargs like response_format into extra_body --- .../llms/openai/completion/transformation.py | 45 ++++++++++- .../completion/test_completion_handler.py | 78 ++++++++++++++----- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index f7d2ce68082..966bcff9849 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -13,6 +13,34 @@ from litellm.types.utils import Choices, Message, ModelResponse, TextCompletionR from ..chat.gpt_transformation import OpenAIGPTConfig from .utils import transform_prompt +OPENAI_TEXT_COMPLETION_PARAMS: Final = frozenset( + { + "model", + "prompt", + "best_of", + "echo", + "frequency_penalty", + "logit_bias", + "logprobs", + "max_tokens", + "n", + "presence_penalty", + "seed", + "stop", + "stream", + "stream_options", + "suffix", + "temperature", + "top_p", + "user", + "extra_headers", + "extra_query", + "extra_body", + "timeout", + "max_retries", + } +) + class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): """ @@ -155,8 +183,21 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): headers: dict, ) -> dict: prompt: Final = transform_prompt(messages) - return { + openai_params: dict = {} + extra_body: dict = dict(optional_params.get("extra_body") or {}) + for k, v in optional_params.items(): + if k == "extra_body": + continue + if k in OPENAI_TEXT_COMPLETION_PARAMS: + openai_params[k] = v + else: + extra_body[k] = v + + result: dict = { "model": model, "prompt": prompt, - **optional_params, + **openai_params, } + if extra_body: + result["extra_body"] = extra_body + return result diff --git a/tests/unit/llms/openai/completion/test_completion_handler.py b/tests/unit/llms/openai/completion/test_completion_handler.py index 9e2754eef68..4fa472a7ca3 100644 --- a/tests/unit/llms/openai/completion/test_completion_handler.py +++ b/tests/unit/llms/openai/completion/test_completion_handler.py @@ -5,7 +5,6 @@ text completion path. Regression tests for https://github.com/BerriAI/litellm/issues/27410 """ - import json from typing import Final @@ -13,7 +12,6 @@ import pytest import respx from httpx import Response - import litellm from litellm import atext_completion, text_completion @@ -78,9 +76,7 @@ def test_completion_forwards_extra_headers_to_provider(mock_completions_endpoint @respx.mock -async def test_acompletion_forwards_client_headers_to_provider( - mock_completions_endpoint, monkeypatch -): +async def test_acompletion_forwards_client_headers_to_provider(mock_completions_endpoint, monkeypatch): monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) await atext_completion( model="gpt-3.5-turbo-instruct", @@ -297,11 +293,9 @@ async def test_async_text_completion_stream_returns_text_and_one_finish_reason(m chunks: Final = [chunk async for chunk in stream] assert [chunk.choices[0].text for chunk in chunks if chunk.choices[0].text] == ["hello"] - assert tuple( - chunk.choices[0].finish_reason - for chunk in chunks - if chunk.choices[0].finish_reason is not None - ) == ("stop",) + assert tuple(chunk.choices[0].finish_reason for chunk in chunks if chunk.choices[0].finish_reason is not None) == ( + "stop", + ) assert len(route.calls) == 1 @@ -320,9 +314,7 @@ async def test_async_text_completion_chat_model_stream_builds_chat_chunks(monkey ) ) - stream: Final = await atext_completion( - model="gpt-3.5-turbo", prompt="say hello", stream=True, max_tokens=5 - ) + stream: Final = await atext_completion(model="gpt-3.5-turbo", prompt="say hello", stream=True, max_tokens=5) chunks: Final = [chunk async for chunk in stream] response: Final = litellm.stream_chunk_builder(chunks=chunks) @@ -332,11 +324,9 @@ async def test_async_text_completion_chat_model_stream_builds_chat_chunks(monkey completion_response=response, custom_cost_per_token={"input_cost_per_token": 5e-07, "output_cost_per_token": 1.5e-06}, ) == pytest.approx(1 * 1.5e-06) - assert tuple( - chunk.choices[0].finish_reason - for chunk in chunks - if chunk.choices[0].finish_reason is not None - ) == ("stop",) + assert tuple(chunk.choices[0].finish_reason for chunk in chunks if chunk.choices[0].finish_reason is not None) == ( + "stop", + ) assert len(route.calls) == 1 @@ -372,6 +362,54 @@ def test_text_completion_stream_forwards_stream_options_and_usage(monkeypatch): 1, 3, ) - assert json.loads(route.calls[0].request.content)["stream_options"] == { - "include_usage": True + assert json.loads(route.calls[0].request.content)["stream_options"] == {"include_usage": True} + + +@respx.mock +def test_completion_forwards_response_format_via_extra_body(mock_completions_endpoint): + text_completion( + model="gpt-3.5-turbo-instruct", + prompt="say hello", + response_format={"type": "json_object"}, + ) + + request_body = json.loads(mock_completions_endpoint.calls.last.request.content) + assert request_body["response_format"] == {"type": "json_object"} + + +@respx.mock +async def test_acompletion_forwards_response_format_via_extra_body(mock_completions_endpoint, monkeypatch): + monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) + await atext_completion( + model="gpt-3.5-turbo-instruct", + prompt="say hello", + response_format={"type": "json_object"}, + ) + + request_body = json.loads(mock_completions_endpoint.calls.last.request.content) + assert request_body["response_format"] == {"type": "json_object"} + + +def test_transform_text_completion_request_preserves_standard_and_moves_extra_to_extra_body(): + from litellm.llms.openai.completion.transformation import OpenAITextCompletionConfig + + config = OpenAITextCompletionConfig() + result = config.transform_text_completion_request( + model="gpt-3.5-turbo-instruct", + messages=[{"role": "user", "content": "hello"}], + optional_params={ + "temperature": 0.7, + "response_format": {"type": "json_object"}, + "extra_body": {"custom_option": 123}, + }, + headers={}, + ) + + assert result["model"] == "gpt-3.5-turbo-instruct" + assert result["prompt"] == "hello" + assert result["temperature"] == 0.7 + assert "response_format" not in result # moved to extra_body + assert result["extra_body"] == { + "custom_option": 123, + "response_format": {"type": "json_object"}, } From e05c6b2c4ec9d502aeea8d87f967004a507d7f24 Mon Sep 17 00:00:00 2001 From: AAKASH S S Date: Fri, 23 Oct 2026 15:25:57 +0530 Subject: [PATCH 2/5] fix(type-discipline): satisfy LIT001 and LIT010 in transform_text_completion_request --- .../llms/openai/completion/transformation.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index 966bcff9849..fa3fe754362 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -183,21 +183,18 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): headers: dict, ) -> dict: prompt: Final = transform_prompt(messages) - openai_params: dict = {} - extra_body: dict = dict(optional_params.get("extra_body") or {}) - for k, v in optional_params.items(): - if k == "extra_body": - continue - if k in OPENAI_TEXT_COMPLETION_PARAMS: - openai_params[k] = v - else: - extra_body[k] = v - - result: dict = { + user_extra: Final = optional_params.get("extra_body") + passthrough_params: Final = { + k: v for k, v in optional_params.items() if k != "extra_body" and k not in OPENAI_TEXT_COMPLETION_PARAMS + } + extra_body: Final = { + **(dict(user_extra) if isinstance(user_extra, (dict, Mapping)) else {}), + **passthrough_params, + } + standard_params: Final = {k: v for k, v in optional_params.items() if k in OPENAI_TEXT_COMPLETION_PARAMS} + return { "model": model, "prompt": prompt, - **openai_params, + **standard_params, + **({"extra_body": extra_body} if extra_body else {}), } - if extra_body: - result["extra_body"] = extra_body - return result From 4a556232ffbbce880b8b53e0d4fb6bd53d395aa0 Mon Sep 17 00:00:00 2001 From: AAKASH S S Date: Fri, 23 Oct 2026 15:48:23 +0530 Subject: [PATCH 3/5] fix(openai/completion): satisfy basedpyright and type discipline in transform_text_completion_request --- .../llms/openai/completion/transformation.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index fa3fe754362..a39febdd1d8 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -183,18 +183,19 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): headers: dict, ) -> dict: prompt: Final = transform_prompt(messages) - user_extra: Final = optional_params.get("extra_body") - passthrough_params: Final = { - k: v for k, v in optional_params.items() if k != "extra_body" and k not in OPENAI_TEXT_COMPLETION_PARAMS - } - extra_body: Final = { - **(dict(user_extra) if isinstance(user_extra, (dict, Mapping)) else {}), - **passthrough_params, - } - standard_params: Final = {k: v for k, v in optional_params.items() if k in OPENAI_TEXT_COMPLETION_PARAMS} - return { + result: Final[dict[str, object]] = { # mutable-ok: completion request payload "model": model, "prompt": prompt, - **standard_params, - **({"extra_body": extra_body} if extra_body else {}), } + result.update(optional_params) # pyright: ignore[reportUnknownMemberType] # optional_params is an untyped dict + extra_body: Final[dict[str, object]] = {} # mutable-ok: completion extra body + user_extra: Final = result.pop("extra_body", None) + if isinstance(user_extra, (dict, Mapping)): + extra_body.update(user_extra) # pyright: ignore[reportUnknownMemberType] # extra_body is untyped + for param in list(result.keys()): + if param not in OPENAI_TEXT_COMPLETION_PARAMS: + extra_body[param] = result.pop(param) + if extra_body: + result["extra_body"] = extra_body + return result + From e0d69822c561b3304baa760a4a35d33571b3e17c Mon Sep 17 00:00:00 2001 From: AAKASH S S Date: Fri, 23 Oct 2026 16:09:40 +0530 Subject: [PATCH 4/5] style: format transformation.py with ruff format --- litellm/llms/openai/completion/transformation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index a39febdd1d8..f5c267b6354 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -198,4 +198,3 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): if extra_body: result["extra_body"] = extra_body return result - From 052f3319bd214524c86b4ef4061bd50a6c7543c0 Mon Sep 17 00:00:00 2001 From: AAKASH S S Date: Fri, 23 Oct 2026 16:24:15 +0530 Subject: [PATCH 5/5] fix(openai/completion): suppress reportUnknownArgumentType on untyped dict update calls --- litellm/llms/openai/completion/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai/completion/transformation.py b/litellm/llms/openai/completion/transformation.py index f5c267b6354..a3bee682725 100644 --- a/litellm/llms/openai/completion/transformation.py +++ b/litellm/llms/openai/completion/transformation.py @@ -187,11 +187,11 @@ class OpenAITextCompletionConfig(BaseTextCompletionConfig, OpenAIGPTConfig): "model": model, "prompt": prompt, } - result.update(optional_params) # pyright: ignore[reportUnknownMemberType] # optional_params is an untyped dict + result.update(optional_params) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType] # optional_params is an untyped dict extra_body: Final[dict[str, object]] = {} # mutable-ok: completion extra body user_extra: Final = result.pop("extra_body", None) if isinstance(user_extra, (dict, Mapping)): - extra_body.update(user_extra) # pyright: ignore[reportUnknownMemberType] # extra_body is untyped + extra_body.update(user_extra) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType] # extra_body is untyped for param in list(result.keys()): if param not in OPENAI_TEXT_COMPLETION_PARAMS: extra_body[param] = result.pop(param)