diff --git a/litellm/caching/redis_semantic_cache.py b/litellm/caching/redis_semantic_cache.py index ab51919447f..7268511fe7e 100644 --- a/litellm/caching/redis_semantic_cache.py +++ b/litellm/caching/redis_semantic_cache.py @@ -407,16 +407,14 @@ class RedisSemanticCache(BaseCache): # Convert string representation to Python object try: - cached_response = json.loads(response_text) + return json.loads(response_text) except json.JSONDecodeError: try: - cached_response = ast.literal_eval(response_text) + return ast.literal_eval(response_text) except (ValueError, SyntaxError) as e: print_verbose(f"Error parsing cached response: {e}") return None - return cached_response - def set_cache(self, key: str, value: object, **kwargs) -> None: """ Store a value in the semantic cache. diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index 5013cd96a09..8b94c34942c 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -618,12 +618,12 @@ class LiteLLMResponseObjectHandler: choices_list: Final[list[TextChoices]] = [] # Convert each choice to TextChoices - for choice in response.choices: + for choice in response["choices"]: text_choices = TextChoices() - text_choices["text"] = choice.message.content - text_choices["index"] = choice.index + text_choices["text"] = choice["message"]["content"] + text_choices["index"] = choice["index"] text_choices["logprobs"] = transformed_logprobs - text_choices["finish_reason"] = choice.finish_reason + text_choices["finish_reason"] = choice["finish_reason"] choices_list.append(text_choices) text_completion_response["choices"] = choices_list diff --git a/litellm/proxy/openai_files_endpoints/common_utils.py b/litellm/proxy/openai_files_endpoints/common_utils.py index cad1515059d..f7289445d3a 100644 --- a/litellm/proxy/openai_files_endpoints/common_utils.py +++ b/litellm/proxy/openai_files_endpoints/common_utils.py @@ -1380,10 +1380,7 @@ async def resolve_input_file_id_to_unified(response, prisma_client) -> None: pass -async def resolve_output_file_ids_to_unified( - response: "LiteLLMBatch", - prisma_client: "PrismaClient | None", -) -> None: +async def resolve_output_file_ids_to_unified(response, prisma_client) -> None: """ If the batch response contains raw provider output_file_id or error_file_id (not already unified IDs), look up the corresponding unified file IDs from @@ -1392,9 +1389,7 @@ async def resolve_output_file_ids_to_unified( if not prisma_client: return for attr in ("output_file_id", "error_file_id"): - if not hasattr(response, attr): - continue - raw_id = response.output_file_id if attr == "output_file_id" else response.error_file_id + raw_id = getattr(response, attr, None) if not raw_id or is_base64_encoded_unified_file_id(raw_id): continue try: diff --git a/litellm/types/router.py b/litellm/types/router.py index 86ebf45a3e6..60c9e596588 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -35,7 +35,6 @@ from litellm.types.llms.base import LiteLLMBaseModel from litellm.types.router_weights import RouterWeights if TYPE_CHECKING: - from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.router import Router from .completion import CompletionRequest @@ -1090,7 +1089,7 @@ class GuardrailTypedDict(TypedDict, total=False): guardrail_name: Required[str] litellm_params: Required[GuardrailLiteLLMParams] - callback: "ReadOnly[CustomGuardrail | None]" # The CustomGuardrail instance + callback: Any # The CustomGuardrail instance id: str | None # Unique identifier for the guardrail deployment diff --git a/tests/unit/litellm_core_utils/test_text_completion_conversion.py b/tests/unit/litellm_core_utils/test_text_completion_conversion.py index 4a2f91093a3..0689b582598 100644 --- a/tests/unit/litellm_core_utils/test_text_completion_conversion.py +++ b/tests/unit/litellm_core_utils/test_text_completion_conversion.py @@ -5,6 +5,7 @@ import pytest import litellm from litellm import text_completion +from litellm.llms.custom_llm import CustomLLM from litellm.types.utils import ModelResponse, TextCompletionResponse, Usage from litellm.utils import LiteLLMResponseObjectHandler @@ -1298,3 +1299,30 @@ def mock_post(*args, **kwargs): "usage": {"prompt_tokens": 2, "total_tokens": 18, "completion_tokens": 16}, } return mock_response + + +def test_text_completion_reads_a_custom_provider_dict_reply(monkeypatch: pytest.MonkeyPatch) -> None: + class DictReplyLLM(CustomLLM): + def completion(self, *args: object, **kwargs: object) -> dict[str, object]: + return { + "id": "dict-reply", + "object": "chat.completion", + "created": 1, + "model": "x", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "hi from a dict reply"}, + "finish_reason": "stop", + } + ], + } + + monkeypatch.setattr(litellm, "custom_provider_map", [{"provider": "dictreply", "custom_handler": DictReplyLLM()}]) + monkeypatch.setattr(litellm, "provider_list", [*litellm.provider_list, "dictreply"]) + monkeypatch.setattr(litellm, "_custom_providers", [*litellm._custom_providers, "dictreply"]) + + response = text_completion(model="dictreply/x", prompt="hi") + + assert response.choices[0].text == "hi from a dict reply" + assert response.choices[0].finish_reason == "stop"