diff --git a/litellm/litellm_core_utils/get_litellm_params.py b/litellm/litellm_core_utils/get_litellm_params.py index 3d8394f7af..c0fbb1cb97 100644 --- a/litellm/litellm_core_utils/get_litellm_params.py +++ b/litellm/litellm_core_utils/get_litellm_params.py @@ -75,7 +75,7 @@ def get_litellm_params( "model_info": model_info, "proxy_server_request": proxy_server_request, "preset_cache_key": preset_cache_key, - "no-log": no_log, + "no-log": no_log or kwargs.get("no-log"), "stream_response": {}, # litellm_call_id: ModelResponse Dict "input_cost_per_token": input_cost_per_token, "input_cost_per_second": input_cost_per_second, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d44fb07637..4d082e29ad 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -3,7 +3,6 @@ # Logging function -> log the exact model details + what's being sent | Non-Blocking import copy import datetime -from functools import lru_cache import json import os import re @@ -13,6 +12,7 @@ import time import traceback import uuid from datetime import datetime as dt_object +from functools import lru_cache from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union, cast from pydantic import BaseModel @@ -2513,15 +2513,19 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915 # auth can be disabled on local deployments of arize phoenix if arize_phoenix_config.otlp_auth_headers is not None: - os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = arize_phoenix_config.otlp_auth_headers - + os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = ( + arize_phoenix_config.otlp_auth_headers + ) + for callback in _in_memory_loggers: if ( isinstance(callback, OpenTelemetry) and callback.callback_name == "arize_phoenix" ): return callback # type: ignore - _otel_logger = OpenTelemetry(config=otel_config, callback_name="arize_phoenix") + _otel_logger = OpenTelemetry( + config=otel_config, callback_name="arize_phoenix" + ) _in_memory_loggers.append(_otel_logger) return _otel_logger # type: ignore elif logging_integration == "otel": diff --git a/litellm/main.py b/litellm/main.py index ece484f1f2..c52bfd7c92 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -94,7 +94,7 @@ from litellm.utils import ( read_config_args, supports_httpx_timeout, token_counter, - validate_chat_completion_messages, + validate_and_fix_openai_messages, validate_chat_completion_tool_choice, ) @@ -851,7 +851,7 @@ def completion( # type: ignore # noqa: PLR0915 if model is None: raise ValueError("model param not passed in.") # validate messages - messages = validate_chat_completion_messages(messages=messages) + messages = validate_and_fix_openai_messages(messages=messages) # validate tool_choice tool_choice = validate_chat_completion_tool_choice(tool_choice=tool_choice) ######### unpacking kwargs ##################### diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index c3d130695e..0000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 0145043a33..abf2e07c88 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -1,5 +1,24 @@ model_list: - model_name: claude-3.5 litellm_params: +<<<<<<< HEAD model: claude-3-5-sonnet-latest - api_key: os.environ/ANTHROPIC_API_KEY \ No newline at end of file + api_key: os.environ/ANTHROPIC_API_KEY +======= + model: openai/gpt-3.5-turbo + api_key: os.environ/OPENAI_API_KEY + api_base: http://0.0.0.0:8090 + - model_name: deepseek-r1 + litellm_params: + model: bedrock/deepseek_r1/arn:aws:bedrock:us-west-2:888602223428:imported-model/bnnr6463ejgf + - model_name: deepseek-r1-api + litellm_params: + model: deepseek/deepseek-reasoner + - model_name: cohere.embed-english-v3 + litellm_params: + model: bedrock/cohere.embed-english-v3 + api_key: os.environ/COHERE_API_KEY + +litellm_settings: + callbacks: ["langfuse"] +>>>>>>> f86a609ea (fix(get_litellm_params.py): handle no-log being passed in via kwargs) diff --git a/litellm/utils.py b/litellm/utils.py index facc2ac59b..8f19e74509 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5932,6 +5932,18 @@ def convert_to_dict(message: Union[BaseModel, dict]) -> dict: ) +def validate_and_fix_openai_messages(messages: List): + """ + Ensures all messages are valid OpenAI chat completion messages. + + Handles missing role for assistant messages. + """ + for message in messages: + if not message.get("role"): + message["role"] = "assistant" + return validate_chat_completion_messages(messages=messages) + + def validate_chat_completion_messages(messages: List[AllMessageValues]): """ Ensures all messages are valid OpenAI chat completion messages. @@ -6282,11 +6294,18 @@ def get_end_user_id_for_cost_tracking( return None return end_user_id -def should_use_cohere_v1_client(api_base: Optional[str], present_version_params: List[str]): + +def should_use_cohere_v1_client( + api_base: Optional[str], present_version_params: List[str] +): if not api_base: return False - uses_v1_params = ("max_chunks_per_doc" in present_version_params) and ('max_tokens_per_doc' not in present_version_params) - return api_base.endswith("/v1/rerank") or (uses_v1_params and not api_base.endswith("/v2/rerank")) + uses_v1_params = ("max_chunks_per_doc" in present_version_params) and ( + "max_tokens_per_doc" not in present_version_params + ) + return api_base.endswith("/v1/rerank") or ( + uses_v1_params and not api_base.endswith("/v2/rerank") + ) def is_prompt_caching_valid_prompt( diff --git a/tests/litellm/rerank_api/test_main.py b/tests/litellm/rerank_api/test_rerank_main.py similarity index 100% rename from tests/litellm/rerank_api/test_main.py rename to tests/litellm/rerank_api/test_rerank_main.py diff --git a/tests/litellm/test_main.py b/tests/litellm/test_main.py new file mode 100644 index 0000000000..b838434915 --- /dev/null +++ b/tests/litellm/test_main.py @@ -0,0 +1,119 @@ +import json +import os +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +from unittest.mock import MagicMock, patch + +import litellm + + +@pytest.fixture +def openai_api_response(): + mock_response_data = { + "id": "chatcmpl-B0W3vmiM78Xkgx7kI7dr7PC949DMS", + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "logprobs": None, + "message": { + "content": "", + "refusal": None, + "role": "assistant", + "audio": None, + "function_call": None, + "tool_calls": None, + }, + } + ], + "created": 1739462947, + "model": "gpt-4o-mini-2024-07-18", + "object": "chat.completion", + "service_tier": "default", + "system_fingerprint": "fp_bd83329f63", + "usage": { + "completion_tokens": 1, + "prompt_tokens": 121, + "total_tokens": 122, + "completion_tokens_details": { + "accepted_prediction_tokens": 0, + "audio_tokens": 0, + "reasoning_tokens": 0, + "rejected_prediction_tokens": 0, + }, + "prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0}, + }, + } + + return mock_response_data + + +def test_completion_missing_role(openai_api_response): + from openai import OpenAI + + from litellm.types.utils import ModelResponse + + client = OpenAI(api_key="test_api_key") + + mock_raw_response = MagicMock() + mock_raw_response.headers = { + "x-request-id": "123", + "openai-organization": "org-123", + "x-ratelimit-limit-requests": "100", + "x-ratelimit-remaining-requests": "99", + } + mock_raw_response.parse.return_value = ModelResponse(**openai_api_response) + + print(f"openai_api_response: {openai_api_response}") + + with patch.object( + client.chat.completions.with_raw_response, "create", mock_raw_response + ) as mock_create: + litellm.completion( + model="gpt-4o-mini", + messages=[ + {"role": "user", "content": "Hey"}, + { + "content": "", + "tool_calls": [ + { + "id": "call_m0vFJjQmTH1McvaHBPR2YFwY", + "function": { + "arguments": '{"input": "dksjsdkjdhskdjshdskhjkhlk"}', + "name": "tool_name", + }, + "type": "function", + "index": 0, + }, + { + "id": "call_Vw6RaqV2n5aaANXEdp5pYxo2", + "function": { + "arguments": '{"input": "jkljlkjlkjlkjlk"}', + "name": "tool_name", + }, + "type": "function", + "index": 1, + }, + { + "id": "call_hBIKwldUEGlNh6NlSXil62K4", + "function": { + "arguments": '{"input": "jkjlkjlkjlkj;lj"}', + "name": "tool_name", + }, + "type": "function", + "index": 2, + }, + ], + }, + ], + client=client, + ) + + mock_create.assert_called_once()