mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-11 23:47:11 +00:00
Merge pull request #14840 from eddierichter-amd/lemonade-integration
Add AMD Lemonade provider support
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Lemonade
|
||||
|
||||
[Lemonade Server](https://lemonade-server.ai/) is an OpenAI-compatible local language model inference provider optimized for AMD GPUs and NPUs. The `lemonade` litellm provider supports standard chat completions with full OpenAI API compatibility.
|
||||
|
||||
| Property | Details |
|
||||
|-------|-------|
|
||||
| Description | OpenAI-compatible AI provider for local and cloud-based language model inference |
|
||||
| Provider Route on LiteLLM | `lemonade/` (add this prefix to the model name - e.g. `lemonade/your-model-name`) |
|
||||
| API Endpoint for Provider | http://localhost:8000/api/v1 (default) |
|
||||
| Supported Endpoints | `/chat/completions` |
|
||||
|
||||
## Supported OpenAI Parameters
|
||||
|
||||
Lemonade is fully OpenAI-compatible and supports the following parameters:
|
||||
|
||||
```
|
||||
"repeat_penalty"
|
||||
"functions"
|
||||
"logit_bias"
|
||||
"max_tokens"
|
||||
"max_completion_tokens"
|
||||
"presence_penalty"
|
||||
"stop"
|
||||
"temperature"
|
||||
"top_p"
|
||||
"top_k"
|
||||
"response_format"
|
||||
"tools"
|
||||
```
|
||||
|
||||
|
||||
## API Key Setup
|
||||
|
||||
Lemonade can be configured with custom API URLs and doesn't require strict API key validation. Set the `LEMONADE_API_BASE` environment variable to modify the base URL.
|
||||
|
||||
## Usage
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="sdk" label="SDK">
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
import os
|
||||
|
||||
# Optional: Set custom API base. Useful if your lemonade server is on
|
||||
# a different port
|
||||
os.environ['LEMONADE_API_BASE'] = "http://localhost:8000/api/v1"
|
||||
|
||||
response = completion(
|
||||
model="lemonade/your-model-name",
|
||||
messages=[
|
||||
{"role": "user", "content": "Hello from LiteLLM!"}
|
||||
],
|
||||
)
|
||||
print(response)
|
||||
```
|
||||
|
||||
## Streaming
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
import os
|
||||
|
||||
# Optional: Set custom API base. Useful if your lemonade server is on
|
||||
# a different port
|
||||
os.environ['LEMONADE_API_BASE'] = "http://localhost:8000/api/v1"
|
||||
|
||||
response = completion(
|
||||
model="lemonade/your-model-name",
|
||||
messages=[
|
||||
{"role": "user", "content": "Write a short story"}
|
||||
],
|
||||
stream=True
|
||||
)
|
||||
|
||||
for chunk in response:
|
||||
print(chunk.choices[0].delta.content, end='', flush=True)
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Parameters
|
||||
|
||||
Lemonade supports additional parameters beyond the standard OpenAI set:
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
|
||||
response = completion(
|
||||
model="lemonade/your-model-name",
|
||||
messages=[{"role": "user", "content": "Explain quantum computing"}],
|
||||
temperature=0.7,
|
||||
max_tokens=500,
|
||||
top_p=0.9,
|
||||
top_k=50,
|
||||
repeat_penalty=1.1,
|
||||
stop=["Human:", "AI:"]
|
||||
)
|
||||
print(response)
|
||||
```
|
||||
|
||||
### Function Calling
|
||||
|
||||
Lemonade supports OpenAI-compatible function calling:
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
|
||||
functions = [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get current weather information",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state"
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
response = completion(
|
||||
model="lemonade/your-model-name",
|
||||
messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
|
||||
tools=[{"type": "function", "function": f} for f in functions],
|
||||
tool_choice="auto"
|
||||
)
|
||||
print(response)
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
Lemonade supports structured output with response format:
|
||||
|
||||
```python
|
||||
from litellm import completion
|
||||
import json
|
||||
|
||||
# Define schema in response_format
|
||||
response = completion(
|
||||
model="lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF",
|
||||
messages=[{"role": "user", "content": "Generate JSON data for a person with their name, age, and city."}],
|
||||
response_format={
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "person",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"city": {"type": "string"}
|
||||
},
|
||||
"required": ["name", "age"]
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Model: {response.model}")
|
||||
print(f"JSON Output:")
|
||||
json_data = json.loads(response.choices[0].message.content)
|
||||
print(json.dumps(json_data, indent=2))
|
||||
```
|
||||
|
||||
## Available Models
|
||||
|
||||
Lemonade automatically validates available models by querying the `/models` endpoint. You can check available models programmatically:
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
api_base = "http://localhost:8000" # or your custom base
|
||||
response = httpx.get(f"{api_base}/api/v1/models")
|
||||
models = response.json()
|
||||
print("Available models:", [model['id'] for model in models.get('data', [])])
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For more information regarding Lemonade please go to to the [Lemonade website](https://lemonade-server.ai/) or [Lemonade repository](https://github.com/lemonade-sdk/lemonade).
|
||||
@@ -477,6 +477,7 @@ const sidebars = {
|
||||
"providers/fireworks_ai",
|
||||
"providers/clarifai",
|
||||
"providers/compactifai",
|
||||
"providers/lemonade",
|
||||
"providers/vllm",
|
||||
"providers/llamafile",
|
||||
"providers/infinity",
|
||||
|
||||
@@ -250,6 +250,7 @@ wandb_key: Optional[str] = None
|
||||
heroku_key: Optional[str] = None
|
||||
cometapi_key: Optional[str] = None
|
||||
ovhcloud_key: Optional[str] = None
|
||||
lemonade_key: Optional[str] = None
|
||||
common_cloud_provider_auth_params: dict = {
|
||||
"params": ["project", "region_name", "token"],
|
||||
"providers": ["vertex_ai", "bedrock", "watsonx", "azure", "vertex_ai_beta"],
|
||||
@@ -536,6 +537,7 @@ volcengine_models: Set = set()
|
||||
wandb_models: Set = set(WANDB_MODELS)
|
||||
ovhcloud_models: Set = set()
|
||||
ovhcloud_embedding_models: Set = set()
|
||||
lemonade_models: Set = set()
|
||||
|
||||
|
||||
def is_bedrock_pricing_only_model(key: str) -> bool:
|
||||
@@ -756,6 +758,8 @@ def add_known_models():
|
||||
ovhcloud_models.add(key)
|
||||
elif value.get("litellm_provider") == "ovhcloud-embedding-models":
|
||||
ovhcloud_embedding_models.add(key)
|
||||
elif value.get("litellm_provider") == "lemonade":
|
||||
lemonade_models.add(key)
|
||||
|
||||
|
||||
add_known_models()
|
||||
@@ -852,6 +856,7 @@ model_list = list(
|
||||
| volcengine_models
|
||||
| wandb_models
|
||||
| ovhcloud_models
|
||||
| lemonade_models
|
||||
)
|
||||
|
||||
model_list_set = set(model_list)
|
||||
@@ -935,6 +940,7 @@ models_by_provider: dict = {
|
||||
"volcengine": volcengine_models,
|
||||
"wandb": wandb_models,
|
||||
"ovhcloud": ovhcloud_models | ovhcloud_embedding_models,
|
||||
"lemonade": lemonade_models,
|
||||
}
|
||||
|
||||
# mapping for those models which have larger equivalents
|
||||
@@ -1284,6 +1290,7 @@ from .llms.hyperbolic.chat.transformation import HyperbolicChatConfig
|
||||
from .llms.vercel_ai_gateway.chat.transformation import VercelAIGatewayConfig
|
||||
from .llms.ovhcloud.chat.transformation import OVHCloudChatConfig
|
||||
from .llms.ovhcloud.embedding.transformation import OVHCloudEmbeddingConfig
|
||||
from .llms.lemonade.chat.transformation import LemonadeChatConfig
|
||||
from .main import * # type: ignore
|
||||
from .integrations import *
|
||||
from .llms.custom_httpx.async_client_cleanup import close_litellm_async_clients
|
||||
|
||||
@@ -315,6 +315,7 @@ LITELLM_CHAT_PROVIDERS = [
|
||||
"vercel_ai_gateway",
|
||||
"wandb",
|
||||
"ovhcloud",
|
||||
"lemonade"
|
||||
]
|
||||
|
||||
LITELLM_EMBEDDING_PROVIDERS_SUPPORTING_INPUT_ARRAY_OF_TOKENS = [
|
||||
|
||||
@@ -58,6 +58,9 @@ from litellm.llms.vertex_ai.cost_calculator import (
|
||||
)
|
||||
from litellm.llms.vertex_ai.cost_calculator import cost_router as google_cost_router
|
||||
from litellm.llms.xai.cost_calculator import cost_per_token as xai_cost_per_token
|
||||
from litellm.llms.lemonade.cost_calculator import (
|
||||
cost_per_token as lemonade_cost_per_token,
|
||||
)
|
||||
from litellm.responses.utils import ResponseAPILoggingUtils
|
||||
from litellm.types.llms.openai import (
|
||||
HttpxBinaryResponseContent,
|
||||
@@ -347,6 +350,8 @@ def cost_per_token( # noqa: PLR0915
|
||||
return perplexity_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "xai":
|
||||
return xai_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "lemonade":
|
||||
return lemonade_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "dashscope":
|
||||
from litellm.llms.dashscope.cost_calculator import (
|
||||
cost_per_token as dashscope_cost_per_token,
|
||||
|
||||
@@ -368,6 +368,8 @@ def get_llm_provider( # noqa: PLR0915
|
||||
# bytez models
|
||||
elif model.startswith("bytez/"):
|
||||
custom_llm_provider = "bytez"
|
||||
elif model.startswith("lemonade/"):
|
||||
custom_llm_provider = "lemonade"
|
||||
elif model.startswith("heroku/"):
|
||||
custom_llm_provider = "heroku"
|
||||
# cometapi models
|
||||
@@ -379,6 +381,8 @@ def get_llm_provider( # noqa: PLR0915
|
||||
custom_llm_provider = "compactifai"
|
||||
elif model.startswith("ovhcloud/"):
|
||||
custom_llm_provider = "ovhcloud"
|
||||
elif model.startswith("lemonade/"):
|
||||
custom_llm_provider = "lemonade"
|
||||
if not custom_llm_provider:
|
||||
if litellm.suppress_debug_info is False:
|
||||
print() # noqa
|
||||
@@ -783,6 +787,13 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915
|
||||
or "https://api.inference.wandb.ai/v1"
|
||||
) # type: ignore
|
||||
dynamic_api_key = api_key or get_secret_str("WANDB_API_KEY")
|
||||
elif custom_llm_provider == "lemonade":
|
||||
(
|
||||
api_base,
|
||||
dynamic_api_key,
|
||||
) = litellm.LemonadeChatConfig()._get_openai_compatible_provider_info(
|
||||
api_base, api_key
|
||||
)
|
||||
|
||||
if api_base is not None and not isinstance(api_base, str):
|
||||
raise Exception("api base needs to be a string. api_base={}".format(api_base))
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Translate from OpenAI's `/v1/chat/completions` to Lemonade's `/v1/chat/completions`
|
||||
"""
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import (
|
||||
AllMessageValues,
|
||||
)
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
from ...openai_like.chat.transformation import OpenAILikeChatConfig
|
||||
|
||||
|
||||
class LemonadeChatConfig(OpenAILikeChatConfig):
|
||||
repeat_penalty: Optional[float] = None
|
||||
functions: Optional[list] = None
|
||||
logit_bias: Optional[dict] = None
|
||||
max_tokens: Optional[int] = None
|
||||
max_completion_tokens: Optional[int] = None
|
||||
n: Optional[int] = None
|
||||
presence_penalty: Optional[int] = None
|
||||
stop: Optional[Union[str, list]] = None
|
||||
temperature: Optional[int] = None
|
||||
top_p: Optional[int] = None
|
||||
top_k: Optional[int] = None
|
||||
response_format: Optional[dict] = None
|
||||
tools: Optional[list] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
repeat_penalty: Optional[float] = None,
|
||||
functions: Optional[list] = None,
|
||||
logit_bias: Optional[dict] = None,
|
||||
max_completion_tokens: Optional[int] = None,
|
||||
max_tokens: Optional[int] = None,
|
||||
n: Optional[int] = None,
|
||||
presence_penalty: Optional[int] = None,
|
||||
stop: Optional[Union[str, list]] = None,
|
||||
temperature: Optional[int] = None,
|
||||
top_p: Optional[int] = None,
|
||||
top_k: Optional[int] = None,
|
||||
response_format: Optional[dict] = None,
|
||||
tools: Optional[list] = None,
|
||||
) -> None:
|
||||
locals_ = locals().copy()
|
||||
for key, value in locals_.items():
|
||||
if key != "self" and value is not None:
|
||||
setattr(self.__class__, key, value)
|
||||
|
||||
@property
|
||||
def custom_llm_provider(self) -> Optional[str]:
|
||||
return "lemonade"
|
||||
|
||||
@classmethod
|
||||
def get_config(cls):
|
||||
return super().get_config()
|
||||
|
||||
def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None):
|
||||
"""
|
||||
Get available models from Lemonade API.
|
||||
|
||||
This method queries the Lemonade /models endpoint to retrieve the list of available models.
|
||||
|
||||
Args:
|
||||
api_key: Optional API key (Lemonade doesn't require authentication)
|
||||
api_base: Optional API base URL (defaults to LEMONADE_API_BASE env var or http://localhost:8000)
|
||||
|
||||
Returns:
|
||||
List of model names prefixed with "lemonade/"
|
||||
"""
|
||||
api_base, api_key = self._get_openai_compatible_provider_info(
|
||||
api_base=api_base, api_key=api_key
|
||||
)
|
||||
|
||||
if api_base is None:
|
||||
raise ValueError(
|
||||
"LEMONADE_API_BASE is not set. Please set the environment variable to query Lemonade's /models endpoint."
|
||||
)
|
||||
|
||||
# Getting the list of models from lemonade
|
||||
try:
|
||||
response = litellm.module_level_client.get(
|
||||
url=f"{api_base}/models",
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
f"Failed to fetch models from Lemonade. Set Lemonade API Base via `LEMONADE_API_BASE` environment variable. Error: {e}"
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise ValueError(
|
||||
f"Failed to fetch models from Lemonade. Status code: {response.status_code}, Response: {response.text}"
|
||||
)
|
||||
|
||||
model_list = response.json().get("data", [])
|
||||
return ["lemonade/" + model["id"] for model in model_list]
|
||||
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: Optional[str], api_key: Optional[str]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
# lemonade is openai compatible, we just need to set this to custom_openai and have the api_base be lemonade's endpoint
|
||||
api_base = (
|
||||
api_base
|
||||
or get_secret_str("LEMONADE_API_BASE")
|
||||
or "http://localhost:8000/api/v1"
|
||||
) # type: ignore
|
||||
# Lemonade doesn't check the key
|
||||
key = "lemonade"
|
||||
return api_base, key
|
||||
|
||||
|
||||
def transform_response(
|
||||
self,
|
||||
model: str,
|
||||
raw_response: httpx.Response,
|
||||
model_response: ModelResponse,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
request_data: dict,
|
||||
messages: List[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
encoding: Any,
|
||||
api_key: Optional[str] = None,
|
||||
json_mode: Optional[bool] = None,
|
||||
) -> ModelResponse:
|
||||
model_response = super().transform_response(
|
||||
model=model,
|
||||
model_response=model_response,
|
||||
raw_response=raw_response,
|
||||
messages=messages,
|
||||
logging_obj=logging_obj,
|
||||
request_data=request_data,
|
||||
encoding=encoding,
|
||||
optional_params=optional_params,
|
||||
json_mode=json_mode,
|
||||
litellm_params=litellm_params,
|
||||
api_key=api_key,
|
||||
)
|
||||
|
||||
# Storing lemonade in the model response for easier cost calculation later
|
||||
setattr(model_response, "model", "lemonade/" + model)
|
||||
|
||||
return model_response
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Cost calculation for Lemonade LLM provider.
|
||||
|
||||
Since Lemonade is a local/self-hosted service, all costs default to 0.
|
||||
This prevents cost calculation errors when using models not in model_prices_and_context_window.json
|
||||
"""
|
||||
from typing import Tuple
|
||||
|
||||
from litellm.types.utils import Usage
|
||||
|
||||
|
||||
def cost_per_token(
|
||||
model: str,
|
||||
usage: Usage,
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
Calculate cost per token for Lemonade models.
|
||||
|
||||
Since Lemonade is a local/self-hosted deployment, there are no per-token costs.
|
||||
This function returns (0.0, 0.0) for all models to allow cost tracking to work
|
||||
without errors for any Lemonade model, regardless of whether it's in the
|
||||
model_prices_and_context_window.json file.
|
||||
|
||||
Args:
|
||||
model: The model name (with or without "lemonade/" prefix)
|
||||
usage: Usage object containing token counts
|
||||
|
||||
Returns:
|
||||
Tuple of (prompt_cost, completion_cost) - always (0.0, 0.0) for Lemonade
|
||||
"""
|
||||
# Lemonade is self-hosted/local, so cost is always 0
|
||||
prompt_cost = 0.0
|
||||
completion_cost = 0.0
|
||||
|
||||
return prompt_cost, completion_cost
|
||||
@@ -149,6 +149,7 @@ from .llms.bedrock.chat import BedrockConverseLLM, BedrockLLM
|
||||
from .llms.bedrock.embed.embedding import BedrockEmbedding
|
||||
from .llms.bedrock.image.image_handler import BedrockImageGeneration
|
||||
from .llms.bytez.chat.transformation import BytezChatConfig
|
||||
from .llms.lemonade.chat.transformation import LemonadeChatConfig
|
||||
from .llms.codestral.completion.handler import CodestralTextCompletion
|
||||
from .llms.cohere.embed import handler as cohere_embed
|
||||
from .llms.custom_httpx.aiohttp_handler import BaseLLMAIOHTTPHandler
|
||||
@@ -267,6 +268,7 @@ bytez_transformation = BytezChatConfig()
|
||||
heroku_transformation = HerokuChatConfig()
|
||||
oci_transformation = OCIChatConfig()
|
||||
ovhcloud_transformation = OVHCloudChatConfig()
|
||||
lemonade_transformation = LemonadeChatConfig()
|
||||
####### COMPLETION ENDPOINTS ################
|
||||
|
||||
|
||||
@@ -3545,6 +3547,35 @@ def completion( # type: ignore # noqa: PLR0915
|
||||
)
|
||||
|
||||
pass
|
||||
elif custom_llm_provider == "lemonade":
|
||||
api_key = (
|
||||
api_key
|
||||
or litellm.lemonade_key
|
||||
or get_secret_str("LEMONADE_API_KEY")
|
||||
or litellm.api_key
|
||||
)
|
||||
|
||||
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, # type: ignore
|
||||
client=client,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
encoding=encoding,
|
||||
stream=stream,
|
||||
provider_config=lemonade_transformation,
|
||||
)
|
||||
|
||||
pass
|
||||
|
||||
|
||||
elif custom_llm_provider == "ovhcloud" or model in litellm.ovhcloud_models:
|
||||
api_key = (
|
||||
|
||||
@@ -13256,6 +13256,18 @@
|
||||
],
|
||||
"supports_tool_choice": false
|
||||
},
|
||||
"lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF": {
|
||||
"input_cost_per_token": 0,
|
||||
"litellm_provider": "lemonade",
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 0,
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"groq/deepseek-r1-distill-llama-70b": {
|
||||
"input_cost_per_token": 7.5e-07,
|
||||
"litellm_provider": "groq",
|
||||
|
||||
@@ -2428,6 +2428,7 @@ class LlmProviders(str, Enum):
|
||||
DOTPROMPT = "dotprompt"
|
||||
WANDB = "wandb"
|
||||
OVHCLOUD = "ovhcloud"
|
||||
LEMONADE = "lemonade"
|
||||
|
||||
|
||||
# Create a set of all provider values for quick lookup
|
||||
|
||||
@@ -7317,6 +7317,8 @@ class ProviderConfigManager:
|
||||
)
|
||||
|
||||
return VLLMModelInfo()
|
||||
elif LlmProviders.LEMONADE == provider:
|
||||
return litellm.LemonadeChatConfig()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -13256,6 +13256,18 @@
|
||||
],
|
||||
"supports_tool_choice": false
|
||||
},
|
||||
"lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF": {
|
||||
"input_cost_per_token": 0,
|
||||
"litellm_provider": "lemonade",
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 32768,
|
||||
"max_output_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 0,
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"groq/deepseek-r1-distill-llama-70b": {
|
||||
"input_cost_per_token": 7.5e-07,
|
||||
"litellm_provider": "groq",
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../../../../..")
|
||||
) # Adds the parent directory to the system path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from litellm.llms.lemonade.chat.transformation import LemonadeChatConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
import httpx
|
||||
|
||||
|
||||
def test_lemonade_config_initialization():
|
||||
"""Test that LemonadeChatConfig can be initialized with various parameters"""
|
||||
config = LemonadeChatConfig(
|
||||
temperature=0.7,
|
||||
max_tokens=100,
|
||||
top_p=0.9,
|
||||
top_k=50,
|
||||
repeat_penalty=1.1
|
||||
)
|
||||
|
||||
assert config.custom_llm_provider == "lemonade"
|
||||
assert config.temperature == 0.7
|
||||
assert config.max_tokens == 100
|
||||
assert config.top_p == 0.9
|
||||
assert config.top_k == 50
|
||||
assert config.repeat_penalty == 1.1
|
||||
|
||||
|
||||
def test_get_openai_compatible_provider_info():
|
||||
"""Test the provider info method returns correct API base and key"""
|
||||
config = LemonadeChatConfig()
|
||||
|
||||
api_base, key = config._get_openai_compatible_provider_info(
|
||||
api_base=None,
|
||||
api_key=None
|
||||
)
|
||||
|
||||
assert api_base == "http://localhost:8000/api/v1"
|
||||
assert key == "lemonade"
|
||||
|
||||
|
||||
def test_get_openai_compatible_provider_info_with_custom_base():
|
||||
"""Test the provider info method with custom API base"""
|
||||
config = LemonadeChatConfig()
|
||||
|
||||
custom_api_base = "https://custom.lemonade.ai/v1"
|
||||
api_base, key = config._get_openai_compatible_provider_info(
|
||||
api_base=custom_api_base,
|
||||
api_key=None
|
||||
)
|
||||
|
||||
assert api_base == custom_api_base
|
||||
assert key == "lemonade"
|
||||
|
||||
|
||||
def test_transform_response():
|
||||
"""Test the response transformation adds lemonade prefix to model name"""
|
||||
config = LemonadeChatConfig()
|
||||
|
||||
# Mock raw response
|
||||
raw_response = MagicMock()
|
||||
raw_response.status_code = 200
|
||||
raw_response.headers = {}
|
||||
|
||||
# Create a model response
|
||||
model_response = ModelResponse()
|
||||
|
||||
# Mock the parent class transform_response method
|
||||
with patch.object(config.__class__.__bases__[0], 'transform_response') as mock_parent:
|
||||
mock_parent.return_value = model_response
|
||||
|
||||
result = config.transform_response(
|
||||
model="test-model",
|
||||
raw_response=raw_response,
|
||||
model_response=model_response,
|
||||
logging_obj=MagicMock(),
|
||||
request_data={},
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
api_key="test-key",
|
||||
json_mode=False,
|
||||
)
|
||||
|
||||
# Check that the model name is prefixed with "lemonade/"
|
||||
assert hasattr(result, 'model')
|
||||
assert result.model == "lemonade/test-model"
|
||||
|
||||
|
||||
def test_config_get_config():
|
||||
"""Test that get_config method returns the configuration"""
|
||||
config_dict = LemonadeChatConfig.get_config()
|
||||
assert isinstance(config_dict, dict)
|
||||
|
||||
|
||||
def test_response_format_support():
|
||||
"""Test that response_format parameter is supported"""
|
||||
response_format = {
|
||||
"type": "json_object"
|
||||
}
|
||||
|
||||
config = LemonadeChatConfig(response_format=response_format)
|
||||
assert config.response_format == response_format
|
||||
|
||||
|
||||
def test_tools_support():
|
||||
"""Test that tools parameter is supported"""
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather information"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
config = LemonadeChatConfig(tools=tools)
|
||||
assert config.tools == tools
|
||||
|
||||
|
||||
def test_functions_support():
|
||||
"""Test that functions parameter is supported"""
|
||||
functions = [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get weather information",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
config = LemonadeChatConfig(functions=functions)
|
||||
assert config.functions == functions
|
||||
|
||||
|
||||
def test_stop_parameter_support():
|
||||
"""Test that stop parameter supports both string and list"""
|
||||
# Test with string
|
||||
config1 = LemonadeChatConfig(stop="STOP")
|
||||
assert config1.stop == "STOP"
|
||||
|
||||
# Test with list
|
||||
config2 = LemonadeChatConfig(stop=["STOP", "END"])
|
||||
assert config2.stop == ["STOP", "END"]
|
||||
|
||||
|
||||
def test_logit_bias_support():
|
||||
"""Test that logit_bias parameter is supported"""
|
||||
logit_bias = {"50256": -100}
|
||||
|
||||
config = LemonadeChatConfig(logit_bias=logit_bias)
|
||||
assert config.logit_bias == logit_bias
|
||||
|
||||
|
||||
def test_presence_penalty_support():
|
||||
"""Test that presence_penalty parameter is supported"""
|
||||
config = LemonadeChatConfig(presence_penalty=0.5)
|
||||
assert config.presence_penalty == 0.5
|
||||
|
||||
|
||||
def test_n_parameter_support():
|
||||
"""Test that n parameter (number of completions) is supported"""
|
||||
config = LemonadeChatConfig(n=3)
|
||||
assert config.n == 3
|
||||
|
||||
|
||||
def test_max_completion_tokens_support():
|
||||
"""Test that max_completion_tokens parameter is supported"""
|
||||
config = LemonadeChatConfig(max_completion_tokens=150)
|
||||
assert config.max_completion_tokens == 150
|
||||
Reference in New Issue
Block a user