From c9b334fcd11e5200ec518fc5fc523fcadf53d9b7 Mon Sep 17 00:00:00 2001 From: TensorNull Date: Sat, 9 Aug 2025 10:42:45 +0800 Subject: [PATCH 1/4] feat: add CometAPI support with config, error handling and tests --- litellm/__init__.py | 8 +- litellm/constants.py | 2 + .../get_llm_provider_logic.py | 3 + litellm/llms/cometapi/chat/transformation.py | 207 ++++++++++++ litellm/llms/cometapi/common_utils.py | 6 + litellm/main.py | 39 +++ litellm/types/utils.py | 1 + litellm/utils.py | 2 + .../chat/test_cometapi_chat_transformation.py | 318 ++++++++++++++++++ 9 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 litellm/llms/cometapi/chat/transformation.py create mode 100644 litellm/llms/cometapi/common_utils.py create mode 100644 tests/test_litellm/llms/cometapi/chat/test_cometapi_chat_transformation.py diff --git a/litellm/__init__.py b/litellm/__init__.py index f7e1fb8f24..727ed9866f 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -232,6 +232,7 @@ nlp_cloud_key: Optional[str] = None novita_api_key: Optional[str] = None snowflake_key: Optional[str] = None nebius_key: Optional[str] = None +cometapi_key: Optional[str] = None common_cloud_provider_auth_params: dict = { "params": ["project", "region_name", "token"], "providers": ["vertex_ai", "bedrock", "watsonx", "azure", "vertex_ai_beta"], @@ -533,6 +534,7 @@ morph_models: List = [] lambda_ai_models: List = [] hyperbolic_models: List = [] recraft_models: List = [] +cometapi_models: List = [] oci_models: List = [] @@ -723,6 +725,8 @@ def add_known_models(): hyperbolic_models.append(key) elif value.get("litellm_provider") == "recraft": recraft_models.append(key) + elif value.get("litellm_provider") == "cometapi": + cometapi_models.append(key) elif value.get("litellm_provider") == "oci": oci_models.append(key) @@ -813,6 +817,7 @@ model_list = ( + morph_models + lambda_ai_models + recraft_models + + cometapi_models + oci_models ) @@ -887,6 +892,7 @@ models_by_provider: dict = { "lambda_ai": lambda_ai_models, "hyperbolic": hyperbolic_models, "recraft": recraft_models, + "cometapi": cometapi_models, "oci": oci_models, } @@ -1191,7 +1197,7 @@ from .llms.azure.azure import ( AzureOpenAIError, AzureOpenAIAssistantsAPIConfig, ) - +from .llms.cometapi.chat.transformation import CometAPIConfig from .llms.azure.chat.gpt_transformation import AzureOpenAIConfig from .llms.azure.completion.transformation import AzureOpenAITextConfig from .llms.hosted_vllm.chat.transformation import HostedVLLMChatConfig diff --git a/litellm/constants.py b/litellm/constants.py index c7404f10a7..c526ee1065 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -224,6 +224,7 @@ LITELLM_CHAT_PROVIDERS = [ "together_ai", "datarobot", "openrouter", + "cometapi", "vertex_ai", "vertex_ai_beta", "gemini", @@ -877,6 +878,7 @@ SENTRY_DENYLIST = [ "CLOUDFLARE_API_KEY", "BASETEN_KEY", "OPENROUTER_KEY", + "COMETAPI_KEY", "DATAROBOT_API_TOKEN", "FIREWORKS_API_KEY", "FIREWORKS_AI_API_KEY", diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 702196a7f0..7a2ec0b523 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -356,6 +356,9 @@ def get_llm_provider( # noqa: PLR0915 # bytez models elif model.startswith("bytez/"): custom_llm_provider = "bytez" + # cometapi models + elif model.startswith("cometapi/"): + custom_llm_provider = "cometapi" elif model.startswith("oci/"): custom_llm_provider = "oci" if not custom_llm_provider: diff --git a/litellm/llms/cometapi/chat/transformation.py b/litellm/llms/cometapi/chat/transformation.py new file mode 100644 index 0000000000..391f9626d3 --- /dev/null +++ b/litellm/llms/cometapi/chat/transformation.py @@ -0,0 +1,207 @@ +""" +Support for CometAPI's `/v1/chat/completions` endpoint. + +Based on OpenAI-compatible API interface implementation +Documentation: [CometAPI Documentation Link] +""" + +from typing import Any, AsyncIterator, Iterator, List, Optional, Tuple, Union + +import httpx + +from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolParam +from litellm.types.utils import ModelResponse, ModelResponseStream + +from ...openai.chat.gpt_transformation import OpenAIGPTConfig +from ..common_utils import CometAPIException + + +class CometAPIConfig(OpenAIGPTConfig): + """ + CometAPI configuration class, inherits from OpenAIGPTConfig + + Since CometAPI is OpenAI-compatible API, we inherit from OpenAIGPTConfig + and only need to override necessary methods to handle CometAPI-specific features + """ + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + """ + Map OpenAI format parameters to CometAPI format + """ + mapped_openai_params = super().map_openai_params( + non_default_params, optional_params, model, drop_params + ) + + # CometAPI-specific parameters (if any) + extra_body = {} + # TODO: Add CometAPI-specific parameter handling here + # Example: + # custom_param = non_default_params.pop("custom_param", None) + # if custom_param is not None: + # extra_body["custom_param"] = custom_param + + if extra_body: + mapped_openai_params["extra_body"] = extra_body + + return mapped_openai_params + + def remove_cache_control_flag_from_messages_and_tools( + self, + model: str, + messages: List[AllMessageValues], + tools: Optional[List["ChatCompletionToolParam"]] = None, + ) -> Tuple[List[AllMessageValues], Optional[List["ChatCompletionToolParam"]]]: + """ + Remove cache control flags from messages and tools if not supported + """ + # For CometAPI, use default behavior (remove cache control) + return super().remove_cache_control_flag_from_messages_and_tools( + model, messages, tools + ) + + def transform_request( + self, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + """ + Transform the overall request to be sent to the API. + + Returns: + dict: The transformed request. Sent as the body of the API call. + """ + extra_body = optional_params.pop("extra_body", {}) + response = super().transform_request( + model, messages, optional_params, litellm_params, headers + ) + response.update(extra_body) + return response + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for the CometAPI call. + + Returns: + str: The complete URL for the API call. + """ + # Default base + if api_base is None: + api_base = "https://api.cometapi.com/v1" + endpoint = "chat/completions" + + # Normalize + api_base = api_base.rstrip("/") + + # If endpoint already present, return as-is + if endpoint in api_base: + return api_base + + # Ensure we include /v1 prefix when missing + if api_base.endswith("/v1"): + return f"{api_base}/{endpoint}" + if api_base.endswith("/v1/"): + return f"{api_base}{endpoint}" + # If user provided https://api.cometapi.com, add /v1 + if api_base == "https://api.cometapi.com": + return f"{api_base}/v1/{endpoint}" + # Generic fallback: if '/v1' not in path, add it + if "/v1" not in api_base.split("//", 1)[-1]: + return f"{api_base}/v1/{endpoint}" + return f"{api_base}/{endpoint}" + + def get_error_class( + self, + error_message: str, + status_code: int, + headers: Union[dict, httpx.Headers] + ) -> BaseLLMException: + """ + Return CometAPI-specific error class + """ + return CometAPIException( + message=error_message, + status_code=status_code, + headers=headers, + ) + + def get_model_response_iterator( + self, + streaming_response: Union[Iterator[str], AsyncIterator[str], ModelResponse], + sync_stream: bool, + json_mode: Optional[bool] = False, + ) -> Any: + """ + Get model response iterator for streaming responses + """ + return CometAPIChatCompletionStreamingHandler( + streaming_response=streaming_response, + sync_stream=sync_stream, + json_mode=json_mode, + ) + + +class CometAPIChatCompletionStreamingHandler(BaseModelResponseIterator): + """ + Handler for CometAPI streaming chat completion responses + """ + + def chunk_parser(self, chunk: dict) -> ModelResponseStream: + """ + Parse individual chunks from streaming response + """ + try: + # Handle error in chunk + if "error" in chunk: + error_chunk = chunk["error"] + error_message = "CometAPI Error: {}".format( + error_chunk.get("message", "Unknown error") + ) + raise CometAPIException( + message=error_message, + status_code=error_chunk.get("code", 400), + headers={"Content-Type": "application/json"}, + ) + + # Process choices + new_choices = [] + for choice in chunk["choices"]: + # Handle reasoning content if present + if "delta" in choice and "reasoning" in choice["delta"]: + choice["delta"]["reasoning_content"] = choice["delta"].get("reasoning") + new_choices.append(choice) + + return ModelResponseStream( + id=chunk["id"], + object="chat.completion.chunk", + created=chunk["created"], + usage=chunk.get("usage"), + model=chunk["model"], + choices=new_choices, + ) + except KeyError as e: + raise CometAPIException( + message=f"KeyError: {e}, Got unexpected response from CometAPI: {chunk}", + status_code=400, + headers={"Content-Type": "application/json"}, + ) + except Exception as e: + raise e diff --git a/litellm/llms/cometapi/common_utils.py b/litellm/llms/cometapi/common_utils.py new file mode 100644 index 0000000000..2e5e3e5fab --- /dev/null +++ b/litellm/llms/cometapi/common_utils.py @@ -0,0 +1,6 @@ +from litellm.llms.base_llm.chat.transformation import BaseLLMException + + +class CometAPIException(BaseLLMException): + """CometAPI exception handling class""" + pass diff --git a/litellm/main.py b/litellm/main.py index 6bedf8f7ea..8f9eaf621a 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -1883,6 +1883,45 @@ def completion( # type: ignore # noqa: PLR0915 encoding=encoding, stream=stream, ) + elif custom_llm_provider == "cometapi": + api_key = ( + api_key + or litellm.cometapi_key + or get_secret_str("COMETAPI_KEY") + or litellm.api_key + ) + + api_base = ( + api_base + or litellm.api_base + or get_secret_str("COMETAPI_API_BASE") + or "https://api.cometapi.com/v1" + ) + + ## COMPLETION CALL + response = base_llm_http_handler.completion( + model=model, + messages=messages, + headers=headers, + model_response=model_response, + api_key=api_key, + api_base=api_base, + acompletion=acompletion, + logging_obj=logging, + optional_params=optional_params, + litellm_params=litellm_params, + timeout=timeout, + client=client, + custom_llm_provider=custom_llm_provider, + encoding=encoding, + stream=stream, + provider_config=provider_config, + ) + + ## LOGGING + logging.post_call( + input=messages, api_key=api_key, original_response=response + ) elif ( model in litellm.open_ai_chat_completion_models or custom_llm_provider == "custom_openai" diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 75c7d28460..438bfb175b 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2329,6 +2329,7 @@ class LlmProviders(str, Enum): PG_VECTOR = "pg_vector" HYPERBOLIC = "hyperbolic" RECRAFT = "recraft" + COMETAPI = "cometapi" OCI = "oci" AUTO_ROUTER = "auto_router" DOTPROMPT = "dotprompt" diff --git a/litellm/utils.py b/litellm/utils.py index 64d5f04a97..79ee94d5ad 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6843,6 +6843,8 @@ class ProviderConfigManager: return litellm.TogetherAIConfig() elif litellm.LlmProviders.OPENROUTER == provider: return litellm.OpenrouterConfig() + elif litellm.LlmProviders.COMETAPI == provider: + return litellm.CometAPIConfig() elif litellm.LlmProviders.DATAROBOT == provider: return litellm.DataRobotConfig() elif litellm.LlmProviders.GEMINI == provider: diff --git a/tests/test_litellm/llms/cometapi/chat/test_cometapi_chat_transformation.py b/tests/test_litellm/llms/cometapi/chat/test_cometapi_chat_transformation.py new file mode 100644 index 0000000000..c7723fa414 --- /dev/null +++ b/tests/test_litellm/llms/cometapi/chat/test_cometapi_chat_transformation.py @@ -0,0 +1,318 @@ +""" +Unit tests for CometAPI Chat Configuration + +Tests the CometAPIChatConfig class methods using mocks +""" + +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.cometapi.chat.transformation import ( + CometAPIChatCompletionStreamingHandler, + CometAPIConfig, +) +from litellm.llms.cometapi.common_utils import CometAPIException + + +class TestCometAPIChatCompletionStreamingHandler: + def test_chunk_parser_successful(self): + handler = CometAPIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Test input chunk + chunk = { + "id": "test_id", + "created": 1234567890, + "model": "gpt-3.5-turbo", + "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, + "choices": [ + {"delta": {"content": "test content", "reasoning": "test reasoning"}} + ], + } + + # Parse chunk + result = handler.chunk_parser(chunk) + + # Verify response + assert result.id == "test_id" + assert result.object == "chat.completion.chunk" + assert result.created == 1234567890 + assert result.model == "gpt-3.5-turbo" + assert result.usage.prompt_tokens == chunk["usage"]["prompt_tokens"] + assert result.usage.completion_tokens == chunk["usage"]["completion_tokens"] + assert result.usage.total_tokens == chunk["usage"]["total_tokens"] + assert len(result.choices) == 1 + assert result.choices[0]["delta"]["reasoning_content"] == "test reasoning" + + def test_chunk_parser_error_response(self): + handler = CometAPIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Test error chunk + error_chunk = { + "error": { + "message": "test error", + "code": 400, + } + } + + # Verify error handling + with pytest.raises(CometAPIException) as exc_info: + handler.chunk_parser(error_chunk) + + assert "CometAPI Error: test error" in str(exc_info.value) + assert exc_info.value.status_code == 400 + + def test_chunk_parser_key_error(self): + handler = CometAPIChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Test invalid chunk missing required fields + invalid_chunk = {"incomplete": "data"} + + # Verify KeyError handling + with pytest.raises(CometAPIException) as exc_info: + handler.chunk_parser(invalid_chunk) + + assert "KeyError" in str(exc_info.value) + assert exc_info.value.status_code == 400 + + +class TestCometAPIConfig: + def test_transform_request_basic(self): + """Test basic request transformation""" + config = CometAPIConfig() + + transformed_request = config.transform_request( + model="cometapi/gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Hello, world!"} + ], + optional_params={}, + litellm_params={}, + headers={}, + ) + + assert transformed_request["model"] == "cometapi/gpt-3.5-turbo" + assert transformed_request["messages"] == [ + {"role": "user", "content": "Hello, world!"} + ] + + def test_transform_request_with_extra_body(self): + """Test request transformation with extra_body parameters""" + config = CometAPIConfig() + + transformed_request = config.transform_request( + model="cometapi/gpt-4", + messages=[{"role": "user", "content": "Hello, world!"}], + optional_params={"extra_body": {"custom_param": "custom_value"}}, + litellm_params={}, + headers={}, + ) + + # Validate that extra_body parameters are merged into the request + assert transformed_request["custom_param"] == "custom_value" + assert transformed_request["messages"] == [ + {"role": "user", "content": "Hello, world!"} + ] + + def test_cache_control_flag_removal(self): + """Test cache control flag removal from messages""" + config = CometAPIConfig() + + transformed_request = config.transform_request( + model="cometapi/gpt-3.5-turbo", + messages=[ + { + "role": "user", + "content": "Hello, world!", + "cache_control": {"type": "ephemeral"}, + } + ], + optional_params={}, + litellm_params={}, + headers={}, + ) + + # CometAPI should remove cache_control flags by default + assert transformed_request["messages"][0].get("cache_control") is None + + def test_map_openai_params(self): + """Test OpenAI parameter mapping""" + config = CometAPIConfig() + + non_default_params = { + "temperature": 0.7, + "max_tokens": 100, + "top_p": 0.9, + } + + mapped_params = config.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model="cometapi/gpt-3.5-turbo", + drop_params=False, + ) + + assert mapped_params["temperature"] == 0.7 + assert mapped_params["max_tokens"] == 100 + assert mapped_params["top_p"] == 0.9 + + def test_get_error_class(self): + """Test error class creation""" + config = CometAPIConfig() + + error = config.get_error_class( + error_message="Test error", + status_code=400, + headers={"Content-Type": "application/json"} + ) + + assert isinstance(error, CometAPIException) + assert error.message == "Test error" + assert error.status_code == 400 + + +# Integration test example (requires real API key) +@pytest.mark.skip(reason="Skipping integration test") +def test_cometapi_integration(): + """ + Integration test - requires real API key + Run with: pytest -k test_cometapi_integration -s + """ + import os + from litellm import completion + + # Try to get API key from multiple environment variables + api_key = ( + os.getenv("COMETAPI_API_KEY") + or os.getenv("COMETAPI_KEY") + or os.getenv("COMET_API_KEY") + ) + + if not api_key: + pytest.skip("COMETAPI_API_KEY not set - skipping integration test") + + response = completion( + model="cometapi/gpt-3.5-turbo", + messages=[{"role": "user", "content": "Say hello in one word"}], + api_key=api_key, + max_tokens=10, + temperature=0.7 + ) + + # Verify response structure + assert response.choices[0].message.content + assert len(response.choices[0].message.content.strip()) > 0 + assert response.model + assert response.usage + assert response.usage.total_tokens > 0 + + +def test_cometapi_streaming_integration(): + """ + Integration test for streaming - requires real API key + Run with: pytest -k test_cometapi_streaming_integration -s + """ + import os + from litellm import completion + + # Try to get API key from multiple environment variables + api_key = ( + os.getenv("COMETAPI_API_KEY") + or os.getenv("COMETAPI_KEY") + or os.getenv("COMET_API_KEY") + ) + + if not api_key: + pytest.skip("COMETAPI_API_KEY not set - skipping streaming integration test") + + try: + print(f"🔍 Testing streaming with API key: {api_key[:6]}...{api_key[-4:]} (length: {len(api_key)})") + print(f"🔍 API base URL: {os.getenv('COMETAPI_API_BASE', 'default')}") + + # test streaming API call + response = completion( + model="cometapi/gpt-3.5-turbo", + messages=[{"role": "user", "content": "Count from 1 to 5"}], + api_key=api_key, + max_tokens=50, + stream=True + ) + + # collect streaming response + chunks = [] + content_parts = [] + + for chunk in response: + chunks.append(chunk) + if chunk.choices[0].delta.content: + content_parts.append(chunk.choices[0].delta.content) + + # Verify we received at least one chunk and content + assert len(chunks) > 0, "Should receive at least one chunk" + assert len(content_parts) > 0, "Should receive content in chunks" + + full_content = "".join(content_parts) + assert len(full_content.strip()) > 0, "Should have non-empty content" + + print(f"✅ Received {len(chunks)} chunks") + print(f"✅ Full content: {full_content}") + + except Exception as e: + print(f"❌ Streaming integration test error details:") + print(f" Error type: {type(e).__name__}") + print(f" Error message: {str(e)}") + if hasattr(e, 'status_code'): + print(f" Status code: {e.status_code}") + if hasattr(e, 'response'): + print(f" Response: {e.response}") + + # Re-raise with more context for pytest + pytest.fail(f"Streaming integration test failed: {type(e).__name__}: {str(e)}") +def test_cometapi_with_custom_base_url(): + """ + Test CometAPI with custom base URL + """ + import os + from litellm import completion + + api_key = ( + os.getenv("COMETAPI_API_KEY") + or os.getenv("COMETAPI_KEY") + or os.getenv("COMET_API_KEY") + ) + + custom_base_url = os.getenv("COMETAPI_API_BASE", "https://api.cometapi.com/v1") + + if not api_key: + pytest.skip("COMETAPI_API_KEY not set - skipping custom base URL test") + + try: + response = completion( + model="cometapi/gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello"}], + api_key=api_key, + api_base=custom_base_url, + max_tokens=5 + ) + + assert response.choices[0].message.content + print(f"✅ Custom base URL test passed: {response.choices[0].message.content}") + + except Exception as e: + pytest.fail(f"Custom base URL test failed: {str(e)}") + + +if __name__ == "__main__": + # Quick test runner + pytest.main([__file__, "-v"]) \ No newline at end of file From ea0f76812276908ec0a56babcecbc2542409ea45 Mon Sep 17 00:00:00 2001 From: TensorNull Date: Sat, 9 Aug 2025 10:59:23 +0800 Subject: [PATCH 2/4] fix: specify type for extra_body in CometAPIConfig --- litellm/llms/cometapi/chat/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/cometapi/chat/transformation.py b/litellm/llms/cometapi/chat/transformation.py index 391f9626d3..fedb8f61e5 100644 --- a/litellm/llms/cometapi/chat/transformation.py +++ b/litellm/llms/cometapi/chat/transformation.py @@ -41,7 +41,7 @@ class CometAPIConfig(OpenAIGPTConfig): ) # CometAPI-specific parameters (if any) - extra_body = {} + extra_body: dict[str, Any] = {} # TODO: Add CometAPI-specific parameter handling here # Example: # custom_param = non_default_params.pop("custom_param", None) From 8b602f9507d38a6524f9e50fa550425f43bd6e2a Mon Sep 17 00:00:00 2001 From: TensorNull Date: Tue, 12 Aug 2025 17:18:33 +0800 Subject: [PATCH 3/4] [Feat] - Add CometAPI documentation with authentication, usage examples, and error handling --- docs/my-website/docs/providers/cometapi.md | 147 +++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/my-website/docs/providers/cometapi.md diff --git a/docs/my-website/docs/providers/cometapi.md b/docs/my-website/docs/providers/cometapi.md new file mode 100644 index 0000000000..cb05e04812 --- /dev/null +++ b/docs/my-website/docs/providers/cometapi.md @@ -0,0 +1,147 @@ +# CometAPI +LiteLLM supports all AI models from [CometAPI](https://www.cometapi.com/). CometAPI provides access to 500+ AI models through a unified API interface, including cutting-edge models like GPT-5, Claude Opus 4.1, and various other state-of-the-art language models. + +## Authentication + +To use CometAPI models, you need to obtain an API key from [CometAPI Token Console](https://api.cometapi.com/console/token). CometAPI offers free tokens for new users - you can get your free API key instantly by registering. + +## Usage + +Set your CometAPI key as an environment variable and use the completion function: + +```python +import os +from litellm import completion + +# Set API key +os.environ["COMETAPI_KEY"] = "your_comet_api_key_here" + +# Define messages +messages = [{"content": "Hello, how are you?", "role": "user"}] + +# Method 1: Using environment variable (recommended) +response = completion( + model="cometapi/gpt-5", + messages=messages +) + +print(response.choices[0].message.content) +``` + +### Alternative Usage - Explicit API Key + +You can also pass the API key explicitly: + +```python +import os +from litellm import completion + +# Define messages +messages = [{"content": "Hello, how are you?", "role": "user"}] + +# Method 2: Explicitly passing API key +response = completion( + model="cometapi/gpt-4o", + messages=messages, + api_key="your_comet_api_key_here" +) + +print(response.choices[0].message.content) +``` + +## Usage - Streaming + +Just set `stream=True` when calling completion: + +```python +import os +from litellm import completion + +os.environ["COMETAPI_KEY"] = "your_comet_api_key_here" + +messages = [{"content": "Hello, how are you?", "role": "user"}] + +response = completion( + model="cometapi/gpt-5", + messages=messages, + stream=True +) + +for chunk in response: + print(chunk.choices[0].delta.content or "", end="") +``` + +## Usage - Async Streaming + +For async streaming, use `acompletion`: + +```python +from litellm import acompletion +import asyncio, os, traceback + +async def completion_call(): + try: + os.environ["COMETAPI_KEY"] = "your_comet_api_key_here" + + print("test acompletion + streaming") + response = await acompletion( + model="cometapi/chatgpt-4o-latest", + messages=[{"content": "Hello, how are you?", "role": "user"}], + stream=True + ) + print(f"response: {response}") + async for chunk in response: + print(chunk) + except: + print(f"error occurred: {traceback.format_exc()}") + pass + +# Run the async function +await completion_call() +``` + +## CometAPI Models + +CometAPI offers access to 500+ AI models through a unified API. Some popular models include: + +| Model Name | Function Call | +|------------|---------------| +| cometapi/gpt-5 | `completion('cometapi/gpt-5', messages)` | +| cometapi/gpt-5-mini | `completion('cometapi/gpt-5-mini', messages)` | +| cometapi/gpt-5-nano | `completion('cometapi/gpt-5-nano', messages)` | +| cometapi/claude-opus-4.1 | `completion('cometapi/claude-opus-4.1', messages)` | +| cometapi/o4-mini-deep-research | `completion('cometapi/o4-mini-deep-research', messages)` | +| cometapi/o3-deep-research | `completion('cometapi/o3-deep-research', messages)` | +| cometapi/gpt-oss-20b | `completion('cometapi/gpt-oss-20b', messages)` | +| cometapi/gpt-oss-120b | `completion('cometapi/gpt-oss-120b', messages)` | +| cometapi/chatgpt-4o-latest | `completion('cometapi/chatgpt-4o-latest', messages)` | + +For a complete list of available models, visit the [CometAPI Models page](https://www.cometapi.com/model/). + +## Environment Variables + +| Variable | Description | Required | +|----------|-------------|----------| +| `COMETAPI_KEY` | Your CometAPI API key | Yes | + +## Error Handling + +```python +import os +from litellm import completion + +try: + os.environ["COMETAPI_KEY"] = "your_comet_api_key_here" + + messages = [{"content": "Hello, how are you?", "role": "user"}] + + response = completion( + model="cometapi/gpt-5", + messages=messages + ) + + print(response.choices[0].message.content) + +except Exception as e: + print(f"Error: {e}") +``` From fa81c20df682a6ec1ac0f3aab5366ffd1e95b409 Mon Sep 17 00:00:00 2001 From: TensorNull Date: Tue, 12 Aug 2025 17:28:10 +0800 Subject: [PATCH 4/4] fix: Remove outdated models from the model list in the CometAPI document --- docs/my-website/docs/providers/cometapi.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/my-website/docs/providers/cometapi.md b/docs/my-website/docs/providers/cometapi.md index cb05e04812..1245bacfad 100644 --- a/docs/my-website/docs/providers/cometapi.md +++ b/docs/my-website/docs/providers/cometapi.md @@ -109,9 +109,6 @@ CometAPI offers access to 500+ AI models through a unified API. Some popular mod | cometapi/gpt-5 | `completion('cometapi/gpt-5', messages)` | | cometapi/gpt-5-mini | `completion('cometapi/gpt-5-mini', messages)` | | cometapi/gpt-5-nano | `completion('cometapi/gpt-5-nano', messages)` | -| cometapi/claude-opus-4.1 | `completion('cometapi/claude-opus-4.1', messages)` | -| cometapi/o4-mini-deep-research | `completion('cometapi/o4-mini-deep-research', messages)` | -| cometapi/o3-deep-research | `completion('cometapi/o3-deep-research', messages)` | | cometapi/gpt-oss-20b | `completion('cometapi/gpt-oss-20b', messages)` | | cometapi/gpt-oss-120b | `completion('cometapi/gpt-oss-120b', messages)` | | cometapi/chatgpt-4o-latest | `completion('cometapi/chatgpt-4o-latest', messages)` |