""" Tests for Evals API operations across providers """ import os import sys from abc import ABC, abstractmethod from typing import Optional import pytest sys.path.insert(0, os.path.abspath("../..")) import litellm from litellm.types.llms.openai_evals import ( CancelEvalResponse, DeleteEvalResponse, Eval, ListEvalsResponse, ) class BaseEvalsAPITest(ABC): """ Base test class for Evals API operations. Tests create, list, get, update, delete, and cancel operations. """ @abstractmethod def get_custom_llm_provider(self) -> str: """Return the provider name (e.g., 'openai')""" pass @abstractmethod def get_api_key(self) -> Optional[str]: """Return the API key for the provider""" pass @abstractmethod def get_api_base(self) -> Optional[str]: """Return the API base URL for the provider""" pass @pytest.mark.flaky(retries=3, delay=2) def test_create_eval(self): """ Test creating an evaluation. """ import time custom_llm_provider = self.get_custom_llm_provider() api_key = self.get_api_key() api_base = self.get_api_base() if not api_key: pytest.skip(f"No API key provided for {custom_llm_provider}") litellm.set_verbose = True # Create eval with stored_completions data source unique_name = f"Test Eval {int(time.time())}" try: response = litellm.create_eval( name=unique_name, data_source_config={ "type": "stored_completions", "metadata": {"usecase": "chatbot"}, }, testing_criteria=[ { "type": "label_model", "model": "gpt-4o", "input": [ { "role": "developer", "content": "Classify the sentiment as 'positive' or 'negative'", }, {"role": "user", "content": "Statement: {{item.input}}"}, ], "passing_labels": ["positive"], "labels": ["positive", "negative"], "name": "Sentiment grader", } ], custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) except (litellm.InternalServerError, litellm.APIConnectionError, litellm.Timeout, litellm.ServiceUnavailableError): pytest.skip("Provider service unavailable") except litellm.RateLimitError: pytest.skip("Rate limit exceeded") assert response is not None assert isinstance(response, Eval) assert response.id is not None assert response.name == unique_name print(f"Created eval: {response}") print(f"Eval ID: {response.id}") def test_list_evals(self): """ Test listing evaluations. """ custom_llm_provider = self.get_custom_llm_provider() api_key = self.get_api_key() api_base = self.get_api_base() if not api_key: pytest.skip(f"No API key provided for {custom_llm_provider}") litellm.set_verbose = True response = litellm.list_evals( limit=10, custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) assert response is not None assert isinstance(response, ListEvalsResponse) assert hasattr(response, "data") assert hasattr(response, "has_more") print(f"Listed evals: {len(response.data)} evaluations") def test_get_eval(self): """ Test getting a specific evaluation by ID. """ custom_llm_provider = self.get_custom_llm_provider() api_key = self.get_api_key() api_base = self.get_api_base() if not api_key: pytest.skip(f"No API key provided for {custom_llm_provider}") litellm.set_verbose = True # First list existing evals to get an ID list_response = litellm.list_evals( limit=1, custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) assert isinstance(list_response, ListEvalsResponse) if list_response.data and len(list_response.data) > 0: eval_id = list_response.data[0].id print(f"Testing with eval ID: {eval_id}") # Get the eval response = litellm.get_eval( eval_id=eval_id, custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) assert response is not None assert isinstance(response, Eval) assert response.id == eval_id print(f"Retrieved eval: {response}") else: pytest.skip("No existing evals to test with") def test_update_eval(self): """ Test updating an evaluation. """ import time custom_llm_provider = self.get_custom_llm_provider() api_key = self.get_api_key() api_base = self.get_api_base() if not api_key: pytest.skip(f"No API key provided for {custom_llm_provider}") litellm.set_verbose = True # First list existing evals list_response = litellm.list_evals( limit=1, custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) assert isinstance(list_response, ListEvalsResponse) if list_response.data and len(list_response.data) > 0: eval_id = list_response.data[0].id updated_name = f"Updated Eval {int(time.time())}" # Update the eval response = litellm.update_eval( eval_id=eval_id, name=updated_name, custom_llm_provider=custom_llm_provider, api_key=api_key, api_base=api_base, ) assert response is not None assert isinstance(response, Eval) assert response.id == eval_id assert response.name == updated_name print(f"Updated eval: {response}") else: pytest.skip("No existing evals to test with") def test_delete_eval(self): """ Test deleting an evaluation. """ custom_llm_provider = self.get_custom_llm_provider() api_key = self.get_api_key() api_base = self.get_api_base() if not api_key: pytest.skip(f"No API key provided for {custom_llm_provider}") # Skip this test to avoid deleting production evals pytest.skip("Skipping delete test to preserve existing evals") class TestOpenAIEvalsAPI(BaseEvalsAPITest): """ Test OpenAI Evals API implementation. """ def get_custom_llm_provider(self) -> str: return "openai" def get_api_key(self) -> Optional[str]: return os.environ.get("OPENAI_API_KEY") def get_api_base(self) -> Optional[str]: return os.environ.get("OPENAI_API_BASE")