Spaces:
Paused
Paused
| import json | |
| import os | |
| import sys | |
| from unittest.mock import patch | |
| import pytest | |
| from jsonschema import validate | |
| sys.path.insert( | |
| 0, os.path.abspath("../..") | |
| ) # Adds the parent directory to the system path | |
| import litellm | |
| from litellm.types.utils import LlmProviders | |
| from litellm.utils import ( | |
| ProviderConfigManager, | |
| get_llm_provider, | |
| get_optional_params_image_gen, | |
| ) | |
| # Adds the parent directory to the system path | |
| def test_get_optional_params_image_gen(): | |
| from litellm.llms.azure.image_generation import AzureGPTImageGenerationConfig | |
| provider_config = AzureGPTImageGenerationConfig() | |
| optional_params = get_optional_params_image_gen( | |
| model="gpt-image-1", | |
| response_format="b64_json", | |
| n=3, | |
| custom_llm_provider="azure", | |
| drop_params=True, | |
| provider_config=provider_config, | |
| ) | |
| assert optional_params is not None | |
| assert "response_format" not in optional_params | |
| assert optional_params["n"] == 3 | |
| def test_all_model_configs(): | |
| from litellm.llms.vertex_ai.vertex_ai_partner_models.ai21.transformation import ( | |
| VertexAIAi21Config, | |
| ) | |
| from litellm.llms.vertex_ai.vertex_ai_partner_models.llama3.transformation import ( | |
| VertexAILlama3Config, | |
| ) | |
| assert ( | |
| "max_completion_tokens" | |
| in VertexAILlama3Config().get_supported_openai_params(model="llama3") | |
| ) | |
| assert VertexAILlama3Config().map_openai_params( | |
| {"max_completion_tokens": 10}, {}, "llama3", drop_params=False | |
| ) == {"max_tokens": 10} | |
| assert "max_completion_tokens" in VertexAIAi21Config().get_supported_openai_params( | |
| model="jamba-1.5-mini@001" | |
| ) | |
| assert VertexAIAi21Config().map_openai_params( | |
| {"max_completion_tokens": 10}, {}, "jamba-1.5-mini@001", drop_params=False | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.fireworks_ai.chat.transformation import FireworksAIConfig | |
| assert "max_completion_tokens" in FireworksAIConfig().get_supported_openai_params( | |
| model="llama3" | |
| ) | |
| assert FireworksAIConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.nvidia_nim.chat.transformation import NvidiaNimConfig | |
| assert "max_completion_tokens" in NvidiaNimConfig().get_supported_openai_params( | |
| model="llama3" | |
| ) | |
| assert NvidiaNimConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.ollama.chat.transformation import OllamaChatConfig | |
| assert "max_completion_tokens" in OllamaChatConfig().get_supported_openai_params( | |
| model="llama3" | |
| ) | |
| assert OllamaChatConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"num_predict": 10} | |
| from litellm.llms.predibase.chat.transformation import PredibaseConfig | |
| assert "max_completion_tokens" in PredibaseConfig().get_supported_openai_params( | |
| model="llama3" | |
| ) | |
| assert PredibaseConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_new_tokens": 10} | |
| from litellm.llms.codestral.completion.transformation import ( | |
| CodestralTextCompletionConfig, | |
| ) | |
| assert ( | |
| "max_completion_tokens" | |
| in CodestralTextCompletionConfig().get_supported_openai_params(model="llama3") | |
| ) | |
| assert CodestralTextCompletionConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.volcengine import VolcEngineConfig | |
| assert "max_completion_tokens" in VolcEngineConfig().get_supported_openai_params( | |
| model="llama3" | |
| ) | |
| assert VolcEngineConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.ai21.chat.transformation import AI21ChatConfig | |
| assert "max_completion_tokens" in AI21ChatConfig().get_supported_openai_params( | |
| "jamba-1.5-mini@001" | |
| ) | |
| assert AI21ChatConfig().map_openai_params( | |
| model="jamba-1.5-mini@001", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.azure.chat.gpt_transformation import AzureOpenAIConfig | |
| assert "max_completion_tokens" in AzureOpenAIConfig().get_supported_openai_params( | |
| model="gpt-3.5-turbo" | |
| ) | |
| assert AzureOpenAIConfig().map_openai_params( | |
| model="gpt-3.5-turbo", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| api_version="2022-12-01", | |
| drop_params=False, | |
| ) == {"max_completion_tokens": 10} | |
| from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig | |
| assert ( | |
| "max_completion_tokens" | |
| in AmazonConverseConfig().get_supported_openai_params( | |
| model="anthropic.claude-3-sonnet-20240229-v1:0" | |
| ) | |
| ) | |
| assert AmazonConverseConfig().map_openai_params( | |
| model="anthropic.claude-3-sonnet-20240229-v1:0", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"maxTokens": 10} | |
| from litellm.llms.codestral.completion.transformation import ( | |
| CodestralTextCompletionConfig, | |
| ) | |
| assert ( | |
| "max_completion_tokens" | |
| in CodestralTextCompletionConfig().get_supported_openai_params(model="llama3") | |
| ) | |
| assert CodestralTextCompletionConfig().map_openai_params( | |
| model="llama3", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm import AmazonAnthropicClaude3Config, AmazonAnthropicConfig | |
| assert ( | |
| "max_completion_tokens" | |
| in AmazonAnthropicClaude3Config().get_supported_openai_params( | |
| model="anthropic.claude-3-sonnet-20240229-v1:0" | |
| ) | |
| ) | |
| assert AmazonAnthropicClaude3Config().map_openai_params( | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| model="anthropic.claude-3-sonnet-20240229-v1:0", | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| assert ( | |
| "max_completion_tokens" | |
| in AmazonAnthropicConfig().get_supported_openai_params(model="") | |
| ) | |
| assert AmazonAnthropicConfig().map_openai_params( | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| model="", | |
| drop_params=False, | |
| ) == {"max_tokens_to_sample": 10} | |
| from litellm.llms.databricks.chat.transformation import DatabricksConfig | |
| assert "max_completion_tokens" in DatabricksConfig().get_supported_openai_params() | |
| assert DatabricksConfig().map_openai_params( | |
| model="databricks/llama-3-70b-instruct", | |
| drop_params=False, | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation import ( | |
| VertexAIAnthropicConfig, | |
| ) | |
| assert ( | |
| "max_completion_tokens" | |
| in VertexAIAnthropicConfig().get_supported_openai_params( | |
| model="claude-3-5-sonnet-20240620" | |
| ) | |
| ) | |
| assert VertexAIAnthropicConfig().map_openai_params( | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| model="claude-3-5-sonnet-20240620", | |
| drop_params=False, | |
| ) == {"max_tokens": 10} | |
| from litellm.llms.gemini.chat.transformation import GoogleAIStudioGeminiConfig | |
| from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( | |
| VertexGeminiConfig, | |
| ) | |
| assert "max_completion_tokens" in VertexGeminiConfig().get_supported_openai_params( | |
| model="gemini-1.0-pro" | |
| ) | |
| assert VertexGeminiConfig().map_openai_params( | |
| model="gemini-1.0-pro", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_output_tokens": 10} | |
| assert ( | |
| "max_completion_tokens" | |
| in GoogleAIStudioGeminiConfig().get_supported_openai_params( | |
| model="gemini-1.0-pro" | |
| ) | |
| ) | |
| assert GoogleAIStudioGeminiConfig().map_openai_params( | |
| model="gemini-1.0-pro", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_output_tokens": 10} | |
| assert "max_completion_tokens" in VertexGeminiConfig().get_supported_openai_params( | |
| model="gemini-1.0-pro" | |
| ) | |
| assert VertexGeminiConfig().map_openai_params( | |
| model="gemini-1.0-pro", | |
| non_default_params={"max_completion_tokens": 10}, | |
| optional_params={}, | |
| drop_params=False, | |
| ) == {"max_output_tokens": 10} | |
| def test_anthropic_web_search_in_model_info(): | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" | |
| litellm.model_cost = litellm.get_model_cost_map(url="") | |
| supported_models = [ | |
| "anthropic/claude-3-7-sonnet-20250219", | |
| "anthropic/claude-3-5-sonnet-latest", | |
| "anthropic/claude-3-5-sonnet-20241022", | |
| "anthropic/claude-3-5-haiku-20241022", | |
| "anthropic/claude-3-5-haiku-latest", | |
| ] | |
| for model in supported_models: | |
| from litellm.utils import get_model_info | |
| model_info = get_model_info(model) | |
| assert model_info is not None | |
| assert ( | |
| model_info["supports_web_search"] is True | |
| ), f"Model {model} should support web search" | |
| assert ( | |
| model_info["search_context_cost_per_query"] is not None | |
| ), f"Model {model} should have a search context cost per query" | |
| def test_cohere_embedding_optional_params(): | |
| from litellm import get_optional_params_embeddings | |
| optional_params = get_optional_params_embeddings( | |
| model="embed-v4.0", | |
| custom_llm_provider="cohere", | |
| input="Hello, world!", | |
| input_type="search_query", | |
| dimensions=512, | |
| ) | |
| assert optional_params is not None | |
| def test_aaamodel_prices_and_context_window_json_is_valid(): | |
| """ | |
| Validates the `model_prices_and_context_window.json` file. | |
| If this test fails after you update the json, you need to update the schema or correct the change you made. | |
| """ | |
| INTENDED_SCHEMA = { | |
| "type": "object", | |
| "additionalProperties": { | |
| "type": "object", | |
| "properties": { | |
| "supports_computer_use": {"type": "boolean"}, | |
| "cache_creation_input_audio_token_cost": {"type": "number"}, | |
| "cache_creation_input_token_cost": {"type": "number"}, | |
| "cache_read_input_token_cost": {"type": "number"}, | |
| "cache_read_input_audio_token_cost": {"type": "number"}, | |
| "deprecation_date": {"type": "string"}, | |
| "input_cost_per_audio_per_second": {"type": "number"}, | |
| "input_cost_per_audio_per_second_above_128k_tokens": {"type": "number"}, | |
| "input_cost_per_audio_token": {"type": "number"}, | |
| "input_cost_per_character": {"type": "number"}, | |
| "input_cost_per_character_above_128k_tokens": {"type": "number"}, | |
| "input_cost_per_image": {"type": "number"}, | |
| "input_cost_per_image_above_128k_tokens": {"type": "number"}, | |
| "input_cost_per_token_above_200k_tokens": {"type": "number"}, | |
| "input_cost_per_pixel": {"type": "number"}, | |
| "input_cost_per_query": {"type": "number"}, | |
| "input_cost_per_request": {"type": "number"}, | |
| "input_cost_per_second": {"type": "number"}, | |
| "input_cost_per_token": {"type": "number"}, | |
| "input_cost_per_token_above_128k_tokens": {"type": "number"}, | |
| "input_cost_per_token_batch_requests": {"type": "number"}, | |
| "input_cost_per_token_batches": {"type": "number"}, | |
| "input_cost_per_token_cache_hit": {"type": "number"}, | |
| "input_cost_per_video_per_second": {"type": "number"}, | |
| "input_cost_per_video_per_second_above_8s_interval": {"type": "number"}, | |
| "input_cost_per_video_per_second_above_15s_interval": { | |
| "type": "number" | |
| }, | |
| "input_cost_per_video_per_second_above_128k_tokens": {"type": "number"}, | |
| "input_dbu_cost_per_token": {"type": "number"}, | |
| "litellm_provider": {"type": "string"}, | |
| "max_audio_length_hours": {"type": "number"}, | |
| "max_audio_per_prompt": {"type": "number"}, | |
| "max_document_chunks_per_query": {"type": "number"}, | |
| "max_images_per_prompt": {"type": "number"}, | |
| "max_input_tokens": {"type": "number"}, | |
| "max_output_tokens": {"type": "number"}, | |
| "max_pdf_size_mb": {"type": "number"}, | |
| "max_query_tokens": {"type": "number"}, | |
| "max_tokens": {"type": "number"}, | |
| "max_tokens_per_document_chunk": {"type": "number"}, | |
| "max_video_length": {"type": "number"}, | |
| "max_videos_per_prompt": {"type": "number"}, | |
| "metadata": {"type": "object"}, | |
| "mode": { | |
| "type": "string", | |
| "enum": [ | |
| "audio_speech", | |
| "audio_transcription", | |
| "chat", | |
| "completion", | |
| "embedding", | |
| "image_generation", | |
| "moderation", | |
| "rerank", | |
| "responses", | |
| ], | |
| }, | |
| "output_cost_per_audio_token": {"type": "number"}, | |
| "output_cost_per_character": {"type": "number"}, | |
| "output_cost_per_character_above_128k_tokens": {"type": "number"}, | |
| "output_cost_per_image": {"type": "number"}, | |
| "output_cost_per_pixel": {"type": "number"}, | |
| "output_cost_per_second": {"type": "number"}, | |
| "output_cost_per_token": {"type": "number"}, | |
| "output_cost_per_token_above_128k_tokens": {"type": "number"}, | |
| "output_cost_per_token_above_200k_tokens": {"type": "number"}, | |
| "output_cost_per_token_batches": {"type": "number"}, | |
| "output_cost_per_reasoning_token": {"type": "number"}, | |
| "output_db_cost_per_token": {"type": "number"}, | |
| "output_dbu_cost_per_token": {"type": "number"}, | |
| "output_vector_size": {"type": "number"}, | |
| "rpd": {"type": "number"}, | |
| "rpm": {"type": "number"}, | |
| "source": {"type": "string"}, | |
| "supports_assistant_prefill": {"type": "boolean"}, | |
| "supports_audio_input": {"type": "boolean"}, | |
| "supports_audio_output": {"type": "boolean"}, | |
| "supports_embedding_image_input": {"type": "boolean"}, | |
| "supports_function_calling": {"type": "boolean"}, | |
| "supports_image_input": {"type": "boolean"}, | |
| "supports_parallel_function_calling": {"type": "boolean"}, | |
| "supports_pdf_input": {"type": "boolean"}, | |
| "supports_prompt_caching": {"type": "boolean"}, | |
| "supports_response_schema": {"type": "boolean"}, | |
| "supports_system_messages": {"type": "boolean"}, | |
| "supports_tool_choice": {"type": "boolean"}, | |
| "supports_video_input": {"type": "boolean"}, | |
| "supports_vision": {"type": "boolean"}, | |
| "supports_web_search": {"type": "boolean"}, | |
| "supports_url_context": {"type": "boolean"}, | |
| "supports_reasoning": {"type": "boolean"}, | |
| "tool_use_system_prompt_tokens": {"type": "number"}, | |
| "tpm": {"type": "number"}, | |
| "supported_endpoints": { | |
| "type": "array", | |
| "items": { | |
| "type": "string", | |
| "enum": [ | |
| "/v1/responses", | |
| "/v1/embeddings", | |
| "/v1/chat/completions", | |
| "/v1/completions", | |
| "/v1/images/generations", | |
| "/v1/images/variations", | |
| "/v1/images/edits", | |
| "/v1/batch", | |
| "/v1/audio/transcriptions", | |
| "/v1/audio/speech", | |
| ], | |
| }, | |
| }, | |
| "supported_regions": { | |
| "type": "array", | |
| "items": { | |
| "type": "string", | |
| }, | |
| }, | |
| "search_context_cost_per_query": { | |
| "type": "object", | |
| "properties": { | |
| "search_context_size_low": {"type": "number"}, | |
| "search_context_size_medium": {"type": "number"}, | |
| "search_context_size_high": {"type": "number"}, | |
| }, | |
| "additionalProperties": False, | |
| }, | |
| "supported_modalities": { | |
| "type": "array", | |
| "items": { | |
| "type": "string", | |
| "enum": ["text", "audio", "image", "video"], | |
| }, | |
| }, | |
| "supported_output_modalities": { | |
| "type": "array", | |
| "items": { | |
| "type": "string", | |
| "enum": ["text", "image", "audio", "code"], | |
| }, | |
| }, | |
| "supports_native_streaming": {"type": "boolean"}, | |
| }, | |
| "additionalProperties": False, | |
| }, | |
| } | |
| prod_json = "./model_prices_and_context_window.json" | |
| # prod_json = "../../model_prices_and_context_window.json" | |
| with open(prod_json, "r") as model_prices_file: | |
| actual_json = json.load(model_prices_file) | |
| assert isinstance(actual_json, dict) | |
| actual_json.pop( | |
| "sample_spec", None | |
| ) # remove the sample, whose schema is inconsistent with the real data | |
| validate(actual_json, INTENDED_SCHEMA) | |
| def test_get_model_info_gemini(): | |
| """ | |
| Tests if ALL gemini models have 'tpm' and 'rpm' in the model info | |
| """ | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" | |
| litellm.model_cost = litellm.get_model_cost_map(url="") | |
| model_map = litellm.model_cost | |
| for model, info in model_map.items(): | |
| if ( | |
| model.startswith("gemini/") | |
| and not "gemma" in model | |
| and not "learnlm" in model | |
| ): | |
| assert info.get("tpm") is not None, f"{model} does not have tpm" | |
| assert info.get("rpm") is not None, f"{model} does not have rpm" | |
| def test_openai_models_in_model_info(): | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" | |
| litellm.model_cost = litellm.get_model_cost_map(url="") | |
| model_map = litellm.model_cost | |
| violated_models = [] | |
| for model, info in model_map.items(): | |
| if ( | |
| info.get("litellm_provider") == "openai" | |
| and info.get("supports_vision") is True | |
| ): | |
| if info.get("supports_pdf_input") is not True: | |
| violated_models.append(model) | |
| assert ( | |
| len(violated_models) == 0 | |
| ), f"The following models should support pdf input: {violated_models}" | |
| def test_supports_tool_choice_simple_tests(): | |
| """ | |
| simple sanity checks | |
| """ | |
| assert litellm.utils.supports_tool_choice(model="gpt-4o") == True | |
| assert ( | |
| litellm.utils.supports_tool_choice( | |
| model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0" | |
| ) | |
| == True | |
| ) | |
| assert ( | |
| litellm.utils.supports_tool_choice( | |
| model="anthropic.claude-3-sonnet-20240229-v1:0" | |
| ) | |
| is True | |
| ) | |
| assert ( | |
| litellm.utils.supports_tool_choice( | |
| model="anthropic.claude-3-sonnet-20240229-v1:0", | |
| custom_llm_provider="bedrock_converse", | |
| ) | |
| is True | |
| ) | |
| assert ( | |
| litellm.utils.supports_tool_choice(model="us.amazon.nova-micro-v1:0") is False | |
| ) | |
| assert ( | |
| litellm.utils.supports_tool_choice(model="bedrock/us.amazon.nova-micro-v1:0") | |
| is False | |
| ) | |
| assert ( | |
| litellm.utils.supports_tool_choice( | |
| model="us.amazon.nova-micro-v1:0", custom_llm_provider="bedrock_converse" | |
| ) | |
| is False | |
| ) | |
| assert litellm.utils.supports_tool_choice(model="perplexity/sonar") is False | |
| def test_check_provider_match(): | |
| """ | |
| Test the _check_provider_match function for various provider scenarios | |
| """ | |
| # Test bedrock and bedrock_converse cases | |
| model_info = {"litellm_provider": "bedrock"} | |
| assert litellm.utils._check_provider_match(model_info, "bedrock") is True | |
| assert litellm.utils._check_provider_match(model_info, "bedrock_converse") is True | |
| # Test bedrock_converse provider | |
| model_info = {"litellm_provider": "bedrock_converse"} | |
| assert litellm.utils._check_provider_match(model_info, "bedrock") is True | |
| assert litellm.utils._check_provider_match(model_info, "bedrock_converse") is True | |
| # Test non-matching provider | |
| model_info = {"litellm_provider": "bedrock"} | |
| assert litellm.utils._check_provider_match(model_info, "openai") is False | |
| # Models that should be skipped during testing | |
| OLD_PROVIDERS = ["aleph_alpha", "palm"] | |
| SKIP_MODELS = [ | |
| "azure/mistral", | |
| "azure/command-r", | |
| "jamba", | |
| "deepinfra", | |
| "mistral.", | |
| "groq/llama-guard-3-8b", | |
| "groq/gemma2-9b-it", | |
| ] | |
| # Bedrock models to block - organized by type | |
| BEDROCK_REGIONS = ["ap-northeast-1", "eu-central-1", "us-east-1", "us-west-2"] | |
| BEDROCK_COMMITMENTS = ["1-month-commitment", "6-month-commitment"] | |
| BEDROCK_MODELS = { | |
| "anthropic.claude-v1", | |
| "anthropic.claude-v2", | |
| "anthropic.claude-v2:1", | |
| "anthropic.claude-instant-v1", | |
| } | |
| # Generate block_list dynamically | |
| block_list = set() | |
| for region in BEDROCK_REGIONS: | |
| for commitment in BEDROCK_COMMITMENTS: | |
| for model in BEDROCK_MODELS: | |
| block_list.add(f"bedrock/{region}/{commitment}/{model}") | |
| block_list.add(f"bedrock/{region}/{model}") | |
| # Add Cohere models | |
| for commitment in BEDROCK_COMMITMENTS: | |
| block_list.add(f"bedrock/*/{commitment}/cohere.command-text-v14") | |
| block_list.add(f"bedrock/*/{commitment}/cohere.command-light-text-v14") | |
| print("block_list", block_list) | |
| async def test_supports_tool_choice(): | |
| """ | |
| Test that litellm.utils.supports_tool_choice() returns the correct value | |
| for all models in model_prices_and_context_window.json. | |
| The test: | |
| 1. Loads model pricing data | |
| 2. Iterates through each model | |
| 3. Checks if tool_choice support matches the model's supported parameters | |
| """ | |
| # Load model prices | |
| litellm._turn_on_debug() | |
| # path = "../../model_prices_and_context_window.json" | |
| path = "./model_prices_and_context_window.json" | |
| with open(path, "r") as f: | |
| model_prices = json.load(f) | |
| litellm.model_cost = model_prices | |
| config_manager = ProviderConfigManager() | |
| for model_name, model_info in model_prices.items(): | |
| print(f"testing model: {model_name}") | |
| # Skip certain models | |
| if ( | |
| model_name == "sample_spec" | |
| or model_info.get("mode") != "chat" | |
| or any(skip in model_name for skip in SKIP_MODELS) | |
| or any(provider in model_name for provider in OLD_PROVIDERS) | |
| or model_info["litellm_provider"] in OLD_PROVIDERS | |
| or model_name in block_list | |
| or "azure/eu" in model_name | |
| or "azure/us" in model_name | |
| or "codestral" in model_name | |
| or "o1" in model_name | |
| or "o3" in model_name | |
| or "mistral" in model_name | |
| ): | |
| continue | |
| try: | |
| model, provider, _, _ = get_llm_provider(model=model_name) | |
| except Exception as e: | |
| print(f"\033[91mERROR for {model_name}: {e}\033[0m") | |
| continue | |
| # Get provider config and supported params | |
| print("LLM provider", provider) | |
| provider_enum = LlmProviders(provider) | |
| config = config_manager.get_provider_chat_config(model, provider_enum) | |
| print("config", config) | |
| if config: | |
| supported_params = config.get_supported_openai_params(model) | |
| print("supported_params", supported_params) | |
| else: | |
| raise Exception(f"No config found for {model_name}, provider: {provider}") | |
| # Check tool_choice support | |
| supports_tool_choice_result = litellm.utils.supports_tool_choice( | |
| model=model_name, custom_llm_provider=provider | |
| ) | |
| tool_choice_in_params = "tool_choice" in supported_params | |
| assert ( | |
| supports_tool_choice_result == tool_choice_in_params | |
| ), f"Tool choice support mismatch for {model_name}. supports_tool_choice() returned: {supports_tool_choice_result}, tool_choice in supported params: {tool_choice_in_params}\nConfig: {config}" | |
| def test_supports_computer_use_utility(): | |
| """ | |
| Tests the litellm.utils.supports_computer_use utility function. | |
| """ | |
| from litellm.utils import supports_computer_use | |
| # Ensure LITELLM_LOCAL_MODEL_COST_MAP is set for consistent test behavior, | |
| # as supports_computer_use relies on get_model_info. | |
| # This also requires litellm.model_cost to be populated. | |
| original_env_var = os.getenv("LITELLM_LOCAL_MODEL_COST_MAP") | |
| original_model_cost = getattr(litellm, "model_cost", None) | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" | |
| litellm.model_cost = litellm.get_model_cost_map(url="") # Load with local/backup | |
| try: | |
| # Test a model known to support computer_use from backup JSON | |
| supports_cu_anthropic = supports_computer_use( | |
| model="anthropic/claude-3-7-sonnet-20250219" | |
| ) | |
| assert supports_cu_anthropic is True | |
| # Test a model known not to have the flag or set to false (defaults to False via get_model_info) | |
| supports_cu_gpt = supports_computer_use(model="gpt-3.5-turbo") | |
| assert supports_cu_gpt is False | |
| finally: | |
| # Restore original environment and model_cost to avoid side effects | |
| if original_env_var is None: | |
| del os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] | |
| else: | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = original_env_var | |
| if original_model_cost is not None: | |
| litellm.model_cost = original_model_cost | |
| elif hasattr(litellm, "model_cost"): | |
| delattr(litellm, "model_cost") | |
| def test_get_model_info_shows_supports_computer_use(): | |
| """ | |
| Tests if 'supports_computer_use' is correctly retrieved by get_model_info. | |
| We'll use 'claude-3-7-sonnet-20250219' as it's configured | |
| in the backup JSON to have supports_computer_use: True. | |
| """ | |
| os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" | |
| # Ensure litellm.model_cost is loaded, relying on the backup mechanism if primary fails | |
| # as per previous debugging. | |
| litellm.model_cost = litellm.get_model_cost_map(url="") | |
| # This model should have 'supports_computer_use': True in the backup JSON | |
| model_known_to_support_computer_use = "claude-3-7-sonnet-20250219" | |
| info = litellm.get_model_info(model_known_to_support_computer_use) | |
| print(f"Info for {model_known_to_support_computer_use}: {info}") | |
| # After the fix in utils.py, this should now be present and True | |
| assert info.get("supports_computer_use") is True | |
| # Optionally, test a model known NOT to support it, or where it's undefined (should default to False) | |
| # For example, if "gpt-3.5-turbo" doesn't have it defined, it should be False. | |
| model_known_not_to_support_computer_use = "gpt-3.5-turbo" | |
| info_gpt = litellm.get_model_info(model_known_not_to_support_computer_use) | |
| print(f"Info for {model_known_not_to_support_computer_use}: {info_gpt}") | |
| assert ( | |
| info_gpt.get("supports_computer_use") is None | |
| ) # Expecting None due to the default in ModelInfoBase | |
| def test_pre_process_non_default_params(model, custom_llm_provider): | |
| from pydantic import BaseModel | |
| from litellm.utils import pre_process_non_default_params | |
| class ResponseFormat(BaseModel): | |
| x: str | |
| y: str | |
| passed_params = { | |
| "model": "gpt-3.5-turbo", | |
| "response_format": ResponseFormat, | |
| } | |
| special_params = {} | |
| processed_non_default_params = pre_process_non_default_params( | |
| model=model, | |
| passed_params=passed_params, | |
| special_params=special_params, | |
| custom_llm_provider=custom_llm_provider, | |
| additional_drop_params=None, | |
| ) | |
| print(processed_non_default_params) | |
| assert processed_non_default_params == { | |
| "response_format": { | |
| "type": "json_schema", | |
| "json_schema": { | |
| "schema": { | |
| "properties": { | |
| "x": {"title": "X", "type": "string"}, | |
| "y": {"title": "Y", "type": "string"}, | |
| }, | |
| "required": ["x", "y"], | |
| "title": "ResponseFormat", | |
| "type": "object", | |
| "additionalProperties": False, | |
| }, | |
| "name": "ResponseFormat", | |
| "strict": True, | |
| }, | |
| } | |
| } | |
| from litellm.utils import supports_function_calling | |
| class TestProxyFunctionCalling: | |
| """Test class for proxy function calling capabilities.""" | |
| def reset_mock_cache(self): | |
| """Reset model cache before each test.""" | |
| from litellm.utils import _model_cache | |
| _model_cache.flush_cache() | |
| def test_proxy_function_calling_support_consistency( | |
| self, direct_model, proxy_model, expected_result | |
| ): | |
| """Test that proxy models have the same function calling support as their direct counterparts.""" | |
| direct_result = supports_function_calling(direct_model) | |
| proxy_result = supports_function_calling(proxy_model) | |
| # Both should match the expected result | |
| assert ( | |
| direct_result == expected_result | |
| ), f"Direct model {direct_model} should return {expected_result}" | |
| assert ( | |
| proxy_result == expected_result | |
| ), f"Proxy model {proxy_model} should return {expected_result}" | |
| # Direct and proxy should be consistent | |
| assert ( | |
| direct_result == proxy_result | |
| ), f"Mismatch: {direct_model}={direct_result} vs {proxy_model}={proxy_result}" | |
| def test_proxy_custom_model_names_without_config( | |
| self, proxy_model_name, underlying_model, expected_proxy_result | |
| ): | |
| """ | |
| Test proxy models with custom model names that differ from underlying models. | |
| Without proxy configuration context, LiteLLM cannot resolve custom model names | |
| to their underlying models, so these will return False. | |
| This demonstrates the limitation and documents the expected behavior. | |
| """ | |
| # Test the underlying model directly first to establish what it SHOULD return | |
| try: | |
| underlying_result = supports_function_calling(underlying_model) | |
| print( | |
| f"Underlying model {underlying_model} supports function calling: {underlying_result}" | |
| ) | |
| except Exception as e: | |
| print(f"Warning: Could not test underlying model {underlying_model}: {e}") | |
| # Test the proxy model - this will return False due to lack of configuration context | |
| proxy_result = supports_function_calling(proxy_model_name) | |
| assert ( | |
| proxy_result == expected_proxy_result | |
| ), f"Proxy model {proxy_model_name} should return {expected_proxy_result} (without config context)" | |
| def test_proxy_model_resolution_with_custom_names_documentation(self): | |
| """ | |
| Document the behavior and limitation for custom proxy model names. | |
| This test demonstrates: | |
| 1. The current limitation with custom model names | |
| 2. How the proxy server would handle this in production | |
| 3. The expected behavior for both scenarios | |
| """ | |
| # Case 1: Custom model name that cannot be resolved | |
| custom_model = "litellm_proxy/my-custom-claude" | |
| result = supports_function_calling(custom_model) | |
| assert ( | |
| result is False | |
| ), "Custom model names return False without proxy config context" | |
| # Case 2: Model name that can be resolved (matches pattern) | |
| resolvable_model = "litellm_proxy/claude-3-sonnet-20240229" | |
| result = supports_function_calling(resolvable_model) | |
| assert result is True, "Resolvable model names work with fallback logic" | |
| # Documentation notes: | |
| print( | |
| """ | |
| PROXY MODEL RESOLUTION BEHAVIOR: | |
| ✅ WORKS (with current fallback logic): | |
| - litellm_proxy/gpt-4 | |
| - litellm_proxy/claude-3-sonnet-20240229 | |
| - litellm_proxy/anthropic/claude-3-haiku-20240307 | |
| ❌ DOESN'T WORK (requires proxy server config): | |
| - litellm_proxy/my-custom-gpt4 | |
| - litellm_proxy/bedrock-claude-3-haiku | |
| - litellm_proxy/production-model | |
| 💡 SOLUTION: Use LiteLLM proxy server with proper model_list configuration | |
| that maps custom names to underlying models. | |
| """ | |
| ) | |
| def test_proxy_models_with_naming_hints( | |
| self, proxy_model_with_hints, expected_result | |
| ): | |
| """ | |
| Test proxy models with names that provide hints about the underlying model. | |
| Note: These will currently fail because the hint-based resolution isn't implemented yet, | |
| but they demonstrate what could be possible with enhanced model name inference. | |
| """ | |
| # This test documents potential future enhancement | |
| proxy_result = supports_function_calling(proxy_model_with_hints) | |
| # Currently these will return False, but we document the expected behavior | |
| # In the future, we could implement smarter model name inference | |
| print( | |
| f"Model {proxy_model_with_hints}: current={proxy_result}, desired={expected_result}" | |
| ) | |
| # For now, we expect False (current behavior), but document the limitation | |
| assert ( | |
| proxy_result is False | |
| ), f"Current limitation: {proxy_model_with_hints} returns False without inference" | |
| def test_proxy_only_function_calling_support(self, proxy_model, expected_result): | |
| """ | |
| Test proxy models independently to ensure they report correct function calling support. | |
| This test focuses on proxy models without comparing to direct models, | |
| useful for cases where we only care about the proxy behavior. | |
| """ | |
| try: | |
| result = supports_function_calling(model=proxy_model) | |
| assert ( | |
| result == expected_result | |
| ), f"Proxy model {proxy_model} returned {result}, expected {expected_result}" | |
| except Exception as e: | |
| pytest.fail(f"Error testing proxy model {proxy_model}: {e}") | |
| def test_litellm_utils_supports_function_calling_import(self): | |
| """Test that supports_function_calling can be imported from litellm.utils.""" | |
| try: | |
| from litellm.utils import supports_function_calling | |
| assert callable(supports_function_calling) | |
| except ImportError as e: | |
| pytest.fail(f"Failed to import supports_function_calling: {e}") | |
| def test_litellm_supports_function_calling_import(self): | |
| """Test that supports_function_calling can be imported from litellm directly.""" | |
| try: | |
| import litellm | |
| assert hasattr(litellm, "supports_function_calling") | |
| assert callable(litellm.supports_function_calling) | |
| except Exception as e: | |
| pytest.fail(f"Failed to access litellm.supports_function_calling: {e}") | |
| def test_proxy_model_with_custom_llm_provider_none(self, model_name): | |
| """ | |
| Test proxy models with custom_llm_provider=None parameter. | |
| This tests the supports_function_calling function with the custom_llm_provider | |
| parameter explicitly set to None, which is a common usage pattern. | |
| """ | |
| try: | |
| result = supports_function_calling( | |
| model=model_name, custom_llm_provider=None | |
| ) | |
| # All the models in this test should support function calling | |
| assert ( | |
| result is True | |
| ), f"Model {model_name} should support function calling but returned {result}" | |
| except Exception as e: | |
| pytest.fail( | |
| f"Error testing {model_name} with custom_llm_provider=None: {e}" | |
| ) | |
| def test_edge_cases_and_malformed_proxy_models(self): | |
| """Test edge cases and malformed proxy model names.""" | |
| test_cases = [ | |
| ("litellm_proxy/", False), # Empty model name after proxy prefix | |
| ("litellm_proxy", False), # Just the proxy prefix without slash | |
| ("litellm_proxy//gpt-3.5-turbo", False), # Double slash | |
| ("litellm_proxy/nonexistent-model", False), # Non-existent model | |
| ] | |
| for model_name, expected_result in test_cases: | |
| try: | |
| result = supports_function_calling(model=model_name) | |
| # For malformed models, we expect False or the function to handle gracefully | |
| assert ( | |
| result == expected_result | |
| ), f"Edge case {model_name} returned {result}, expected {expected_result}" | |
| except Exception: | |
| # It's acceptable for malformed model names to raise exceptions | |
| # rather than returning False, as long as they're handled gracefully | |
| pass | |
| def test_proxy_model_resolution_demonstration(self): | |
| """ | |
| Demonstration test showing the current issue with proxy model resolution. | |
| This test documents the current behavior and can be used to verify | |
| when the issue is fixed. | |
| """ | |
| direct_model = "gpt-3.5-turbo" | |
| proxy_model = "litellm_proxy/gpt-3.5-turbo" | |
| direct_result = supports_function_calling(model=direct_model) | |
| proxy_result = supports_function_calling(model=proxy_model) | |
| print(f"\nDemonstration of proxy model resolution:") | |
| print( | |
| f"Direct model '{direct_model}' supports function calling: {direct_result}" | |
| ) | |
| print(f"Proxy model '{proxy_model}' supports function calling: {proxy_result}") | |
| # This assertion will currently fail due to the bug | |
| # When the bug is fixed, this test should pass | |
| if direct_result != proxy_result: | |
| pytest.skip( | |
| f"Known issue: Proxy model resolution inconsistency. " | |
| f"Direct: {direct_result}, Proxy: {proxy_result}. " | |
| f"This test will pass when the issue is resolved." | |
| ) | |
| assert direct_result == proxy_result, ( | |
| f"Proxy model resolution issue: {direct_model} -> {direct_result}, " | |
| f"{proxy_model} -> {proxy_result}" | |
| ) | |
| def test_bedrock_converse_api_proxy_mappings( | |
| self, | |
| proxy_model_name, | |
| underlying_bedrock_model, | |
| expected_proxy_result, | |
| description, | |
| ): | |
| """ | |
| Test real-world Bedrock Converse API proxy model mappings. | |
| This test covers the specific scenario where proxy model names like | |
| 'bedrock-claude-3-haiku' map to underlying Bedrock Converse API models like | |
| 'bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0'. | |
| These mappings are typically defined in proxy server configuration files | |
| and cannot be resolved by LiteLLM without that context. | |
| """ | |
| print(f"\nTesting: {description}") | |
| print(f" Proxy model: {proxy_model_name}") | |
| print(f" Underlying model: {underlying_bedrock_model}") | |
| # Test the underlying model directly to verify it supports function calling | |
| try: | |
| underlying_result = supports_function_calling(underlying_bedrock_model) | |
| print(f" Underlying model function calling support: {underlying_result}") | |
| # Most Bedrock Converse API models with Anthropic Claude should support function calling | |
| if "anthropic.claude-3" in underlying_bedrock_model: | |
| assert ( | |
| underlying_result is True | |
| ), f"Claude 3 models should support function calling: {underlying_bedrock_model}" | |
| except Exception as e: | |
| print( | |
| f" Warning: Could not test underlying model {underlying_bedrock_model}: {e}" | |
| ) | |
| # Test the proxy model - should return False due to lack of configuration context | |
| proxy_result = supports_function_calling(proxy_model_name) | |
| print(f" Proxy model function calling support: {proxy_result}") | |
| assert proxy_result == expected_proxy_result, ( | |
| f"Proxy model {proxy_model_name} should return {expected_proxy_result} " | |
| f"(without config context). Description: {description}" | |
| ) | |
| def test_real_world_proxy_config_documentation(self): | |
| """ | |
| Document how real-world proxy configurations would handle model mappings. | |
| This test provides documentation on how the proxy server configuration | |
| would typically map custom model names to underlying models. | |
| """ | |
| print( | |
| """ | |
| REAL-WORLD PROXY SERVER CONFIGURATION EXAMPLE: | |
| =============================================== | |
| In a proxy_server_config.yaml file, you would define: | |
| model_list: | |
| - model_name: bedrock-claude-3-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: bedrock-claude-3-sonnet | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: prod-claude-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/PROD_AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/PROD_AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-west-2 | |
| FUNCTION CALLING WITH PROXY SERVER: | |
| =================================== | |
| When using the proxy server with this configuration: | |
| 1. Client calls: supports_function_calling("bedrock-claude-3-haiku") | |
| 2. Proxy server resolves to: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| 3. LiteLLM evaluates the underlying model's capabilities | |
| 4. Returns: True (because Claude 3 Haiku supports function calling) | |
| Without the proxy server configuration context, LiteLLM cannot resolve | |
| the custom model name and returns False. | |
| BEDROCK CONVERSE API BENEFITS: | |
| ============================== | |
| The Bedrock Converse API provides: | |
| - Standardized function calling interface across providers | |
| - Better tool use capabilities compared to legacy APIs | |
| - Consistent request/response format | |
| - Enhanced streaming support for function calls | |
| """ | |
| ) | |
| # Verify that direct underlying models work as expected | |
| bedrock_models = [ | |
| "bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0", | |
| "bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0", | |
| "bedrock/converse/anthropic.claude-3-opus-20240229-v1:0", | |
| ] | |
| for model in bedrock_models: | |
| try: | |
| result = supports_function_calling(model) | |
| print(f"Direct test - {model}: {result}") | |
| # Claude 3 models should support function calling | |
| assert ( | |
| result is True | |
| ), f"Claude 3 model should support function calling: {model}" | |
| except Exception as e: | |
| print(f"Could not test {model}: {e}") | |
| def test_bedrock_converse_api_proxy_mappings( | |
| self, | |
| proxy_model_name, | |
| underlying_bedrock_model, | |
| expected_proxy_result, | |
| description, | |
| ): | |
| """ | |
| Test real-world Bedrock Converse API proxy model mappings. | |
| This test covers the specific scenario where proxy model names like | |
| 'bedrock-claude-3-haiku' map to underlying Bedrock Converse API models like | |
| 'bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0'. | |
| These mappings are typically defined in proxy server configuration files | |
| and cannot be resolved by LiteLLM without that context. | |
| """ | |
| print(f"\nTesting: {description}") | |
| print(f" Proxy model: {proxy_model_name}") | |
| print(f" Underlying model: {underlying_bedrock_model}") | |
| # Test the underlying model directly to verify it supports function calling | |
| try: | |
| underlying_result = supports_function_calling(underlying_bedrock_model) | |
| print(f" Underlying model function calling support: {underlying_result}") | |
| # Most Bedrock Converse API models with Anthropic Claude should support function calling | |
| if "anthropic.claude-3" in underlying_bedrock_model: | |
| assert ( | |
| underlying_result is True | |
| ), f"Claude 3 models should support function calling: {underlying_bedrock_model}" | |
| except Exception as e: | |
| print( | |
| f" Warning: Could not test underlying model {underlying_bedrock_model}: {e}" | |
| ) | |
| # Test the proxy model - should return False due to lack of configuration context | |
| proxy_result = supports_function_calling(proxy_model_name) | |
| print(f" Proxy model function calling support: {proxy_result}") | |
| assert proxy_result == expected_proxy_result, ( | |
| f"Proxy model {proxy_model_name} should return {expected_proxy_result} " | |
| f"(without config context). Description: {description}" | |
| ) | |
| def test_real_world_proxy_config_documentation(self): | |
| """ | |
| Document how real-world proxy configurations would handle model mappings. | |
| This test provides documentation on how the proxy server configuration | |
| would typically map custom model names to underlying models. | |
| """ | |
| print( | |
| """ | |
| REAL-WORLD PROXY SERVER CONFIGURATION EXAMPLE: | |
| =============================================== | |
| In a proxy_server_config.yaml file, you would define: | |
| model_list: | |
| - model_name: bedrock-claude-3-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: bedrock-claude-3-sonnet | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: prod-claude-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/PROD_AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/PROD_AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-west-2 | |
| FUNCTION CALLING WITH PROXY SERVER: | |
| =================================== | |
| When using the proxy server with this configuration: | |
| 1. Client calls: supports_function_calling("bedrock-claude-3-haiku") | |
| 2. Proxy server resolves to: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| 3. LiteLLM evaluates the underlying model's capabilities | |
| 4. Returns: True (because Claude 3 Haiku supports function calling) | |
| Without the proxy server configuration context, LiteLLM cannot resolve | |
| the custom model name and returns False. | |
| BEDROCK CONVERSE API BENEFITS: | |
| ============================== | |
| The Bedrock Converse API provides: | |
| - Standardized function calling interface across providers | |
| - Better tool use capabilities compared to legacy APIs | |
| - Consistent request/response format | |
| - Enhanced streaming support for function calls | |
| """ | |
| ) | |
| # Verify that direct underlying models work as expected | |
| bedrock_models = [ | |
| "bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0", | |
| "bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0", | |
| "bedrock/converse/anthropic.claude-3-opus-20240229-v1:0", | |
| ] | |
| for model in bedrock_models: | |
| try: | |
| result = supports_function_calling(model) | |
| print(f"Direct test - {model}: {result}") | |
| # Claude 3 models should support function calling | |
| assert ( | |
| result is True | |
| ), f"Claude 3 model should support function calling: {model}" | |
| except Exception as e: | |
| print(f"Could not test {model}: {e}") | |
| def test_bedrock_converse_api_proxy_mappings( | |
| self, | |
| proxy_model_name, | |
| underlying_bedrock_model, | |
| expected_proxy_result, | |
| description, | |
| ): | |
| """ | |
| Test real-world Bedrock Converse API proxy model mappings. | |
| This test covers the specific scenario where proxy model names like | |
| 'bedrock-claude-3-haiku' map to underlying Bedrock Converse API models like | |
| 'bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0'. | |
| These mappings are typically defined in proxy server configuration files | |
| and cannot be resolved by LiteLLM without that context. | |
| """ | |
| print(f"\nTesting: {description}") | |
| print(f" Proxy model: {proxy_model_name}") | |
| print(f" Underlying model: {underlying_bedrock_model}") | |
| # Test the underlying model directly to verify it supports function calling | |
| try: | |
| underlying_result = supports_function_calling(underlying_bedrock_model) | |
| print(f" Underlying model function calling support: {underlying_result}") | |
| # Most Bedrock Converse API models with Anthropic Claude should support function calling | |
| if "anthropic.claude-3" in underlying_bedrock_model: | |
| assert ( | |
| underlying_result is True | |
| ), f"Claude 3 models should support function calling: {underlying_bedrock_model}" | |
| except Exception as e: | |
| print( | |
| f" Warning: Could not test underlying model {underlying_bedrock_model}: {e}" | |
| ) | |
| # Test the proxy model - should return False due to lack of configuration context | |
| proxy_result = supports_function_calling(proxy_model_name) | |
| print(f" Proxy model function calling support: {proxy_result}") | |
| assert proxy_result == expected_proxy_result, ( | |
| f"Proxy model {proxy_model_name} should return {expected_proxy_result} " | |
| f"(without config context). Description: {description}" | |
| ) | |
| def test_real_world_proxy_config_documentation(self): | |
| """ | |
| Document how real-world proxy configurations would handle model mappings. | |
| This test provides documentation on how the proxy server configuration | |
| would typically map custom model names to underlying models. | |
| """ | |
| print( | |
| """ | |
| REAL-WORLD PROXY SERVER CONFIGURATION EXAMPLE: | |
| =============================================== | |
| In a proxy_server_config.yaml file, you would define: | |
| model_list: | |
| - model_name: bedrock-claude-3-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: bedrock-claude-3-sonnet | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0 | |
| aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-east-1 | |
| - model_name: prod-claude-haiku | |
| litellm_params: | |
| model: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| aws_access_key_id: os.environ/PROD_AWS_ACCESS_KEY_ID | |
| aws_secret_access_key: os.environ/PROD_AWS_SECRET_ACCESS_KEY | |
| aws_region_name: us-west-2 | |
| FUNCTION CALLING WITH PROXY SERVER: | |
| =================================== | |
| When using the proxy server with this configuration: | |
| 1. Client calls: supports_function_calling("bedrock-claude-3-haiku") | |
| 2. Proxy server resolves to: bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0 | |
| 3. LiteLLM evaluates the underlying model's capabilities | |
| 4. Returns: True (because Claude 3 Haiku supports function calling) | |
| Without the proxy server configuration context, LiteLLM cannot resolve | |
| the custom model name and returns False. | |
| BEDROCK CONVERSE API BENEFITS: | |
| ============================== | |
| The Bedrock Converse API provides: | |
| - Standardized function calling interface across providers | |
| - Better tool use capabilities compared to legacy APIs | |
| - Consistent request/response format | |
| - Enhanced streaming support for function calls | |
| """ | |
| ) | |
| # Verify that direct underlying models work as expected | |
| bedrock_models = [ | |
| "bedrock/converse/anthropic.claude-3-haiku-20240307-v1:0", | |
| "bedrock/converse/anthropic.claude-3-sonnet-20240229-v1:0", | |
| "bedrock/converse/anthropic.claude-3-opus-20240229-v1:0", | |
| ] | |
| for model in bedrock_models: | |
| try: | |
| result = supports_function_calling(model) | |
| print(f"Direct test - {model}: {result}") | |
| # Claude 3 models should support function calling | |
| assert ( | |
| result is True | |
| ), f"Claude 3 model should support function calling: {model}" | |
| except Exception as e: | |
| print(f"Could not test {model}: {e}") | |
| def test_register_model_with_scientific_notation(): | |
| """ | |
| Test that the register_model function can handle scientific notation in the model name. | |
| """ | |
| model_cost_dict = { | |
| "my-custom-model": { | |
| "max_tokens": 8192, | |
| "input_cost_per_token": "3e-07", | |
| "output_cost_per_token": "6e-07", | |
| "litellm_provider": "openai", | |
| "mode": "chat", | |
| }, | |
| } | |
| litellm.register_model(model_cost_dict) | |
| registered_model = litellm.model_cost["my-custom-model"] | |
| print(registered_model) | |
| assert registered_model["input_cost_per_token"] == 3e-07 | |
| assert registered_model["output_cost_per_token"] == 6e-07 | |
| assert registered_model["litellm_provider"] == "openai" | |
| assert registered_model["mode"] == "chat" | |
| if __name__ == "__main__": | |
| # Allow running this test file directly for debugging | |
| pytest.main([__file__, "-v"]) | |