Add digitalocean provider (#12169)

* Add digitalocean provider

* Add digitalocean provider

* Revert "Add digitalocean provider"

This reverts commit 96dda40f45b3d12ea03e861d060ec81460b7759e.

* changes

* fixes

* Update transformation

* refactoring

* rename provider to Gradient AI

* fixes

* Incorporte review comments

* revert changes

* fix typo

* revert change

* incorporated review comments

* Revert "Incorporte review comments"

This reverts commit 37bd51bd54ef4fd52ccc12866e47f8de9476d597.

* changes

* Revert "Revert "Incorporte review comments"

This reverts commit 37bd51bd54ef4fd52ccc12866e47f8de9476d597."

This reverts commit 68c8a198ee0d6441c3a52f6c6a49c9c95a4cb0a8.

* changes

* fixes

* Update provider_specific_fields.tsx
This commit is contained in:
Sannan Nasir
2025-08-09 16:26:33 -07:00
committed by GitHub
parent f60a9cf908
commit 0e53b1feab
15 changed files with 752 additions and 26 deletions
+7 -6
View File
@@ -47,7 +47,7 @@ Support for more providers. Missing a provider or LLM Platform, raise a [feature
# Usage ([**Docs**](https://docs.litellm.ai/docs/))
> [!IMPORTANT]
> LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration)
> LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration)
> LiteLLM v1.40.14+ now requires `pydantic>=2.0.0`. No changes required.
<a target="_blank" href="https://colab.research.google.com/github/BerriAI/litellm/blob/main/cookbook/liteLLM_Getting_Started.ipynb">
@@ -132,7 +132,7 @@ print(response)
## Streaming ([Docs](https://docs.litellm.ai/docs/completion/stream))
liteLLM supports streaming the model response back, pass `stream=True` to get a streaming iterator in response.
liteLLM supports streaming the model response back, pass `stream=True` to get a streaming iterator in response.
Streaming is supported for all models (Bedrock, Huggingface, TogetherAI, Azure, OpenAI, etc.)
```python
@@ -234,7 +234,7 @@ $ litellm --model huggingface/bigcode/starcoder
> [!IMPORTANT]
> 💡 [Use LiteLLM Proxy with Langchain (Python, JS), OpenAI SDK (Python, JS) Anthropic SDK, Mistral SDK, LlamaIndex, Instructor, Curl](https://docs.litellm.ai/docs/proxy/user_keys)
> 💡 [Use LiteLLM Proxy with Langchain (Python, JS), OpenAI SDK (Python, JS) Anthropic SDK, Mistral SDK, LlamaIndex, Instructor, Curl](https://docs.litellm.ai/docs/proxy/user_keys)
```python
import openai # openai v1.0.0+
@@ -266,7 +266,7 @@ echo 'LITELLM_MASTER_KEY="sk-1234"' > .env
# Add the litellm salt key - you cannot change this after adding a model
# It is used to encrypt / decrypt your LLM API Key credentials
# We recommend - https://1password.com/password-generator/
# We recommend - https://1password.com/password-generator/
# password generator to get a random hash for litellm salt key
echo 'LITELLM_SALT_KEY="sk-1234"' >> .env
@@ -340,6 +340,7 @@ curl 'http://0.0.0.0:4000/key/generate' \
| [xinference [Xorbits Inference]](https://docs.litellm.ai/docs/providers/xinference) | | | | | ✅ | |
| [FriendliAI](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | ✅ | | |
| [Galadriel](https://docs.litellm.ai/docs/providers/galadriel) | ✅ | ✅ | ✅ | ✅ | | |
| [GradientAI](https://docs.litellm.ai/docs/providers/gradient_ai) | ✅ | ✅ | | | | |
| [Novita AI](https://novita.ai/models/llm?utm_source=github_litellm&utm_medium=github_readme&utm_campaign=github_link) | ✅ | ✅ | ✅ | ✅ | | |
| [Featherless AI](https://docs.litellm.ai/docs/providers/featherless_ai) | ✅ | ✅ | ✅ | ✅ | | |
| [Nebius AI Studio](https://docs.litellm.ai/docs/providers/nebius) | ✅ | ✅ | ✅ | ✅ | ✅ | |
@@ -348,7 +349,7 @@ curl 'http://0.0.0.0:4000/key/generate' \
## Contributing
Interested in contributing? Contributions to LiteLLM Python SDK, Proxy Server, and LLM integrations are both accepted and highly encouraged!
Interested in contributing? Contributions to LiteLLM Python SDK, Proxy Server, and LLM integrations are both accepted and highly encouraged!
**Quick start:** `git clone``make install-dev``make format``make lint``make test-unit`
@@ -359,7 +360,7 @@ For companies that need better security, user management and professional suppor
[Talk to founders](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
This covers:
This covers:
-**Features under the [LiteLLM Commercial License](https://docs.litellm.ai/docs/proxy/enterprise):**
-**Feature Prioritization**
-**Custom Integrations**
@@ -0,0 +1,79 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# GradientAI
https://digitalocean.com/products/gradientai
LiteLLM provides native support for GradientAI models.
To use a GradientAI model, specify it as `gradient_ai/<model-name>` in your LiteLLM requests.
## API Key & Endpoint
Set your credentials and endpoint as environment variables:
```python
import os
os.environ['GRADIENT_AI_API_KEY'] = "your-api-key"
os.environ['GRADIENT_AI_AGENT_ENDPOINT'] = "https://api.gradient_ai.com/api/v1/chat" # default endpoint
```
## Sample Usage
```python
from litellm import completion
import os
os.environ['GRADIENT_AI_API_KEY'] = "your-api-key"
response = completion(
model="gradient_ai/model-name",
messages=[
{"role": "user", "content": "Hello, how are you?"}
],
)
print(response.choices[0].message.content)
```
## Streaming Example
```python
from litellm import completion
import os
os.environ['GRADIENT_AI_API_KEY'] = "your-api-key"
response = completion(
model="gradient_ai/model-name",
messages=[
{"role": "user", "content": "Write a story about a robot learning to love"}
],
stream=True,
)
for chunk in response:
print(chunk.choices[0].delta.content or "", end="")
```
## Supported Parameters
| Parameter | Type | Description |
|-----------------------------------|--------------|--------------------------------------------------------------------|
| `temperature` | float | Controls randomness (0.0-2.0) |
| `top_p` | float | Nucleus sampling parameter (0.0-1.0) |
| `max_tokens` | int | Maximum tokens to generate |
| `max_completion_tokens` | int | Alternative to max_tokens |
| `stream` | bool | Whether to stream the response |
| `k` | int | Top results to return from knowledge bases |
| `retrieval_method` | string | Retrieval strategy (rewrite/step_back/sub_queries/none) |
| `frequency_penalty` | float | Penalizes repeated tokens (-2.0 to 2.0) |
| `presence_penalty` | float | Penalizes tokens based on presence (-2.0 to 2.0) |
| `stop` | string/list | Sequences to stop generation |
| `kb_filters` | List[Dict] | Filters for knowledge base retrieval |
| `instruction_override` | string | Override agent's default instruction |
| `include_retrieval_info` | bool | Include document retrieval metadata |
| `include_guardrails_info` | bool | Include guardrail trigger metadata |
| `provide_citations` | bool | Include citations in response |
---
For more details, see [DigitalOcean GradientAI documentation](https://digitalocean.com/products/gradientai).
+6 -5
View File
@@ -82,12 +82,12 @@ const sidebars = {
"tutorials/cost_tracking_coding",
]
},
],
// But you can create a sidebar manually
tutorialSidebar: [
{ type: "doc", id: "index" }, // NEW
{
type: "category",
label: "LiteLLM Proxy Server",
@@ -214,7 +214,7 @@ const sidebars = {
"proxy/dynamic_logging"
],
},
{
type: "category",
label: "Secret Managers",
@@ -467,6 +467,7 @@ const sidebars = {
"providers/custom_llm_server",
"providers/petals",
"providers/snowflake",
"providers/gradient_ai",
"providers/featherless_ai",
"providers/nebius",
"providers/dashscope",
@@ -505,7 +506,7 @@ const sidebars = {
]
},
{
type: "category",
label: "Routing, Loadbalancing & Fallbacks",
@@ -536,7 +537,7 @@ const sidebars = {
},
],
},
{
type: "category",
label: "Load Testing",
+7 -1
View File
@@ -231,6 +231,7 @@ aleph_alpha_key: Optional[str] = None
nlp_cloud_key: Optional[str] = None
novita_api_key: Optional[str] = None
snowflake_key: Optional[str] = None
gradient_ai_api_key: Optional[str] = None
nebius_key: Optional[str] = None
common_cloud_provider_auth_params: dict = {
"params": ["project", "region_name", "token"],
@@ -520,6 +521,7 @@ sambanova_models: List = []
novita_models: List = []
assemblyai_models: List = []
snowflake_models: List = []
gradient_ai_models: List = []
llama_models: List = []
nscale_models: List = []
nebius_models: List = []
@@ -703,6 +705,8 @@ def add_known_models():
jina_ai_models.append(key)
elif value.get("litellm_provider") == "snowflake":
snowflake_models.append(key)
elif value.get("litellm_provider") == "gradient_ai":
gradient_ai_models.append(key)
elif value.get("litellm_provider") == "featherless_ai":
featherless_ai_models.append(key)
elif value.get("litellm_provider") == "deepgram":
@@ -802,6 +806,7 @@ model_list = (
+ assemblyai_models
+ jina_ai_models
+ snowflake_models
+ gradient_ai_models
+ llama_models
+ featherless_ai_models
+ nscale_models
@@ -875,6 +880,7 @@ models_by_provider: dict = {
"assemblyai": assemblyai_models,
"jina_ai": jina_ai_models,
"snowflake": snowflake_models,
"gradient_ai": gradient_ai_models,
"meta_llama": llama_models,
"nscale": nscale_models,
"featherless_ai": featherless_ai_models,
@@ -1141,7 +1147,7 @@ from .llms.openai.chat.o_series_transformation import (
)
from .llms.snowflake.chat.transformation import SnowflakeConfig
from .llms.gradient_ai.chat.transformation import GradientAIConfig
openaiOSeriesConfig = OpenAIOSeriesConfig()
from .llms.openai.chat.gpt_transformation import (
OpenAIGPTConfig,
+1
View File
@@ -270,6 +270,7 @@ LITELLM_CHAT_PROVIDERS = [
"llamafile",
"lm_studio",
"galadriel",
"gradient_ai",
"github_copilot", # GitHub Copilot Chat API
"novita",
"meta_llama",
@@ -351,6 +351,8 @@ def get_llm_provider( # noqa: PLR0915
custom_llm_provider = "openai"
elif model in litellm.empower_models:
custom_llm_provider = "empower"
elif model in litellm.gradient_ai_models:
custom_llm_provider = "gradient_ai"
elif model == "*":
custom_llm_provider = "openai"
# bytez models
@@ -664,6 +666,13 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915
or f"https://{get_secret('SNOWFLAKE_ACCOUNT_ID')}.snowflakecomputing.com/api/v2/cortex/inference:complete"
) # type: ignore
dynamic_api_key = api_key or get_secret_str("SNOWFLAKE_JWT")
elif custom_llm_provider == "gradient_ai":
(
api_base,
dynamic_api_key,
) = litellm.GradientAIConfig()._get_openai_compatible_provider_info(
api_base, api_key
)
elif custom_llm_provider == "featherless_ai":
(
api_base,
@@ -0,0 +1,147 @@
from typing import List, Optional, Tuple, Union, Dict, Literal
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import (
AllMessageValues,
)
from ...openai_like.chat.transformation import OpenAILikeChatConfig
# Default GradientAI endpoint
GRADIENT_AI_SERVERLESS_ENDPOINT = "https://inference.do-ai.run"
class GradientAIConfig(OpenAILikeChatConfig):
k: Optional[int] = None
kb_filters: Optional[List[Dict]] = None
filter_kb_content_by_query_metadata: Optional[bool] = None
instruction_override: Optional[str] = None
include_functions_info: Optional[bool] = None
include_retrieval_info: Optional[bool] = None
include_guardrails_info: Optional[bool] = None
provide_citations: Optional[bool] = None
retrieval_method: Optional[Literal["rewrite", "step_back", "sub_queries", "none"]] = None
def __init__(
self,
frequency_penalty: Optional[float] = None,
max_tokens: Optional[int] = None,
max_completion_tokens: Optional[int] = None,
presence_penalty: Optional[float] = None,
retrieval_method: Optional[str] = None,
stop: Optional[Union[str, List[str]]] = None,
stream: Optional[bool] = None,
temperature: Optional[float] = None,
top_p: Optional[float] = None,
k: Optional[int] = None,
kb_filters: Optional[List[Dict]] = None,
filter_kb_content_by_query_metadata: Optional[bool] = None,
instruction_override: Optional[str] = None,
include_functions_info: Optional[bool] = None,
include_retrieval_info: Optional[bool] = None,
include_guardrails_info: Optional[bool] = None,
provide_citations: Optional[bool] = None,
) -> None:
locals_ = locals().copy()
for key, value in locals_.items():
if key != "self" and value is not None:
setattr(self.__class__, key, value)
@classmethod
def get_config(cls):
return super().get_config()
def get_supported_openai_params(self, model: str) -> list:
supported_params = [
"frequency_penalty",
"max_tokens",
"max_completion_tokens",
"presence_penalty",
"stop",
"stream",
"stream_options",
"temperature",
"top_p",
# GradientAI specific parameters
"k",
"kb_filters",
"filter_kb_content_by_query_metadata",
"instruction_override",
"include_functions_info",
"include_retrieval_info",
"include_guardrails_info",
"provide_citations",
"retrieval_method",
]
return supported_params
def validate_environment(self,
headers: dict,
model: str,
messages: List[AllMessageValues],
optional_params: dict,
litellm_params: dict,
api_key: Optional[str] = None,
api_base: Optional[str] = None):
api_key = api_key or get_secret_str("GRADIENT_AI_API_KEY")
if api_key is None:
raise ValueError("GradientAI API key not found")
if headers is None:
headers = {}
headers["Authorization"] = f"Bearer {api_key}"
headers["Content-Type"] = "application/json"
return headers
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:
gradient_ai_endpoint = get_secret_str("GRADIENT_AI_AGENT_ENDPOINT")
complete_url = f"{GRADIENT_AI_SERVERLESS_ENDPOINT}/v1/chat/completions"
if api_base and api_base != GRADIENT_AI_SERVERLESS_ENDPOINT:
complete_url = f"{api_base}/api/v1/chat/completions"
elif gradient_ai_endpoint and gradient_ai_endpoint != GRADIENT_AI_SERVERLESS_ENDPOINT:
complete_url = f"{gradient_ai_endpoint}/api/v1/chat/completions"
return complete_url
def _get_openai_compatible_provider_info(
self, api_base: Optional[str], api_key: Optional[str]
) -> Tuple[Optional[str], Optional[str]]:
gradient_ai_endpoint = get_secret_str("GRADIENT_AI_AGENT_ENDPOINT")
if not api_base and not gradient_ai_endpoint:
api_base = GRADIENT_AI_SERVERLESS_ENDPOINT
else:
api_base = api_base or gradient_ai_endpoint
dynamic_api_key = api_key or get_secret_str("GRADIENT_AI_API_KEY")
return api_base, dynamic_api_key
def map_openai_params(
self,
non_default_params: dict,
optional_params: dict,
model: str,
drop_params: bool = False,
replace_max_completion_tokens_with_max_tokens: bool = False,
) -> dict:
supported_openai_params = self.get_supported_openai_params(model=model)
for param, value in non_default_params.items():
if param in supported_openai_params:
optional_params[param] = value
elif not drop_params:
from litellm.utils import UnsupportedParamsError
raise UnsupportedParamsError(
status_code=400,
message=f"GradientAI does not support parameter '{param}'. To drop unsupported params, set `drop_params=True`."
)
return optional_params
+19
View File
@@ -3303,6 +3303,25 @@ def completion( # type: ignore # noqa: PLR0915
additional_args={"headers": headers},
)
raise e
elif custom_llm_provider == "gradient_ai":
api_base = litellm.api_base or api_base
response = base_llm_http_handler.completion(
model=model,
stream=stream,
messages=messages,
acompletion=acompletion,
api_base=api_base,
model_response=model_response,
optional_params=optional_params,
litellm_params=litellm_params,
custom_llm_provider="gradient_ai",
timeout=timeout,
headers=headers,
encoding=encoding,
api_key=api_key,
logging_obj=logging,
)
elif custom_llm_provider == "bytez":
api_key = (
+2 -1
View File
@@ -1618,7 +1618,7 @@ class ImageResponse(OpenAIImageResponse, BaseLiteLLMOpenAIResponseObject):
usage: Optional[ImageUsage] = None # type: ignore
"""
Users might use litellm with older python versions, we don't want this to break for them.
Users might use litellm with older python versions, we don't want this to break for them.
Happens when their OpenAIImageResponse has the old OpenAI usage class.
"""
@@ -2324,6 +2324,7 @@ class LlmProviders(str, Enum):
ASSEMBLYAI = "assemblyai"
GITHUB_COPILOT = "github_copilot"
SNOWFLAKE = "snowflake"
GRADIENT_AI = "gradient_ai"
LLAMA = "meta_llama"
NSCALE = "nscale"
PG_VECTOR = "pg_vector"
+2
View File
@@ -6963,6 +6963,8 @@ class ProviderConfigManager:
return litellm.LiteLLMProxyChatConfig()
elif litellm.LlmProviders.OPENAI == provider:
return litellm.OpenAIGPTConfig()
elif litellm.LlmProviders.GRADIENT_AI == provider:
return litellm.GradientAIConfig()
elif litellm.LlmProviders.NSCALE == provider:
return litellm.NscaleConfig()
elif litellm.LlmProviders.OCI == provider:
+124
View File
@@ -17098,6 +17098,130 @@
"litellm_provider": "snowflake",
"mode": "chat"
},
"gradient_ai/anthropic-claude-3.7-sonnet": {
"input_cost_per_token": 3e-06,
"output_cost_per_token": 15e-06,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 1024,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/anthropic-claude-3.5-sonnet": {
"input_cost_per_token": 3e-06,
"output_cost_per_token": 15e-06,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 1024,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/anthropic-claude-3.5-haiku": {
"input_cost_per_token": 8e-07,
"output_cost_per_token": 4e-06,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 1024,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/anthropic-claude-3-opus": {
"input_cost_per_token": 15e-06,
"output_cost_per_token": 75e-06,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 1024,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/deepseek-r1-distill-llama-70b": {
"input_cost_per_token": 99e-08,
"output_cost_per_token": 99e-08,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 8000,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/llama3.3-70b-instruct": {
"input_cost_per_token": 65e-08,
"output_cost_per_token": 65e-08,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 2048,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/llama3-8b-instruct": {
"input_cost_per_token": 2e-07,
"output_cost_per_token": 2e-07,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 512,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/mistral-nemo-instruct-2407": {
"input_cost_per_token": 3e-07,
"output_cost_per_token": 3e-07,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 512,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/openai-o3": {
"input_cost_per_token": 2e-06,
"output_cost_per_token": 8e-06,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 100000,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/openai-o3-mini": {
"input_cost_per_token": 11e-07,
"output_cost_per_token": 44e-07,
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 100000,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/openai-gpt-4o": {
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 16384,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/openai-gpt-4o-mini": {
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 16384,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"gradient_ai/alibaba-qwen3-32b": {
"litellm_provider": "gradient_ai",
"mode": "chat",
"max_tokens": 2048,
"supported_endpoints": ["/v1/chat/completions"],
"supported_modalities": ["text"],
"supports_tool_choice": false
},
"nscale/meta-llama/Llama-4-Scout-17B-16E-Instruct": {
"input_cost_per_token": 9e-08,
"output_cost_per_token": 2.9e-07,
@@ -0,0 +1,91 @@
import os
import sys
import pytest
sys.path.insert(
0, os.path.abspath("../../../../..")
) # Adds the parent directory to the system path
from litellm.llms.gradient_ai.chat.transformation import GradientAIConfig, GRADIENT_AI_SERVERLESS_ENDPOINT
DO_ENDPOINT_PATH = "/api/v1/chat/completions"
DO_BASE_URL = "https://api.gradient_ai.com"
@pytest.fixture
def config():
return GradientAIConfig()
def test_validate_environment_sets_headers(monkeypatch, config):
monkeypatch.setenv("GRADIENT_AI_API_KEY", "test-key")
headers = {}
result = config.validate_environment(
headers=headers,
model="gradient_ai/test-model",
messages=[],
optional_params={},
litellm_params={},
api_key=None,
api_base=None,
)
assert result["Authorization"] == "Bearer test-key"
assert result["Content-Type"] == "application/json"
def test_get_complete_url_custom_base(config):
url = config.get_complete_url(
api_base=DO_BASE_URL,
api_key="test-key",
model="gradient_ai/test-model",
optional_params={},
litellm_params={},
stream=False,
)
assert url == f"{DO_BASE_URL}{DO_ENDPOINT_PATH}"
def test_get_complete_url_default_serverless(monkeypatch, config):
monkeypatch.delenv("GRADIENT_AI_AGENT_ENDPOINT", raising=False)
url = config.get_complete_url(
api_base=None,
api_key="test-key",
model="gradient_ai/test-model",
optional_params={},
litellm_params={},
stream=False,
)
assert url == f"{GRADIENT_AI_SERVERLESS_ENDPOINT}/v1/chat/completions"
def test_get_complete_url_with_env_endpoint(monkeypatch, config):
monkeypatch.setenv("GRADIENT_AI_AGENT_ENDPOINT", DO_BASE_URL)
url = config.get_complete_url(
api_base=None,
api_key="test-key",
model="gradient_ai/test-model",
optional_params={},
litellm_params={},
stream=False,
)
assert url == f"{DO_BASE_URL}{DO_ENDPOINT_PATH}"
def test_transform_messages_handles_dicts_only(config):
messages = [
{"role": "assistant", "content": "Hello!"},
{"role": "user", "content": "Hi!"},
]
out = config._transform_messages(messages, model="gradient_ai/test-model")
assert out[0]["role"] == "assistant"
assert out[0]["content"] == "Hello!"
assert out[1]["role"] == "user"
assert out[1]["content"] == "Hi!"
def test_get_openai_compatible_provider_info_env(monkeypatch, config):
monkeypatch.setenv("GRADIENT_AI_AGENT_ENDPOINT", DO_BASE_URL)
monkeypatch.setenv("GRADIENT_AI_API_KEY", "env-key")
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
assert api_base == DO_BASE_URL
assert api_key == "env-key"
def test_get_openai_compatible_provider_info_default(monkeypatch, config):
monkeypatch.delenv("GRADIENT_AI_AGENT_ENDPOINT", raising=False)
monkeypatch.setenv("GRADIENT_AI_API_KEY", "env-key")
api_base, api_key = config._get_openai_compatible_provider_info(None, None)
assert api_base == GRADIENT_AI_SERVERLESS_ENDPOINT
assert api_key == "env-key"
@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="68.544662mm"
height="68.542419mm"
viewBox="0 0 68.544665 68.542419"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (b8e25be833, 2022-02-05)"
sodipodi:docname="DigitalOcean 2016 Icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.90583672"
inkscape:cx="219.68639"
inkscape:cy="198.71131"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g11760" />
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath838">
<path
d="M 0,792 H 612 V 0 H 0 Z"
id="path836" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath858">
<path
d="M 0,792 H 612 V 0 H 0 Z"
id="path856" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath255">
<path
d="M 425.197,24.946 H 559.275 V 161.574 H 425.197 Z"
id="path253"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2816">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2814"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2794">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2792"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2778">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2776"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2748">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2746"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2710">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2708"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2664">
<path
d="M 0,0 H 396 V 612 H 0 Z"
id="path2662"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6055">
<path
d="m 170.008,153.156 v 18.559 h -5.93 v -18.559 h 5.93"
id="path6053"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6035">
<path
d="M 30,0 V 292 H 177 V 0 Z"
id="path6033"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath6025">
<path
d="m 170.008,153.156 v 18.559 h -5.93 v -18.559 h 5.93"
id="path6023"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath81">
<path
d="M 45.36,756.5 H 90.60001 V 806 H 45.36 Z"
id="path79"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath71">
<path
d="M 45,755.12 H 90.84 V 806 H 45 Z"
id="path69"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7101">
<path
d="M 0,0 H 612 V 828 H 0 Z"
id="path7099"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath9113">
<path
d="M 0,531 H 719.972 V 0 H 0 Z"
id="path9111"
inkscape:connector-curvature="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath11764">
<path
d="M 0,792 H 612 V 0 H 0 Z"
id="path11762"
inkscape:connector-curvature="0" />
</clipPath>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-23.73095,-45.75945)">
<g
id="g830"
inkscape:label="PSA-Style-Guide"
transform="matrix(0.35277777,0,0,-0.35277777,-34.820531,253.68695)">
<g
style="fill:none"
id="g2484"
transform="matrix(4.7902363,0,0,-4.7902363,168.73293,576.70634)">
<g
id="g11760"
clip-path="url(#clipPath11764)"
transform="matrix(0.92105933,0,0,-0.92105933,-111.34849,650.43322)">
<g
id="g11766"
transform="matrix(1.5371265,0,0,1.5371265,142.2855,665.02011)">
<path
d="m 0,0 v 5.547 c 5.888,0 10.439,5.828 8.192,12.017 -0.833,2.292 -2.661,4.121 -4.954,4.953 -6.189,2.246 -12.014,-2.305 -12.015,-8.191 0,0 0,-0.002 -0.001,-0.002 h -5.547 c 0,9.38 9.062,16.683 18.89,13.614 4.295,-1.34 7.708,-4.753 9.049,-9.048 C 16.683,9.061 9.379,0 0,0"
style="fill:#0069ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path11768"
inkscape:connector-curvature="0" />
</g>
<g
id="g11770"
transform="matrix(1.5371265,0,0,1.5371265,142.30502,673.52534)">
<path
d="m 0,0 h -5.531 v 5.53 c 0,0 0,10e-4 10e-4,10e-4 h 5.529 L 0,5.53 Z"
style="fill:#0069ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path11772"
inkscape:connector-curvature="0" />
</g>
<g
id="g11774"
transform="matrix(1.5371265,0,0,1.5371265,133.80133,666.99332)">
<path
d="M 0,0 H -4.249 L -4.25,0.001 V 4.25 H 0.001 V 0.001 Z"
style="fill:#0069ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path11776"
inkscape:connector-curvature="0" />
</g>
<g
id="g11778"
transform="matrix(1.5371265,0,0,1.5371265,127.27377,673.52534)">
<path
d="m 0,0 h -3.562 c -0.001,0 -0.002,0.001 -0.002,0.001 v 3.56 c 0,0 10e-4,0.003 0.002,0.003 h 3.56 C -0.001,3.564 0,3.563 0,3.563 Z"
style="fill:#0069ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path11780"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -374,6 +374,20 @@ const PROVIDER_CREDENTIAL_FIELDS: Record<Providers, ProviderCredentialField[]> =
type: "password",
required: true
}],
[Providers.GradientAI]: [
{
key: "api_base",
label: "GradientAI Endpoint",
placeholder: "https://...",
required: false
},
{
key: "api_key",
label: "GradientAI API Key",
type: "password",
required: true
}
],
[Providers.Triton]: [{
key: "api_key",
label: "API Key",
@@ -446,7 +460,7 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
onChange(info: any) {
console.log("Upload onChange triggered in ProviderSpecificFields");
console.log("Current form values:", form.getFieldsValue());
if (info.file.status !== "uploading") {
console.log(info.file, info.fileList);
}
@@ -465,7 +479,7 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
className={field.key === "vertex_credentials" ? "mb-0" : undefined}
>
{field.type === "select" ? (
<Select
<Select
placeholder={field.placeholder}
defaultValue={field.defaultValue}
>
@@ -476,14 +490,14 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
))}
</Select>
) : field.type === "upload" ? (
<Upload
<Upload
{...handleUpload}
onChange={(info) => {
// First call the original onChange
if (uploadProps?.onChange) {
uploadProps.onChange(info);
}
// Check the field value after a short delay
setTimeout(() => {
const value = form.getFieldValue(field.key);
@@ -494,9 +508,9 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
<Button2 icon={<UploadOutlined />}>Click to Upload</Button2>
</Upload>
) : (
<TextInput
placeholder={field.placeholder}
type={field.type === "password" ? "password" : "text"}
<TextInput
placeholder={field.placeholder}
type={field.type === "password" ? "password" : "text"}
/>
)}
</Form.Item>
@@ -536,4 +550,4 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
);
};
export default ProviderSpecificFields;
export default ProviderSpecificFields;
@@ -17,6 +17,7 @@ export enum Providers {
ElevenLabs = "ElevenLabs",
FireworksAI = "Fireworks AI",
Google_AI_Studio = "Google AI Studio",
GradientAI = "GradientAI",
Groq = "Groq",
JinaAI = "Jina AI",
MistralAI = "Mistral AI",
@@ -35,7 +36,7 @@ export enum Providers {
Voyage = "Voyage AI",
xAI = "xAI",
}
export const provider_map: Record<string, string> = {
OpenAI: "openai",
OpenAI_Text: "text-completion-openai",
@@ -61,6 +62,7 @@ export const provider_map: Record<string, string> = {
TogetherAI: "together_ai",
Openrouter: "openrouter",
FireworksAI: "fireworks_ai",
GradientAI: "gradient_ai",
Triton: "triton",
Deepgram: "deepgram",
ElevenLabs: "elevenlabs",
@@ -99,6 +101,7 @@ export const providerLogoMap: Record<string, string> = {
[Providers.TogetherAI]: `${asset_logos_folder}togetherai.svg`,
[Providers.Vertex_AI]: `${asset_logos_folder}google.svg`,
[Providers.xAI]: `${asset_logos_folder}xai.svg`,
[Providers.GradientAI]: `${asset_logos_folder}gradientai.svg`,
[Providers.Triton]: `${asset_logos_folder}nvidia_triton.png`,
[Providers.Deepgram]: `${asset_logos_folder}deepgram.png`,
[Providers.ElevenLabs]: `${asset_logos_folder}elevenlabs.png`,
@@ -169,9 +172,9 @@ export const getPlaceholder = (selectedProvider: string): string => {
console.log(`Provider key: ${providerKey}`);
let custom_llm_provider = provider_map[providerKey];
console.log(`Provider mapped to: ${custom_llm_provider}`);
let providerModels: Array<string> = [];
if (providerKey && typeof modelMap === "object") {
Object.entries(modelMap).forEach(([key, value]) => {
if (
@@ -184,7 +187,6 @@ export const getPlaceholder = (selectedProvider: string): string => {
providerModels.push(key);
}
});
// Special case for cohere
// we need both cohere_chat and cohere models to show on dropdown
if (providerKey == Providers.Cohere) {
@@ -217,6 +219,6 @@ export const getPlaceholder = (selectedProvider: string): string => {
});
}
}
return providerModels;
};