Add new model provider Novita AI (#7582)

* feat: add new model provider Novita AI

* feat: use deepseek r1 model for examples in Novita AI docs

* fix: fix tests

* fix: fix tests for novita

* fix: fix novita transformation
This commit is contained in:
Jason 2025-03-26 00:03:46 +08:00 committed by GitHub
parent b33c56cf10
commit 7a92a03565
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 581 additions and 86 deletions

View file

@ -20,8 +20,10 @@ REPLICATE_API_TOKEN = ""
ANTHROPIC_API_KEY = "" ANTHROPIC_API_KEY = ""
# Infisical # Infisical
INFISICAL_TOKEN = "" INFISICAL_TOKEN = ""
# Novita AI
NOVITA_API_KEY = ""
# Development Configs # Development Configs
LITELLM_MASTER_KEY = "sk-1234" LITELLM_MASTER_KEY = "sk-1234"
DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm" DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
STORE_MODEL_IN_DB = "True" STORE_MODEL_IN_DB = "True"

View file

@ -335,6 +335,7 @@ curl 'http://0.0.0.0:4000/key/generate' \
| [xinference [Xorbits Inference]](https://docs.litellm.ai/docs/providers/xinference) | | | | | ✅ | | | [xinference [Xorbits Inference]](https://docs.litellm.ai/docs/providers/xinference) | | | | | ✅ | |
| [FriendliAI](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | ✅ | | | | [FriendliAI](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | ✅ | | |
| [Galadriel](https://docs.litellm.ai/docs/providers/galadriel) | ✅ | ✅ | ✅ | ✅ | | | | [Galadriel](https://docs.litellm.ai/docs/providers/galadriel) | ✅ | ✅ | ✅ | ✅ | | |
| [Novita AI](https://novita.ai/models/llm?utm_source=github_litellm&utm_medium=github_readme&utm_campaign=github_link) | ✅ | ✅ | ✅ | ✅ | | |
[**Read the Docs**](https://docs.litellm.ai/docs/) [**Read the Docs**](https://docs.litellm.ai/docs/)

View file

@ -0,0 +1,97 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "iFEmsVJI_2BR"
},
"source": [
"# LiteLLM NovitaAI Cookbook"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cBlUhCEP_xj4"
},
"outputs": [],
"source": [
"!pip install litellm"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "p-MQqWOT_1a7"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.environ['NOVITA_API_KEY'] = \"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Ze8JqMqWAARO"
},
"outputs": [],
"source": [
"from litellm import completion\n",
"response = completion(\n",
" model=\"novita/deepseek/deepseek-r1\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-LnhELrnAM_J"
},
"outputs": [],
"source": [
"response = completion(\n",
" model=\"novita/deepseek/deepseek-r1\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dJBOUYdwCEn1"
},
"outputs": [],
"source": [
"response = completion(\n",
" model=\"mistralai/mistral-7b-instruct\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View file

@ -1,27 +1,13 @@
{ {
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [ "cells": [
{ {
"cell_type": "markdown", "cell_type": "markdown",
"source": [
"# LiteLLM OpenRouter Cookbook"
],
"metadata": { "metadata": {
"id": "iFEmsVJI_2BR" "id": "iFEmsVJI_2BR"
} },
"source": [
"# LiteLLM OpenRouter Cookbook"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@ -36,27 +22,20 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14,
"metadata": {
"id": "p-MQqWOT_1a7"
},
"outputs": [],
"source": [ "source": [
"import os\n", "import os\n",
"\n", "\n",
"os.environ['OPENROUTER_API_KEY'] = \"\"" "os.environ['OPENROUTER_API_KEY'] = \"\""
], ]
"metadata": {
"id": "p-MQqWOT_1a7"
},
"execution_count": 14,
"outputs": []
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": 11,
"from litellm import completion\n",
"response = completion(\n",
" model=\"openrouter/google/palm-2-chat-bison\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
],
"metadata": { "metadata": {
"colab": { "colab": {
"base_uri": "https://localhost:8080/" "base_uri": "https://localhost:8080/"
@ -64,10 +43,8 @@
"id": "Ze8JqMqWAARO", "id": "Ze8JqMqWAARO",
"outputId": "64f3e836-69fa-4f8e-fb35-088a913bbe98" "outputId": "64f3e836-69fa-4f8e-fb35-088a913bbe98"
}, },
"execution_count": 11,
"outputs": [ "outputs": [
{ {
"output_type": "execute_result",
"data": { "data": {
"text/plain": [ "text/plain": [
"<OpenAIObject id=gen-W8FTMSIEorCp3vG5iYIgNMR4IeBv at 0x7c3dcef1f060> JSON: {\n", "<OpenAIObject id=gen-W8FTMSIEorCp3vG5iYIgNMR4IeBv at 0x7c3dcef1f060> JSON: {\n",
@ -85,20 +62,23 @@
"}" "}"
] ]
}, },
"execution_count": 11,
"metadata": {}, "metadata": {},
"execution_count": 11 "output_type": "execute_result"
} }
],
"source": [
"from litellm import completion\n",
"response = completion(\n",
" model=\"openrouter/google/palm-2-chat-bison\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": 12,
"response = completion(\n",
" model=\"openrouter/anthropic/claude-2\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
],
"metadata": { "metadata": {
"colab": { "colab": {
"base_uri": "https://localhost:8080/" "base_uri": "https://localhost:8080/"
@ -106,10 +86,8 @@
"id": "-LnhELrnAM_J", "id": "-LnhELrnAM_J",
"outputId": "d51c7ab7-d761-4bd1-f849-1534d9df4cd0" "outputId": "d51c7ab7-d761-4bd1-f849-1534d9df4cd0"
}, },
"execution_count": 12,
"outputs": [ "outputs": [
{ {
"output_type": "execute_result",
"data": { "data": {
"text/plain": [ "text/plain": [
"<OpenAIObject id=gen-IiuV7ZNimDufVeutBHrl8ajPuzEh at 0x7c3dcea67560> JSON: {\n", "<OpenAIObject id=gen-IiuV7ZNimDufVeutBHrl8ajPuzEh at 0x7c3dcea67560> JSON: {\n",
@ -128,20 +106,22 @@
"}" "}"
] ]
}, },
"execution_count": 12,
"metadata": {}, "metadata": {},
"execution_count": 12 "output_type": "execute_result"
} }
],
"source": [
"response = completion(\n",
" model=\"openrouter/anthropic/claude-2\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": 13,
"response = completion(\n",
" model=\"openrouter/meta-llama/llama-2-70b-chat\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
],
"metadata": { "metadata": {
"colab": { "colab": {
"base_uri": "https://localhost:8080/" "base_uri": "https://localhost:8080/"
@ -149,10 +129,8 @@
"id": "dJBOUYdwCEn1", "id": "dJBOUYdwCEn1",
"outputId": "ffa18679-ec15-4dad-fe2b-68665cdf36b0" "outputId": "ffa18679-ec15-4dad-fe2b-68665cdf36b0"
}, },
"execution_count": 13,
"outputs": [ "outputs": [
{ {
"output_type": "execute_result",
"data": { "data": {
"text/plain": [ "text/plain": [
"<OpenAIObject id=gen-PyMd3yyJ0aQsCgIY9R8XGZoAtPbl at 0x7c3dceefcae0> JSON: {\n", "<OpenAIObject id=gen-PyMd3yyJ0aQsCgIY9R8XGZoAtPbl at 0x7c3dceefcae0> JSON: {\n",
@ -170,10 +148,32 @@
"}" "}"
] ]
}, },
"execution_count": 13,
"metadata": {}, "metadata": {},
"execution_count": 13 "output_type": "execute_result"
} }
],
"source": [
"response = completion(\n",
" model=\"openrouter/meta-llama/llama-2-70b-chat\",\n",
" messages=[{\"role\": \"user\", \"content\": \"write code for saying hi\"}]\n",
")\n",
"response"
] ]
} }
] ],
} "metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View file

@ -62,6 +62,7 @@ Use `litellm.get_supported_openai_params()` for an updated list of params for ea
|Databricks| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | | |Databricks| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | |
|ClarifAI| ✅ | ✅ | ✅ | |✅ | ✅ | | | | | | | | | | | |ClarifAI| ✅ | ✅ | ✅ | |✅ | ✅ | | | | | | | | | | |
|Github| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |✅ (model dependent)|✅ (model dependent)| | | |Github| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |✅ (model dependent)|✅ (model dependent)| | |
|Novita AI| ✅ | ✅ | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | | | | | |
:::note :::note
By default, LiteLLM raises an exception if the openai param being passed in isn't supported. By default, LiteLLM raises an exception if the openai param being passed in isn't supported.

View file

@ -208,6 +208,22 @@ response = completion(
) )
``` ```
</TabItem>
<TabItem value="novita" label="Novita AI">
```python
from litellm import completion
import os
## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita-api-key"
response = completion(
model="novita/deepseek/deepseek-r1",
messages=[{ "content": "Hello, how are you?","role": "user"}]
)
```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -411,6 +427,23 @@ response = completion(
) )
``` ```
</TabItem>
<TabItem value="novita" label="Novita AI">
```python
from litellm import completion
import os
## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita_api_key"
response = completion(
model="novita/deepseek/deepseek-r1",
messages = [{ "content": "Hello, how are you?","role": "user"}],
stream=True,
)
```
</TabItem> </TabItem>
</Tabs> </Tabs>

View file

@ -0,0 +1,39 @@
# Novita AI
LiteLLM supports all models from [Novita AI](https://novita.ai/models/llm?utm_source=github_litellm&utm_medium=github_readme&utm_campaign=github_link)
## Usage
```python
import os
from litellm import completion
os.environ["NOVITA_API_KEY"] = ""
response = completion(
model="meta-llama/llama-3.3-70b-instruct",
messages=messages,
)
```
## Novita AI Completion Models
🚨 LiteLLM supports ALL Novita AI models, send `model=novita/<your-novita-model>` to send it to Novita AI. See all Novita AI models [here](https://novita.ai/models/llm?utm_source=github_litellm&utm_medium=github_readme&utm_campaign=github_link)
| Model Name | Function Call |
|---------------------------|-----------------------------------------------------|
| novita/deepseek/deepseek-r1 | `completion('novita/deepseek/deepseek-r1', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/deepseek/deepseek_v3 | `completion('novita/deepseek/deepseek_v3', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.3-70b-instruct | `completion('novita/meta-llama/llama-3.3-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-8b-instruct | `completion('novita/meta-llama/llama-3.1-8b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-8b-instruct-max | `completion('novita/meta-llama/llama-3.1-8b-instruct-max', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-70b-instruct | `completion('novita/meta-llama/llama-3.1-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3-8b-instruct | `completion('novita/meta-llama/llama-3-8b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3-70b-instruct | `completion('novita/meta-llama/llama-3-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-1b-instruct | `completion('novita/meta-llama/llama-3.2-1b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-11b-vision-instruct | `completion('novita/meta-llama/llama-3.2-11b-vision-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-3b-instruct | `completion('novita/meta-llama/llama-3.2-3b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/gryphe/mythomax-l2-13b | `completion('novita/gryphe/mythomax-l2-13b', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/google/gemma-2-9b-it | `completion('novita/google/gemma-2-9b-it', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/mistralai/mistral-nemo | `completion('novita/mistralai/mistral-nemo', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/mistralai/mistral-7b-instruct | `completion('novita/mistralai/mistral-7b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/qwen/qwen-2.5-72b-instruct | `completion('novita/qwen/qwen-2.5-72b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/qwen/qwen-2-vl-72b-instruct | `completion('novita/qwen/qwen-2-vl-72b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |

View file

@ -225,6 +225,7 @@ const sidebars = {
"providers/nlp_cloud", "providers/nlp_cloud",
"providers/replicate", "providers/replicate",
"providers/togetherai", "providers/togetherai",
"providers/novita",
"providers/voyage", "providers/voyage",
"providers/jina_ai", "providers/jina_ai",
"providers/aleph_alpha", "providers/aleph_alpha",

View file

@ -1,6 +1,6 @@
# Completion Function - completion() # Completion Function - completion()
The Input params are **exactly the same** as the The Input params are **exactly the same** as the
<a href="https://platform.openai.com/docs/api-reference/chat/create" target="_blank" rel="noopener noreferrer">OpenAI Create chat completion</a>, and let you call **Azure OpenAI, Anthropic, Cohere, Replicate, OpenRouter** models in the same format. <a href="https://platform.openai.com/docs/api-reference/chat/create" target="_blank" rel="noopener noreferrer">OpenAI Create chat completion</a>, and let you call **Azure OpenAI, Anthropic, Cohere, Replicate, OpenRouter, Novita AI** models in the same format.
In addition, liteLLM allows you to pass in the following **Optional** liteLLM args: In addition, liteLLM allows you to pass in the following **Optional** liteLLM args:
`force_timeout`, `azure`, `logger_fn`, `verbose` `force_timeout`, `azure`, `logger_fn`, `verbose`

View file

@ -70,4 +70,28 @@ All the text models from [OpenRouter](https://openrouter.ai/docs) are supported
| google/palm-2-chat-bison | `completion('google/palm-2-chat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` | | google/palm-2-chat-bison | `completion('google/palm-2-chat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
| google/palm-2-codechat-bison | `completion('google/palm-2-codechat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` | | google/palm-2-codechat-bison | `completion('google/palm-2-codechat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
| meta-llama/llama-2-13b-chat | `completion('meta-llama/llama-2-13b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` | | meta-llama/llama-2-13b-chat | `completion('meta-llama/llama-2-13b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
| meta-llama/llama-2-70b-chat | `completion('meta-llama/llama-2-70b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` | | meta-llama/llama-2-70b-chat | `completion('meta-llama/llama-2-70b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
## Novita AI Completion Models
🚨 LiteLLM supports ALL Novita AI models, send `model=novita/<your-novita-model>` to send it to Novita AI. See all Novita AI models [here](https://novita.ai/models/llm?utm_source=github_litellm&utm_medium=github_readme&utm_campaign=github_link)
| Model Name | Function Call | Required OS Variables |
|------------------|--------------------------------------------|--------------------------------------|
| novita/deepseek/deepseek-r1 | `completion('novita/deepseek/deepseek-r1', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/deepseek/deepseek_v3 | `completion('novita/deepseek/deepseek_v3', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.3-70b-instruct | `completion('novita/meta-llama/llama-3.3-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-8b-instruct | `completion('novita/meta-llama/llama-3.1-8b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-8b-instruct-max | `completion('novita/meta-llama/llama-3.1-8b-instruct-max', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.1-70b-instruct | `completion('novita/meta-llama/llama-3.1-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3-8b-instruct | `completion('novita/meta-llama/llama-3-8b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3-70b-instruct | `completion('novita/meta-llama/llama-3-70b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-1b-instruct | `completion('novita/meta-llama/llama-3.2-1b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-11b-vision-instruct | `completion('novita/meta-llama/llama-3.2-11b-vision-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/meta-llama/llama-3.2-3b-instruct | `completion('novita/meta-llama/llama-3.2-3b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/gryphe/mythomax-l2-13b | `completion('novita/gryphe/mythomax-l2-13b', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/google/gemma-2-9b-it | `completion('novita/google/gemma-2-9b-it', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/mistralai/mistral-nemo | `completion('novita/mistralai/mistral-nemo', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/mistralai/mistral-7b-instruct | `completion('novita/mistralai/mistral-7b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/qwen/qwen-2.5-72b-instruct | `completion('novita/qwen/qwen-2.5-72b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |
| novita/qwen/qwen-2-vl-72b-instruct | `completion('novita/qwen/qwen-2-vl-72b-instruct', messages)` | `os.environ['NOVITA_API_KEY']` |

View file

@ -194,6 +194,22 @@ response = completion(
) )
``` ```
</TabItem>
<TabItem value="novita" label="Novita AI">
```python
from litellm import completion
import os
## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita-api-key"
response = completion(
model="novita/deepseek/deepseek-r1",
messages=[{ "content": "Hello, how are you?","role": "user"}]
)
```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -347,7 +363,23 @@ response = completion(
``` ```
</TabItem> </TabItem>
<TabItem value="novita" label="Novita AI">
```python
from litellm import completion
import os
## set ENV variables. Visit https://novita.ai/settings/key-management to get your API key
os.environ["NOVITA_API_KEY"] = "novita_api_key"
response = completion(
model="novita/deepseek/deepseek-r1",
messages = [{ "content": "Hello, how are you?","role": "user"}],
stream=True,
)
```
</TabItem>
</Tabs> </Tabs>
### Exception handling ### Exception handling

View file

@ -182,6 +182,7 @@ cloudflare_api_key: Optional[str] = None
baseten_key: Optional[str] = None baseten_key: Optional[str] = None
aleph_alpha_key: Optional[str] = None aleph_alpha_key: Optional[str] = None
nlp_cloud_key: Optional[str] = None nlp_cloud_key: Optional[str] = None
novita_api_key: Optional[str] = None
snowflake_key: Optional[str] = None snowflake_key: Optional[str] = None
common_cloud_provider_auth_params: dict = { common_cloud_provider_auth_params: dict = {
"params": ["project", "region_name", "token"], "params": ["project", "region_name", "token"],
@ -416,10 +417,10 @@ anyscale_models: List = []
cerebras_models: List = [] cerebras_models: List = []
galadriel_models: List = [] galadriel_models: List = []
sambanova_models: List = [] sambanova_models: List = []
novita_models: List = []
assemblyai_models: List = [] assemblyai_models: List = []
snowflake_models: List = [] snowflake_models: List = []
def is_bedrock_pricing_only_model(key: str) -> bool: def is_bedrock_pricing_only_model(key: str) -> bool:
""" """
Excludes keys with the pattern 'bedrock/<region>/<model>'. These are in the model_prices_and_context_window.json file for pricing purposes only. Excludes keys with the pattern 'bedrock/<region>/<model>'. These are in the model_prices_and_context_window.json file for pricing purposes only.
@ -567,6 +568,8 @@ def add_known_models():
galadriel_models.append(key) galadriel_models.append(key)
elif value.get("litellm_provider") == "sambanova_models": elif value.get("litellm_provider") == "sambanova_models":
sambanova_models.append(key) sambanova_models.append(key)
elif value.get("litellm_provider") == "novita":
novita_models.append(key)
elif value.get("litellm_provider") == "assemblyai": elif value.get("litellm_provider") == "assemblyai":
assemblyai_models.append(key) assemblyai_models.append(key)
elif value.get("litellm_provider") == "jina_ai": elif value.get("litellm_provider") == "jina_ai":
@ -645,6 +648,7 @@ model_list = (
+ galadriel_models + galadriel_models
+ sambanova_models + sambanova_models
+ azure_text_models + azure_text_models
+ novita_models
+ assemblyai_models + assemblyai_models
+ jina_ai_models + jina_ai_models
+ snowflake_models + snowflake_models
@ -701,6 +705,7 @@ models_by_provider: dict = {
"cerebras": cerebras_models, "cerebras": cerebras_models,
"galadriel": galadriel_models, "galadriel": galadriel_models,
"sambanova": sambanova_models, "sambanova": sambanova_models,
"novita": novita_models,
"assemblyai": assemblyai_models, "assemblyai": assemblyai_models,
"jina_ai": jina_ai_models, "jina_ai": jina_ai_models,
"snowflake": snowflake_models, "snowflake": snowflake_models,
@ -835,6 +840,7 @@ from .llms.anthropic.experimental_pass_through.messages.transformation import (
from .llms.together_ai.chat import TogetherAIConfig from .llms.together_ai.chat import TogetherAIConfig
from .llms.together_ai.completion.transformation import TogetherAITextCompletionConfig from .llms.together_ai.completion.transformation import TogetherAITextCompletionConfig
from .llms.cloudflare.chat.transformation import CloudflareChatConfig from .llms.cloudflare.chat.transformation import CloudflareChatConfig
from .llms.novita.chat.transformation import NovitaConfig
from .llms.deprecated_providers.palm import ( from .llms.deprecated_providers.palm import (
PalmConfig, PalmConfig,
) # here to prevent breaking changes ) # here to prevent breaking changes

View file

@ -82,6 +82,7 @@ LITELLM_CHAT_PROVIDERS = [
"hosted_vllm", "hosted_vllm",
"lm_studio", "lm_studio",
"galadriel", "galadriel",
"novita",
] ]
@ -171,6 +172,7 @@ openai_compatible_providers: List = [
"hosted_vllm", "hosted_vllm",
"lm_studio", "lm_studio",
"galadriel", "galadriel",
"novita",
] ]
openai_text_completion_compatible_providers: List = ( openai_text_completion_compatible_providers: List = (
[ # providers that support `/v1/completions` [ # providers that support `/v1/completions`

View file

@ -569,6 +569,13 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915
or "https://api.galadriel.com/v1" or "https://api.galadriel.com/v1"
) # type: ignore ) # type: ignore
dynamic_api_key = api_key or get_secret_str("GALADRIEL_API_KEY") dynamic_api_key = api_key or get_secret_str("GALADRIEL_API_KEY")
elif custom_llm_provider == "novita":
api_base = (
api_base
or get_secret("NOVITA_API_BASE")
or "https://api.novita.ai/v3/openai"
) # type: ignore
dynamic_api_key = api_key or get_secret_str("NOVITA_API_KEY")
elif custom_llm_provider == "snowflake": elif custom_llm_provider == "snowflake":
api_base = ( api_base = (
api_base api_base

View file

@ -119,6 +119,8 @@ def get_supported_openai_params( # noqa: PLR0915
return litellm.GoogleAIStudioGeminiConfig().get_supported_openai_params( return litellm.GoogleAIStudioGeminiConfig().get_supported_openai_params(
model=model model=model
) )
elif custom_llm_provider == "novita":
return litellm.NovitaConfig().get_supported_openai_params(model=model)
elif custom_llm_provider == "vertex_ai" or custom_llm_provider == "vertex_ai_beta": elif custom_llm_provider == "vertex_ai" or custom_llm_provider == "vertex_ai_beta":
if request_type == "chat_completion": if request_type == "chat_completion":
if model.startswith("mistral"): if model.startswith("mistral"):

View file

@ -0,0 +1,35 @@
"""
Support for OpenAI's `/v1/chat/completions` endpoint.
Calls done in OpenAI/openai.py as Novita AI is openai-compatible.
Docs: https://novita.ai/docs/guides/llm-api
"""
from typing import List, Optional
from ....types.llms.openai import AllMessageValues
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
class NovitaConfig(OpenAIGPTConfig):
def validate_environment(
self,
headers: dict,
model: str,
messages: List[AllMessageValues],
optional_params: dict,
api_key: Optional[str] = None,
api_base: Optional[str] = None,
) -> dict:
if api_key is None:
raise ValueError(
"Missing Novita AI API Key - A call is being made to novita but no key is set either in the environment variables or via params"
)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-Novita-Source": "litellm",
}
return headers

View file

@ -3252,7 +3252,6 @@ async def aembedding(*args, **kwargs) -> EmbeddingResponse:
response = init_response response = init_response
elif asyncio.iscoroutine(init_response): elif asyncio.iscoroutine(init_response):
response = await init_response # type: ignore response = await init_response # type: ignore
if ( if (
response is not None response is not None
and isinstance(response, EmbeddingResponse) and isinstance(response, EmbeddingResponse)

View file

@ -2015,6 +2015,7 @@ class LlmProviders(str, Enum):
GALADRIEL = "galadriel" GALADRIEL = "galadriel"
INFINITY = "infinity" INFINITY = "infinity"
DEEPGRAM = "deepgram" DEEPGRAM = "deepgram"
NOVITA = "novita"
AIOHTTP_OPENAI = "aiohttp_openai" AIOHTTP_OPENAI = "aiohttp_openai"
LANGFUSE = "langfuse" LANGFUSE = "langfuse"
HUMANLOOP = "humanloop" HUMANLOOP = "humanloop"

View file

@ -2317,6 +2317,9 @@ def register_model(model_cost: Union[str, dict]): # noqa: PLR0915
elif value.get("litellm_provider") == "bedrock": elif value.get("litellm_provider") == "bedrock":
if key not in litellm.bedrock_models: if key not in litellm.bedrock_models:
litellm.bedrock_models.append(key) litellm.bedrock_models.append(key)
elif value.get("litellm_provider") == "novita":
if key not in litellm.novita_models:
litellm.novita_models.append(key)
return model_cost return model_cost
@ -5073,6 +5076,11 @@ def validate_environment( # noqa: PLR0915
else: else:
missing_keys.append("CLOUDFLARE_API_KEY") missing_keys.append("CLOUDFLARE_API_KEY")
missing_keys.append("CLOUDFLARE_API_BASE") missing_keys.append("CLOUDFLARE_API_BASE")
elif custom_llm_provider == "novita":
if "NOVITA_API_KEY" in os.environ:
keys_in_environment = True
else:
missing_keys.append("NOVITA_API_KEY")
else: else:
## openai - chatcompletion + text completion ## openai - chatcompletion + text completion
if ( if (
@ -5155,6 +5163,11 @@ def validate_environment( # noqa: PLR0915
keys_in_environment = True keys_in_environment = True
else: else:
missing_keys.append("NLP_CLOUD_API_KEY") missing_keys.append("NLP_CLOUD_API_KEY")
elif model in litellm.novita_models:
if "NOVITA_API_KEY" in os.environ:
keys_in_environment = True
else:
missing_keys.append("NOVITA_API_KEY")
if api_key is not None: if api_key is not None:
new_missing_keys = [] new_missing_keys = []
@ -6281,6 +6294,8 @@ class ProviderConfigManager:
return litellm.TritonConfig() return litellm.TritonConfig()
elif litellm.LlmProviders.PETALS == provider: elif litellm.LlmProviders.PETALS == provider:
return litellm.PetalsConfig() return litellm.PetalsConfig()
elif litellm.LlmProviders.NOVITA == provider:
return litellm.NovitaConfig()
elif litellm.LlmProviders.BEDROCK == provider: elif litellm.LlmProviders.BEDROCK == provider:
bedrock_route = BedrockModelInfo.get_bedrock_route(model) bedrock_route = BedrockModelInfo.get_bedrock_route(model)
bedrock_invoke_provider = litellm.BedrockLLM.get_bedrock_invoke_provider( bedrock_invoke_provider = litellm.BedrockLLM.get_bedrock_invoke_provider(

108
poetry.lock generated
View file

@ -123,7 +123,7 @@ multidict = ">=4.5,<7.0"
yarl = ">=1.12.0,<2.0" yarl = ">=1.12.0,<2.0"
[package.extras] [package.extras]
speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
[[package]] [[package]]
name = "aiosignal" name = "aiosignal"
@ -175,7 +175,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
trio = ["trio (>=0.26.1)"] trio = ["trio (>=0.26.1)"]
[[package]] [[package]]
@ -203,7 +203,7 @@ mongodb = ["pymongo (>=3.0)"]
redis = ["redis (>=3.0)"] redis = ["redis (>=3.0)"]
rethinkdb = ["rethinkdb (>=2.4.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"]
sqlalchemy = ["sqlalchemy (>=1.4)"] sqlalchemy = ["sqlalchemy (>=1.4)"]
test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "anyio (>=4.5.2)", "gevent ; python_version < \"3.14\"", "pytest", "pytz", "twisted ; python_version < \"3.14\""]
tornado = ["tornado (>=4.3)"] tornado = ["tornado (>=4.3)"]
twisted = ["twisted"] twisted = ["twisted"]
zookeeper = ["kazoo"] zookeeper = ["kazoo"]
@ -234,12 +234,12 @@ files = [
] ]
[package.extras] [package.extras]
benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
[[package]] [[package]]
name = "azure-core" name = "azure-core"
@ -386,7 +386,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
@ -865,7 +865,7 @@ files = [
[package.extras] [package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
typing = ["typing-extensions (>=4.12.2)"] typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
[[package]] [[package]]
name = "flake8" name = "flake8"
@ -1043,12 +1043,12 @@ files = [
google-auth = ">=2.14.1,<3.0.0" google-auth = ">=2.14.1,<3.0.0"
googleapis-common-protos = ">=1.56.2,<2.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0"
grpcio = [ grpcio = [
{version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
] ]
grpcio-status = [ grpcio-status = [
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
] ]
proto-plus = [ proto-plus = [
{version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""},
@ -1059,7 +1059,7 @@ requests = ">=2.18.0,<3.0.0"
[package.extras] [package.extras]
async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"]
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""]
grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
@ -1352,6 +1352,66 @@ files = [
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
] ]
[[package]]
name = "h2"
version = "4.1.0"
description = "HTTP/2 State-Machine based protocol implementation"
optional = false
python-versions = ">=3.6.1"
groups = ["main"]
markers = "python_version < \"3.10\""
files = [
{file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
{file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
]
[package.dependencies]
hpack = ">=4.0,<5"
hyperframe = ">=6.0,<7"
[[package]]
name = "h2"
version = "4.2.0"
description = "Pure-Python HTTP/2 protocol implementation"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.10\""
files = [
{file = "h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0"},
{file = "h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f"},
]
[package.dependencies]
hpack = ">=4.1,<5"
hyperframe = ">=6.1,<7"
[[package]]
name = "hpack"
version = "4.0.0"
description = "Pure-Python HPACK header compression"
optional = false
python-versions = ">=3.6.1"
groups = ["main"]
markers = "python_version < \"3.10\""
files = [
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
]
[[package]]
name = "hpack"
version = "4.1.0"
description = "Pure-Python HPACK header encoding"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version >= \"3.10\""
files = [
{file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"},
{file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"},
]
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "1.0.7" version = "1.0.7"
@ -1393,7 +1453,7 @@ httpcore = "==1.*"
idna = "*" idna = "*"
[package.extras] [package.extras]
brotli = ["brotli", "brotlicffi"] brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (==1.*)"]
@ -1481,12 +1541,12 @@ files = [
zipp = ">=3.20" zipp = ">=3.20"
[package.extras] [package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
cover = ["pytest-cov"] cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"] enabler = ["pytest-enabler (>=2.2)"]
perf = ["ipython"] perf = ["ipython"]
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
type = ["pytest-mypy"] type = ["pytest-mypy"]
[[package]] [[package]]
@ -1506,7 +1566,7 @@ files = [
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras] [package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
cover = ["pytest-cov"] cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"] enabler = ["pytest-enabler (>=2.2)"]
@ -1835,7 +1895,7 @@ PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]}
requests = ">=2.0.0,<3" requests = ">=2.0.0,<3"
[package.extras] [package.extras]
broker = ["pymsalruntime (>=0.14,<0.18)", "pymsalruntime (>=0.17,<0.18)"] broker = ["pymsalruntime (>=0.14,<0.18) ; python_version >= \"3.6\" and platform_system == \"Windows\"", "pymsalruntime (>=0.17,<0.18) ; python_version >= \"3.8\" and platform_system == \"Darwin\""]
[[package]] [[package]]
name = "msal-extensions" name = "msal-extensions"
@ -2614,7 +2674,7 @@ typing-extensions = ">=4.12.2"
[package.extras] [package.extras]
email = ["email-validator (>=2.0.0)"] email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"] timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]] [[package]]
name = "pydantic-core" name = "pydantic-core"
@ -3373,7 +3433,7 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
optional = true optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"] groups = ["main"]
markers = "extra == \"extra-proxy\" or extra == \"proxy\"" markers = "extra == \"extra-proxy\" or extra == \"proxy\""
@ -3688,7 +3748,7 @@ files = [
] ]
[package.extras] [package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"] h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"] zstd = ["zstandard (>=0.18.0)"]
@ -3712,7 +3772,7 @@ h11 = ">=0.8"
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]] [[package]]
name = "uvloop" name = "uvloop"
@ -3990,11 +4050,11 @@ files = [
] ]
[package.extras] [package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
cover = ["pytest-cov"] cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"] enabler = ["pytest-enabler (>=2.2)"]
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
type = ["pytest-mypy"] type = ["pytest-mypy"]
[extras] [extras]

View file

@ -0,0 +1,67 @@
"""
Unit tests for Novita AI configuration.
These tests validate the NovitaConfig class which extends OpenAIGPTConfig.
Novita AI is an OpenAI-compatible provider with a few customizations.
"""
import os
import sys
from typing import Dict, List, Optional
from unittest.mock import patch
import pytest
sys.path.insert(
0, os.path.abspath("../../../../..")
) # Adds the parent directory to the system path
from litellm.llms.novita.chat.transformation import NovitaConfig
class TestNovitaConfig:
"""Test class for NovitaConfig functionality"""
def test_validate_environment(self):
"""Test that validate_environment adds correct headers"""
config = NovitaConfig()
headers = {}
api_key = "fake-novita-key"
result = config.validate_environment(
headers=headers,
model="novita/meta-llama/llama-3.3-70b-instruct",
messages=[{"role": "user", "content": "Hello"}],
optional_params={},
api_key=api_key,
api_base="https://api.novita.ai/v3/openai"
)
# Verify headers
assert result["Authorization"] == f"Bearer {api_key}"
assert result["Content-Type"] == "application/json"
assert result["X-Novita-Source"] == "litellm"
def test_missing_api_key(self):
"""Test error handling when API key is missing"""
config = NovitaConfig()
with pytest.raises(ValueError) as excinfo:
config.validate_environment(
headers={},
model="novita/meta-llama/llama-3.3-70b-instruct",
messages=[{"role": "user", "content": "Hello"}],
optional_params={},
api_key=None,
api_base="https://api.novita.ai/v3/openai"
)
assert "Missing Novita AI API Key" in str(excinfo.value)
def test_inheritance(self):
"""Test proper inheritance from OpenAIGPTConfig"""
config = NovitaConfig()
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
assert isinstance(config, OpenAIGPTConfig)
assert hasattr(config, "get_supported_openai_params")

View file

@ -4658,6 +4658,77 @@ def test_humanloop_completion(monkeypatch):
messages=[{"role": "user", "content": "Tell me a joke."}], messages=[{"role": "user", "content": "Tell me a joke."}],
) )
def test_completion_novita_ai():
litellm.set_verbose = True
messages = [
{"role": "system", "content": "You're a good bot"},
{
"role": "user",
"content": "Hey",
},
]
from openai import OpenAI
openai_client = OpenAI(api_key="fake-key")
with patch.object(
openai_client.chat.completions, "create", new=MagicMock()
) as mock_call:
try:
completion(
model="novita/meta-llama/llama-3.3-70b-instruct",
messages=messages,
client=openai_client,
api_base="https://api.novita.ai/v3/openai",
)
mock_call.assert_called_once()
# Verify model is passed correctly
assert mock_call.call_args.kwargs["model"] == "meta-llama/llama-3.3-70b-instruct"
# Verify messages are passed correctly
assert mock_call.call_args.kwargs["messages"] == messages
except Exception as e:
pytest.fail(f"Error occurred: {e}")
@pytest.mark.parametrize(
"api_key", ["my-bad-api-key"]
)
def test_completion_novita_ai_dynamic_params(api_key):
try:
litellm.set_verbose = True
messages = [
{"role": "system", "content": "You're a good bot"},
{
"role": "user",
"content": "Hey",
},
]
from openai import OpenAI
openai_client = OpenAI(api_key="fake-key")
with patch.object(
openai_client.chat.completions, "create", side_effect=Exception("Invalid API key")
) as mock_call:
try:
completion(
model="novita/meta-llama/llama-3.3-70b-instruct",
messages=messages,
api_key=api_key,
client=openai_client,
api_base="https://api.novita.ai/v3/openai",
)
pytest.fail(f"This call should have failed!")
except Exception as e:
# This should fail with the mocked exception
assert "Invalid API key" in str(e)
mock_call.assert_called_once()
except Exception as e:
pytest.fail(f"Unexpected error: {e}")
def test_deepseek_reasoning_content_completion(): def test_deepseek_reasoning_content_completion():
try: try: