Merge remote-tracking branch 'origin/main' into fix_vertex_expired_tokens
This commit is contained in:
commit
58bbecffb7
@ -11,7 +11,7 @@ WORKDIR /app
|
||||
# Install build dependencies
|
||||
USER root
|
||||
RUN apk add --no-cache build-base bash \
|
||||
&& pip install --no-cache-dir --upgrade pip build
|
||||
&& pip install --no-cache-dir --upgrade pip build
|
||||
|
||||
# Copy project files
|
||||
COPY . .
|
||||
@ -21,8 +21,8 @@ RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
|
||||
|
||||
# Build package and wheel dependencies
|
||||
RUN rm -rf dist/* && python -m build && \
|
||||
pip install dist/*.whl && \
|
||||
pip wheel --no-cache-dir --wheel-dir=/wheels/ -r requirements.txt
|
||||
pip install dist/*.whl && \
|
||||
pip wheel --no-cache-dir --wheel-dir=/wheels/ -r requirements.txt
|
||||
|
||||
# -----------------
|
||||
# Runtime Stage
|
||||
@ -33,9 +33,10 @@ WORKDIR /app
|
||||
# Install runtime dependencies
|
||||
USER root
|
||||
RUN apk upgrade --no-cache && \
|
||||
apk add --no-cache bash libstdc++ ca-certificates openssl
|
||||
apk add --no-cache bash libstdc++ ca-certificates openssl
|
||||
|
||||
# Copy only necessary artifacts from builder stage for runtime
|
||||
COPY . .
|
||||
COPY --from=builder /app/docker/entrypoint.sh /app/docker/prod_entrypoint.sh /app/docker/
|
||||
COPY --from=builder /app/schema.prisma /app/schema.prisma
|
||||
COPY --from=builder /app/dist/*.whl .
|
||||
@ -43,16 +44,16 @@ COPY --from=builder /wheels/ /wheels/
|
||||
|
||||
# Install package from wheel and dependencies
|
||||
RUN pip install *.whl /wheels/* --no-index --find-links=/wheels/ \
|
||||
&& rm -f *.whl \
|
||||
&& rm -rf /wheels
|
||||
&& rm -f *.whl \
|
||||
&& rm -rf /wheels
|
||||
|
||||
# Install semantic_router without dependencies
|
||||
RUN pip install semantic_router --no-deps
|
||||
|
||||
# Ensure correct JWT library is used (pyjwt not jwt)
|
||||
RUN pip uninstall jwt -y && \
|
||||
pip uninstall PyJWT -y && \
|
||||
pip install PyJWT==2.9.0 --no-cache-dir
|
||||
pip uninstall PyJWT -y && \
|
||||
pip install PyJWT==2.9.0 --no-cache-dir
|
||||
|
||||
# --- Prisma Handling for Non-Root User ---
|
||||
# Set Prisma cache directories
|
||||
@ -61,29 +62,29 @@ ENV NPM_CONFIG_CACHE=/.npm
|
||||
|
||||
# Install prisma and make entrypoints executable
|
||||
RUN pip install --no-cache-dir prisma && \
|
||||
chmod +x docker/entrypoint.sh && \
|
||||
chmod +x docker/prod_entrypoint.sh
|
||||
chmod +x docker/entrypoint.sh && \
|
||||
chmod +x docker/prod_entrypoint.sh
|
||||
|
||||
# Create directories and set permissions for non-root user
|
||||
RUN mkdir -p /nonexistent /.npm && \
|
||||
chown -R nobody:nogroup /app && \
|
||||
chown -R nobody:nogroup /nonexistent /.npm && \
|
||||
PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \
|
||||
chown -R nobody:nogroup $PRISMA_PATH
|
||||
chown -R nobody:nogroup /app && \
|
||||
chown -R nobody:nogroup /nonexistent /.npm && \
|
||||
PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \
|
||||
chown -R nobody:nogroup $PRISMA_PATH
|
||||
|
||||
# --- OpenShift Compatibility: Apply Red Hat recommended pattern ---
|
||||
# Get paths for directories that need write access at runtime
|
||||
RUN PRISMA_PATH=$(python -c "import os, prisma; print(os.path.dirname(prisma.__file__))") && \
|
||||
LITELLM_PROXY_EXTRAS_PATH=$(python -c "import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))" 2>/dev/null || echo "") && \
|
||||
# Set group ownership to 0 (root group) for OpenShift compatibility && \
|
||||
chgrp -R 0 $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chgrp -R 0 $LITELLM_PROXY_EXTRAS_PATH || true && \
|
||||
# Mirror owner permissions to group (g=u) as recommended by Red Hat && \
|
||||
chmod -R g=u $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g=u $LITELLM_PROXY_EXTRAS_PATH || true && \
|
||||
# Ensure directories are writable by group && \
|
||||
chmod -R g+w $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w $LITELLM_PROXY_EXTRAS_PATH || true
|
||||
LITELLM_PROXY_EXTRAS_PATH=$(python -c "import os, litellm_proxy_extras; print(os.path.dirname(litellm_proxy_extras.__file__))" 2>/dev/null || echo "") && \
|
||||
# Set group ownership to 0 (root group) for OpenShift compatibility && \
|
||||
chgrp -R 0 $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chgrp -R 0 $LITELLM_PROXY_EXTRAS_PATH || true && \
|
||||
# Mirror owner permissions to group (g=u) as recommended by Red Hat && \
|
||||
chmod -R g=u $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g=u $LITELLM_PROXY_EXTRAS_PATH || true && \
|
||||
# Ensure directories are writable by group && \
|
||||
chmod -R g+w $PRISMA_PATH && \
|
||||
[ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w $LITELLM_PROXY_EXTRAS_PATH || true
|
||||
|
||||
# Switch to non-root user
|
||||
USER nobody
|
||||
@ -100,4 +101,4 @@ ENTRYPOINT ["/app/docker/prod_entrypoint.sh"]
|
||||
|
||||
# Append "--detailed_debug" to the end of CMD to view detailed debug logs
|
||||
# CMD ["--port", "4000", "--detailed_debug"]
|
||||
CMD ["--port", "4000"]
|
||||
CMD ["--port", "4000"]
|
||||
|
||||
@ -163,6 +163,14 @@ os.environ["OPENAI_BASE_URL"] = "https://your_host/v1" # OPTIONAL
|
||||
|
||||
| Model Name | Function Call |
|
||||
|-----------------------|-----------------------------------------------------------------|
|
||||
| gpt-5 | `response = completion(model="gpt-5", messages=messages)` |
|
||||
| gpt-5-mini | `response = completion(model="gpt-5-mini", messages=messages)` |
|
||||
| gpt-5-nano | `response = completion(model="gpt-5-nano", messages=messages)` |
|
||||
| gpt-5-chat | `response = completion(model="gpt-5-chat", messages=messages)` |
|
||||
| gpt-5-chat-latest | `response = completion(model="gpt-5-chat-latest", messages=messages)` |
|
||||
| gpt-5-2025-08-07 | `response = completion(model="gpt-5-2025-08-07", messages=messages)` |
|
||||
| gpt-5-mini-2025-08-07 | `response = completion(model="gpt-5-mini-2025-08-07", messages=messages)` |
|
||||
| gpt-5-nano-2025-08-07 | `response = completion(model="gpt-5-nano-2025-08-07", messages=messages)` |
|
||||
| gpt-4.1 | `response = completion(model="gpt-4.1", messages=messages)` |
|
||||
| gpt-4.1-mini | `response = completion(model="gpt-4.1-mini", messages=messages)` |
|
||||
| gpt-4.1-nano | `response = completion(model="gpt-4.1-nano", messages=messages)` |
|
||||
|
||||
@ -12,7 +12,7 @@ import TabItem from '@theme/TabItem';
|
||||
| Provider | [Microsoft Presidio](https://github.com/microsoft/presidio/) |
|
||||
| Supported Entity Types | All Presidio Entity Types |
|
||||
| Supported Actions | `MASK`, `BLOCK` |
|
||||
| Supported Modes | `pre_call`, `during_call`, `post_call`, `logging_only` |
|
||||
| Supported Modes | `pre_call`, `during_call`, `post_call`, `logging_only`, `pre_mcp_call` |
|
||||
| Language Support | Configurable via `presidio_language` parameter (supports multiple languages including English, Spanish, German, etc.) |
|
||||
|
||||
## Deployment options
|
||||
@ -239,7 +239,7 @@ guardrails:
|
||||
- guardrail_name: "presidio-mask-guard"
|
||||
litellm_params:
|
||||
guardrail: presidio
|
||||
mode: "pre_call"
|
||||
mode: "pre_mcp_call" # Use this mode for MCP requests
|
||||
pii_entities_config:
|
||||
CREDIT_CARD: "MASK" # Will mask credit card numbers
|
||||
EMAIL_ADDRESS: "MASK" # Will mask email addresses
|
||||
@ -247,7 +247,7 @@ guardrails:
|
||||
- guardrail_name: "presidio-block-guard"
|
||||
litellm_params:
|
||||
guardrail: presidio
|
||||
mode: "pre_call"
|
||||
mode: "pre_call" # Use this mode for regular LLM requests
|
||||
pii_entities_config:
|
||||
CREDIT_CARD: "BLOCK" # Will block requests containing credit card numbers
|
||||
```
|
||||
@ -338,6 +338,52 @@ The exception includes the entity type that was blocked (`CREDIT_CARD` in this c
|
||||
|
||||
## Advanced
|
||||
|
||||
### Supported Modes
|
||||
|
||||
The Presidio guardrail supports the following modes:
|
||||
|
||||
- `pre_call`: Run **before** LLM call, on **input**
|
||||
- `post_call`: Run **after** LLM call, on **input & output**
|
||||
- `logging_only`: Run **after** LLM call, only apply PII Masking before logging to Langfuse, etc. Not on the actual llm api request / response
|
||||
- `pre_mcp_call`: Run **before** MCP call, on **input**. Use this mode when you want to apply PII masking/blocking for MCP requests
|
||||
|
||||
### MCP Usage Example
|
||||
|
||||
Here's how to use Presidio guardrails with MCP:
|
||||
|
||||
```yaml title="MCP Configuration Example" showLineNumbers
|
||||
guardrails:
|
||||
- guardrail_name: "presidio-mcp-guard"
|
||||
litellm_params:
|
||||
guardrail: presidio
|
||||
mode: "pre_mcp_call"
|
||||
pii_entities_config:
|
||||
CREDIT_CARD: "MASK" # Will mask credit card numbers
|
||||
EMAIL_ADDRESS: "BLOCK" # Will block email addresses
|
||||
PHONE_NUMBER: "MASK" # Will mask phone numbers
|
||||
MEDICAL_LICENSE: "BLOCK" # Will block medical license numbers
|
||||
default_on: true
|
||||
```
|
||||
|
||||
Test the MCP guardrail with a request:
|
||||
|
||||
```shell title="Test MCP Guardrail" showLineNumbers
|
||||
curl http://localhost:4000/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer sk-1234" \
|
||||
-d '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{"role": "user", "content": "My credit card is 4111-1111-1111-1111 and my medical license is ABC123"}
|
||||
],
|
||||
"guardrails": ["presidio-mcp-guard"]
|
||||
}'
|
||||
```
|
||||
|
||||
The request will be processed as follows:
|
||||
1. Credit card number will be masked (e.g., replaced with `<CREDIT_CARD>`)
|
||||
2. If a medical license is detected, the request will be blocked with a `BlockedPiiEntityError`
|
||||
|
||||
### Set `language` per request
|
||||
|
||||
The Presidio API [supports passing the `language` param](https://microsoft.github.io/presidio/api-docs/api-docs.html#tag/Analyzer/paths/~1analyze/post). Here is how to set the `language` per request
|
||||
|
||||
BIN
litellm-proxy-extras/dist/litellm_proxy_extras-0.2.16-py3-none-any.whl
vendored
Normal file
BIN
litellm-proxy-extras/dist/litellm_proxy_extras-0.2.16-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
litellm-proxy-extras/dist/litellm_proxy_extras-0.2.16.tar.gz
vendored
Normal file
BIN
litellm-proxy-extras/dist/litellm_proxy_extras-0.2.16.tar.gz
vendored
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "litellm-proxy-extras"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package."
|
||||
authors = ["BerriAI"]
|
||||
readme = "README.md"
|
||||
@ -22,7 +22,7 @@ requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
version_files = [
|
||||
"pyproject.toml:version",
|
||||
"../requirements.txt:litellm-proxy-extras==",
|
||||
|
||||
@ -357,7 +357,7 @@ disable_copilot_system_to_assistant: bool = (
|
||||
)
|
||||
public_model_groups: Optional[List[str]] = None
|
||||
public_model_groups_links: Dict[str, str] = {}
|
||||
#### REQUEST PRIORITIZATION #####
|
||||
#### REQUEST PRIORITIZATION ######
|
||||
priority_reservation: Optional[Dict[str, float]] = None
|
||||
|
||||
|
||||
@ -533,6 +533,7 @@ morph_models: List = []
|
||||
lambda_ai_models: List = []
|
||||
hyperbolic_models: List = []
|
||||
recraft_models: List = []
|
||||
oci_models: List = []
|
||||
|
||||
|
||||
def is_bedrock_pricing_only_model(key: str) -> bool:
|
||||
@ -722,6 +723,8 @@ def add_known_models():
|
||||
hyperbolic_models.append(key)
|
||||
elif value.get("litellm_provider") == "recraft":
|
||||
recraft_models.append(key)
|
||||
elif value.get("litellm_provider") == "oci":
|
||||
oci_models.append(key)
|
||||
|
||||
|
||||
add_known_models()
|
||||
@ -810,6 +813,7 @@ model_list = (
|
||||
+ morph_models
|
||||
+ lambda_ai_models
|
||||
+ recraft_models
|
||||
+ oci_models
|
||||
)
|
||||
|
||||
model_list_set = set(model_list)
|
||||
@ -883,6 +887,7 @@ models_by_provider: dict = {
|
||||
"lambda_ai": lambda_ai_models,
|
||||
"hyperbolic": hyperbolic_models,
|
||||
"recraft": recraft_models,
|
||||
"oci": oci_models,
|
||||
}
|
||||
|
||||
# mapping for those models which have larger equivalents
|
||||
@ -1140,6 +1145,9 @@ openaiOSeriesConfig = OpenAIOSeriesConfig()
|
||||
from .llms.openai.chat.gpt_transformation import (
|
||||
OpenAIGPTConfig,
|
||||
)
|
||||
from .llms.openai.chat.gpt_5_transformation import (
|
||||
OpenAIGPT5Config,
|
||||
)
|
||||
from .llms.openai.transcriptions.whisper_transformation import (
|
||||
OpenAIWhisperAudioTranscriptionConfig,
|
||||
)
|
||||
@ -1153,6 +1161,7 @@ from .llms.openai.chat.gpt_audio_transformation import (
|
||||
)
|
||||
|
||||
openAIGPTAudioConfig = OpenAIGPTAudioConfig()
|
||||
openAIGPT5Config = OpenAIGPT5Config()
|
||||
|
||||
from .llms.nvidia_nim.chat.transformation import NvidiaNimConfig
|
||||
from .llms.nvidia_nim.embed import NvidiaNimEmbeddingConfig
|
||||
|
||||
@ -9,6 +9,7 @@ Docs - https://docs.mistral.ai/api/
|
||||
from typing import Any, Coroutine, List, Literal, Optional, Tuple, Union, cast, overload
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
handle_messages_with_content_list_to_str_conversion,
|
||||
@ -147,7 +148,8 @@ class MistralConfig(OpenAIGPTConfig):
|
||||
if param == "max_completion_tokens": # max_completion_tokens should take priority
|
||||
optional_params["max_tokens"] = value
|
||||
if param == "tools":
|
||||
optional_params["tools"] = value
|
||||
# Clean tools to remove problematic schema fields for Mistral API
|
||||
optional_params["tools"] = self._clean_tool_schema_for_mistral(value)
|
||||
if param == "stream" and value is True:
|
||||
optional_params["stream"] = value
|
||||
if param == "temperature":
|
||||
@ -195,7 +197,8 @@ class MistralConfig(OpenAIGPTConfig):
|
||||
@overload
|
||||
def _transform_messages(
|
||||
self, messages: List[AllMessageValues], model: str, is_async: Literal[True]
|
||||
) -> Coroutine[Any, Any, List[AllMessageValues]]: ...
|
||||
) -> Coroutine[Any, Any, List[AllMessageValues]]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def _transform_messages(
|
||||
@ -203,7 +206,8 @@ class MistralConfig(OpenAIGPTConfig):
|
||||
messages: List[AllMessageValues],
|
||||
model: str,
|
||||
is_async: Literal[False] = False,
|
||||
) -> List[AllMessageValues]: ...
|
||||
) -> List[AllMessageValues]:
|
||||
...
|
||||
|
||||
def _transform_messages(
|
||||
self, messages: List[AllMessageValues], model: str, is_async: bool = False
|
||||
@ -286,6 +290,38 @@ class MistralConfig(OpenAIGPTConfig):
|
||||
optional_params.pop("_add_reasoning_prompt", None)
|
||||
return messages
|
||||
|
||||
@classmethod
|
||||
def _clean_tool_schema_for_mistral(cls, tools: list) -> list:
|
||||
"""
|
||||
Clean tool schemas to remove fields that cause issues with Mistral API.
|
||||
|
||||
Removes:
|
||||
- $id and $schema fields (cause grammar validation errors)
|
||||
- additionalProperties=False (causes OpenAI API schema errors)
|
||||
- strict field (not supported by Mistral)
|
||||
|
||||
Args:
|
||||
tools: List of tool definitions
|
||||
max_depth: Maximum recursion depth for schema cleaning (default: 10)
|
||||
|
||||
Returns:
|
||||
Cleaned tools list
|
||||
"""
|
||||
if not tools:
|
||||
return tools
|
||||
|
||||
import copy
|
||||
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
from litellm.utils import _remove_json_schema_refs
|
||||
|
||||
cleaned_tools = copy.deepcopy(tools)
|
||||
|
||||
# Apply all cleaning functions with max_depth protection
|
||||
cleaned_tools = _remove_json_schema_refs(cleaned_tools, max_depth=DEFAULT_MAX_RECURSE_DEPTH)
|
||||
|
||||
return cleaned_tools
|
||||
|
||||
@classmethod
|
||||
def _handle_name_in_message(cls, message: AllMessageValues) -> AllMessageValues:
|
||||
"""
|
||||
|
||||
58
litellm/llms/openai/chat/gpt_5_transformation.py
Normal file
58
litellm/llms/openai/chat/gpt_5_transformation.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""Support for OpenAI gpt-5 model family."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import litellm
|
||||
|
||||
from .gpt_transformation import OpenAIGPTConfig
|
||||
|
||||
|
||||
class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
"""Configuration for gpt-5 models.
|
||||
|
||||
Handles OpenAI API quirks for the gpt-5 series like:
|
||||
|
||||
- Mapping ``max_tokens`` -> ``max_completion_tokens``.
|
||||
- Dropping unsupported ``temperature`` values when requested.
|
||||
"""
|
||||
@classmethod
|
||||
def is_model_gpt_5_model(cls, model: str) -> bool:
|
||||
return "gpt-5" in model
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
################################################################
|
||||
# max_tokens is not supported for gpt-5 models on OpenAI API
|
||||
# Relevant issue: https://github.com/BerriAI/litellm/issues/13381
|
||||
################################################################
|
||||
if "max_tokens" in non_default_params:
|
||||
optional_params["max_completion_tokens"] = non_default_params.pop(
|
||||
"max_tokens"
|
||||
)
|
||||
|
||||
if "temperature" in non_default_params:
|
||||
temperature_value: Optional[float] = non_default_params.pop("temperature")
|
||||
if temperature_value is not None:
|
||||
if temperature_value == 1:
|
||||
optional_params["temperature"] = temperature_value
|
||||
elif litellm.drop_params or drop_params:
|
||||
pass
|
||||
else:
|
||||
raise litellm.utils.UnsupportedParamsError(
|
||||
message=(
|
||||
"gpt-5 models don't support temperature={}. Only temperature=1 is supported. To drop unsupported params set `litellm.drop_params = True`"
|
||||
).format(temperature_value),
|
||||
status_code=400,
|
||||
)
|
||||
return super()._map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
@ -47,6 +47,7 @@ from litellm.utils import (
|
||||
|
||||
from ...types.llms.openai import *
|
||||
from ..base import BaseLLM
|
||||
from .chat.gpt_5_transformation import OpenAIGPT5Config
|
||||
from .chat.o_series_transformation import OpenAIOSeriesConfig
|
||||
from .common_utils import (
|
||||
BaseOpenAILLM,
|
||||
@ -55,6 +56,7 @@ from .common_utils import (
|
||||
)
|
||||
|
||||
openaiOSeriesConfig = OpenAIOSeriesConfig()
|
||||
openAIGPT5Config = OpenAIGPT5Config()
|
||||
|
||||
|
||||
class MistralEmbeddingConfig:
|
||||
@ -183,6 +185,8 @@ class OpenAIConfig(BaseConfig):
|
||||
"""
|
||||
if openaiOSeriesConfig.is_model_o_series_model(model=model):
|
||||
return openaiOSeriesConfig.get_supported_openai_params(model=model)
|
||||
elif openAIGPT5Config.is_model_gpt_5_model(model=model):
|
||||
return openAIGPT5Config.get_supported_openai_params(model=model)
|
||||
elif litellm.openAIGPTAudioConfig.is_model_gpt_audio_model(model=model):
|
||||
return litellm.openAIGPTAudioConfig.get_supported_openai_params(model=model)
|
||||
else:
|
||||
@ -217,6 +221,13 @@ class OpenAIConfig(BaseConfig):
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
elif openAIGPT5Config.is_model_gpt_5_model(model=model):
|
||||
return openAIGPT5Config.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
elif litellm.openAIGPTAudioConfig.is_model_gpt_audio_model(model=model):
|
||||
return litellm.openAIGPTAudioConfig.map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
|
||||
@ -612,6 +612,263 @@
|
||||
"search_context_size_high": 0.03
|
||||
}
|
||||
},
|
||||
"gpt-5": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-mini": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-nano": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-chat": {
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 1047576,
|
||||
"max_output_tokens": 32768,
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 2e-05,
|
||||
"input_cost_per_token_batches": 2.5e-06,
|
||||
"output_cost_per_token_batches": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-06,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"gpt-5-chat-latest": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-mini-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-nano-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"codex-mini-latest": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
@ -2007,6 +2264,263 @@
|
||||
"/v1/audio/speech"
|
||||
]
|
||||
},
|
||||
"azure/gpt-5": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-mini": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-mini-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-nano-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-nano": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-chat": {
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 1047576,
|
||||
"max_output_tokens": 32768,
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 2e-05,
|
||||
"input_cost_per_token_batches": 2.5e-06,
|
||||
"output_cost_per_token_batches": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-06,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"azure/gpt-5-chat-latest": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-4o-mini-tts": {
|
||||
"mode": "audio_speech",
|
||||
"input_cost_per_token": 2.5e-06,
|
||||
@ -5771,6 +6285,32 @@
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-opus-4-1": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "anthropic",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-opus-4-1-20250805": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
@ -17693,5 +18233,126 @@
|
||||
"supports_vision": false,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": false
|
||||
},
|
||||
"oci/meta.llama-4-maverick-17b-128e-instruct-fp8": {
|
||||
"max_tokens": 512000,
|
||||
"max_input_tokens": 512000,
|
||||
"max_output_tokens": 4000,
|
||||
"input_cost_per_token": 7.2e-07,
|
||||
"output_cost_per_token": 7.2e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-4-scout-17b-16e-instruct": {
|
||||
"max_tokens": 192000,
|
||||
"max_input_tokens": 192000,
|
||||
"max_output_tokens": 4000,
|
||||
"input_cost_per_token": 7.2e-07,
|
||||
"output_cost_per_token": 7.2e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.3-70b-instruct": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 4000,
|
||||
"input_cost_per_token": 7.2e-07,
|
||||
"output_cost_per_token": 7.2e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.2-90b-vision-instruct": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 4000,
|
||||
"input_cost_per_token": 2.0e-06,
|
||||
"output_cost_per_token": 2.0e-06,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.1-405b-instruct": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 4000,
|
||||
"input_cost_per_token": 1.068e-05,
|
||||
"output_cost_per_token": 1.068e-05,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
|
||||
"oci/xai.grok-4": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 3.0e-06,
|
||||
"output_cost_per_token": 1.5e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 3.0e-06,
|
||||
"output_cost_per_token": 1.5e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-mini": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 3.0e-07,
|
||||
"output_cost_per_token": 5.0e-07,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-fast": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 5.0e-06,
|
||||
"output_cost_per_token": 2.5e-05,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-mini-fast": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 6.0e-07,
|
||||
"output_cost_per_token": 4.0e-06,
|
||||
"litellm_provider": "oci",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[19813,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-9e1803a09e9ae8da.js","498","static/chunks/498-ee02f9b58491d7a9.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","172","static/chunks/172-08ae62d50ce1f0e7.js","931","static/chunks/app/page-0a9a9f137522a76c.js"],"default",1]
|
||||
3:I[6691,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-3523e0e07cf314f6.js","683","static/chunks/683-07087d813e7eeb43.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","172","static/chunks/172-1c7afccd96ceca39.js","931","static/chunks/app/page-1d51309983956823.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[52829,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","418","static/chunks/app/model_hub/page-b26e0d313b582dbf.js"],"default",1]
|
||||
3:I[52829,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","418","static/chunks/app/model_hub/page-b26e0d313b582dbf.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[22775,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-9e1803a09e9ae8da.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","172","static/chunks/172-08ae62d50ce1f0e7.js","25","static/chunks/app/model_hub_table/page-d080c5775ebaf3a1.js"],"default",1]
|
||||
3:I[22775,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-3523e0e07cf314f6.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","172","static/chunks/172-1c7afccd96ceca39.js","25","static/chunks/app/model_hub_table/page-d080c5775ebaf3a1.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","416","static/chunks/416-ad6bd55a20a586bd.js","154","static/chunks/154-78c3416dcb61977f.js","461","static/chunks/app/onboarding/page-883c32e6b072b842.js"],"default",1]
|
||||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","416","static/chunks/416-ad6bd55a20a586bd.js","154","static/chunks/154-66d79df6143c694f.js","461","static/chunks/app/onboarding/page-7e4cd2bb92dbf9ce.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
@ -267,6 +267,106 @@ async def get_api_key_metadata(
|
||||
}
|
||||
|
||||
|
||||
def _build_where_conditions(
|
||||
*,
|
||||
entity_id_field: str,
|
||||
entity_id: Optional[Union[str, List[str]]],
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
model: Optional[str],
|
||||
api_key: Optional[str],
|
||||
exclude_entity_ids: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Build prisma where clause for daily activity queries."""
|
||||
where_conditions: Dict[str, Any] = {
|
||||
"date": {
|
||||
"gte": start_date,
|
||||
"lte": end_date,
|
||||
}
|
||||
}
|
||||
|
||||
if model:
|
||||
where_conditions["model"] = model
|
||||
if api_key:
|
||||
where_conditions["api_key"] = api_key
|
||||
|
||||
if entity_id is not None:
|
||||
if isinstance(entity_id, list):
|
||||
where_conditions[entity_id_field] = {"in": entity_id}
|
||||
else:
|
||||
where_conditions[entity_id_field] = {"equals": entity_id}
|
||||
|
||||
if exclude_entity_ids:
|
||||
current = where_conditions.get(entity_id_field, {})
|
||||
if isinstance(current, str):
|
||||
current = {"equals": current}
|
||||
current["not"] = {"in": exclude_entity_ids}
|
||||
where_conditions[entity_id_field] = current
|
||||
|
||||
return where_conditions
|
||||
|
||||
|
||||
async def _aggregate_spend_records(
|
||||
*,
|
||||
prisma_client: PrismaClient,
|
||||
records: List[Any],
|
||||
entity_id_field: Optional[str],
|
||||
entity_metadata_field: Optional[Dict[str, dict]],
|
||||
) -> Dict[str, Any]:
|
||||
"""Aggregate rows into DailySpendData list and total metrics."""
|
||||
api_keys: Set[str] = set()
|
||||
for record in records:
|
||||
if record.api_key:
|
||||
api_keys.add(record.api_key)
|
||||
|
||||
api_key_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
model_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
provider_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
if api_keys:
|
||||
api_key_metadata = await get_api_key_metadata(prisma_client, api_keys)
|
||||
|
||||
results: List[DailySpendData] = []
|
||||
total_metrics = SpendMetrics()
|
||||
grouped_data: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
for record in records:
|
||||
date_str = record.date
|
||||
if date_str not in grouped_data:
|
||||
grouped_data[date_str] = {
|
||||
"metrics": SpendMetrics(),
|
||||
"breakdown": BreakdownMetrics(),
|
||||
}
|
||||
|
||||
grouped_data[date_str]["metrics"] = update_metrics(
|
||||
grouped_data[date_str]["metrics"], record
|
||||
)
|
||||
|
||||
grouped_data[date_str]["breakdown"] = update_breakdown_metrics(
|
||||
grouped_data[date_str]["breakdown"],
|
||||
record,
|
||||
model_metadata,
|
||||
provider_metadata,
|
||||
api_key_metadata,
|
||||
entity_id_field=entity_id_field,
|
||||
entity_metadata_field=entity_metadata_field,
|
||||
)
|
||||
|
||||
total_metrics = update_metrics(total_metrics, record)
|
||||
|
||||
for date_str, data in grouped_data.items():
|
||||
results.append(
|
||||
DailySpendData(
|
||||
date=datetime.strptime(date_str, "%Y-%m-%d").date(),
|
||||
metrics=data["metrics"],
|
||||
breakdown=data["breakdown"],
|
||||
)
|
||||
)
|
||||
|
||||
results.sort(key=lambda x: x.date, reverse=True)
|
||||
|
||||
return {"results": results, "totals": total_metrics}
|
||||
|
||||
|
||||
async def get_daily_activity(
|
||||
prisma_client: Optional[PrismaClient],
|
||||
table_name: str,
|
||||
@ -296,27 +396,15 @@ async def get_daily_activity(
|
||||
)
|
||||
|
||||
try:
|
||||
# Build filter conditions
|
||||
where_conditions: Dict[str, Any] = {
|
||||
"date": {
|
||||
"gte": start_date,
|
||||
"lte": end_date,
|
||||
}
|
||||
}
|
||||
|
||||
if model:
|
||||
where_conditions["model"] = model
|
||||
if api_key:
|
||||
where_conditions["api_key"] = api_key
|
||||
if entity_id is not None:
|
||||
if isinstance(entity_id, list):
|
||||
where_conditions[entity_id_field] = {"in": entity_id}
|
||||
else:
|
||||
where_conditions[entity_id_field] = entity_id
|
||||
if exclude_entity_ids:
|
||||
where_conditions.setdefault(entity_id_field, {})["not"] = {
|
||||
"in": exclude_entity_ids
|
||||
}
|
||||
where_conditions = _build_where_conditions(
|
||||
entity_id_field=entity_id_field,
|
||||
entity_id=entity_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
exclude_entity_ids=exclude_entity_ids,
|
||||
)
|
||||
|
||||
# Get total count for pagination
|
||||
total_count = await getattr(prisma_client.db, table_name).count(
|
||||
@ -333,87 +421,25 @@ async def get_daily_activity(
|
||||
take=page_size,
|
||||
)
|
||||
|
||||
# Get all unique API keys from the spend data
|
||||
api_keys = set()
|
||||
for record in daily_spend_data:
|
||||
if record.api_key:
|
||||
api_keys.add(record.api_key)
|
||||
|
||||
# Fetch key aliases in bulk
|
||||
api_key_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
model_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
provider_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
if api_keys:
|
||||
api_key_metadata = await get_api_key_metadata(prisma_client, api_keys)
|
||||
|
||||
# Process results
|
||||
results = []
|
||||
total_metrics = SpendMetrics()
|
||||
grouped_data: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
for record in daily_spend_data:
|
||||
date_str = record.date
|
||||
if date_str not in grouped_data:
|
||||
grouped_data[date_str] = {
|
||||
"metrics": SpendMetrics(),
|
||||
"breakdown": BreakdownMetrics(),
|
||||
}
|
||||
|
||||
# Update metrics
|
||||
grouped_data[date_str]["metrics"] = update_metrics(
|
||||
grouped_data[date_str]["metrics"], record
|
||||
)
|
||||
# Update breakdowns
|
||||
grouped_data[date_str]["breakdown"] = update_breakdown_metrics(
|
||||
grouped_data[date_str]["breakdown"],
|
||||
record,
|
||||
model_metadata,
|
||||
provider_metadata,
|
||||
api_key_metadata,
|
||||
entity_id_field=entity_id_field,
|
||||
entity_metadata_field=entity_metadata_field,
|
||||
)
|
||||
|
||||
# Update total metrics
|
||||
total_metrics.spend += record.spend
|
||||
total_metrics.prompt_tokens += record.prompt_tokens
|
||||
total_metrics.completion_tokens += record.completion_tokens
|
||||
total_metrics.total_tokens += (
|
||||
record.prompt_tokens + record.completion_tokens
|
||||
)
|
||||
total_metrics.cache_read_input_tokens += record.cache_read_input_tokens
|
||||
total_metrics.cache_creation_input_tokens += (
|
||||
record.cache_creation_input_tokens
|
||||
)
|
||||
total_metrics.api_requests += record.api_requests
|
||||
total_metrics.successful_requests += record.successful_requests
|
||||
total_metrics.failed_requests += record.failed_requests
|
||||
|
||||
# Convert grouped data to response format
|
||||
for date_str, data in grouped_data.items():
|
||||
results.append(
|
||||
DailySpendData(
|
||||
date=datetime.strptime(date_str, "%Y-%m-%d").date(),
|
||||
metrics=data["metrics"],
|
||||
breakdown=data["breakdown"],
|
||||
)
|
||||
)
|
||||
|
||||
# Sort results by date
|
||||
results.sort(key=lambda x: x.date, reverse=True)
|
||||
aggregated = await _aggregate_spend_records(
|
||||
prisma_client=prisma_client,
|
||||
records=daily_spend_data,
|
||||
entity_id_field=entity_id_field,
|
||||
entity_metadata_field=entity_metadata_field,
|
||||
)
|
||||
|
||||
return SpendAnalyticsPaginatedResponse(
|
||||
results=results,
|
||||
results=aggregated["results"],
|
||||
metadata=DailySpendMetadata(
|
||||
total_spend=total_metrics.spend,
|
||||
total_prompt_tokens=total_metrics.prompt_tokens,
|
||||
total_completion_tokens=total_metrics.completion_tokens,
|
||||
total_tokens=total_metrics.total_tokens,
|
||||
total_api_requests=total_metrics.api_requests,
|
||||
total_successful_requests=total_metrics.successful_requests,
|
||||
total_failed_requests=total_metrics.failed_requests,
|
||||
total_cache_read_input_tokens=total_metrics.cache_read_input_tokens,
|
||||
total_cache_creation_input_tokens=total_metrics.cache_creation_input_tokens,
|
||||
total_spend=aggregated["totals"].spend,
|
||||
total_prompt_tokens=aggregated["totals"].prompt_tokens,
|
||||
total_completion_tokens=aggregated["totals"].completion_tokens,
|
||||
total_tokens=aggregated["totals"].total_tokens,
|
||||
total_api_requests=aggregated["totals"].api_requests,
|
||||
total_successful_requests=aggregated["totals"].successful_requests,
|
||||
total_failed_requests=aggregated["totals"].failed_requests,
|
||||
total_cache_read_input_tokens=aggregated["totals"].cache_read_input_tokens,
|
||||
total_cache_creation_input_tokens=aggregated["totals"].cache_creation_input_tokens,
|
||||
page=page,
|
||||
total_pages=-(-total_count // page_size), # Ceiling division
|
||||
has_more=(page * page_size) < total_count,
|
||||
@ -426,3 +452,85 @@ async def get_daily_activity(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={"error": f"Failed to fetch analytics: {str(e)}"},
|
||||
)
|
||||
|
||||
|
||||
async def get_daily_activity_aggregated(
|
||||
prisma_client: Optional[PrismaClient],
|
||||
table_name: str,
|
||||
entity_id_field: str,
|
||||
entity_id: Optional[Union[str, List[str]]],
|
||||
entity_metadata_field: Optional[Dict[str, dict]],
|
||||
start_date: Optional[str],
|
||||
end_date: Optional[str],
|
||||
model: Optional[str],
|
||||
api_key: Optional[str],
|
||||
exclude_entity_ids: Optional[List[str]] = None,
|
||||
) -> SpendAnalyticsPaginatedResponse:
|
||||
"""Aggregated variant that returns the full result set (no pagination).
|
||||
|
||||
Matches the response model of the paginated endpoint so the UI does not need to transform.
|
||||
"""
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={"error": CommonProxyErrors.db_not_connected_error.value},
|
||||
)
|
||||
|
||||
if start_date is None or end_date is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={"error": "Please provide start_date and end_date"},
|
||||
)
|
||||
|
||||
try:
|
||||
where_conditions = _build_where_conditions(
|
||||
entity_id_field=entity_id_field,
|
||||
entity_id=entity_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
exclude_entity_ids=exclude_entity_ids,
|
||||
)
|
||||
|
||||
# Fetch all matching results (no pagination)
|
||||
daily_spend_data = await getattr(prisma_client.db, table_name).find_many(
|
||||
where=where_conditions,
|
||||
order=[
|
||||
{"date": "desc"},
|
||||
],
|
||||
)
|
||||
|
||||
aggregated = await _aggregate_spend_records(
|
||||
prisma_client=prisma_client,
|
||||
records=daily_spend_data,
|
||||
entity_id_field=entity_id_field,
|
||||
entity_metadata_field=entity_metadata_field,
|
||||
)
|
||||
|
||||
return SpendAnalyticsPaginatedResponse(
|
||||
results=aggregated["results"],
|
||||
metadata=DailySpendMetadata(
|
||||
total_spend=aggregated["totals"].spend,
|
||||
total_prompt_tokens=aggregated["totals"].prompt_tokens,
|
||||
total_completion_tokens=aggregated["totals"].completion_tokens,
|
||||
total_tokens=aggregated["totals"].total_tokens,
|
||||
total_api_requests=aggregated["totals"].api_requests,
|
||||
total_successful_requests=aggregated["totals"].successful_requests,
|
||||
total_failed_requests=aggregated["totals"].failed_requests,
|
||||
total_cache_read_input_tokens=aggregated["totals"].cache_read_input_tokens,
|
||||
total_cache_creation_input_tokens=aggregated["totals"].cache_creation_input_tokens,
|
||||
page=1,
|
||||
total_pages=1,
|
||||
has_more=False,
|
||||
),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.exception(
|
||||
f"Error fetching aggregated daily activity: {str(e)}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={"error": f"Failed to fetch analytics: {str(e)}"},
|
||||
)
|
||||
|
||||
@ -26,7 +26,10 @@ from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy._types import *
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.hooks.user_management_event_hooks import UserManagementEventHooks
|
||||
from litellm.proxy.management_endpoints.common_daily_activity import get_daily_activity
|
||||
from litellm.proxy.management_endpoints.common_daily_activity import (
|
||||
get_daily_activity,
|
||||
get_daily_activity_aggregated,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
generate_key_helper_fn,
|
||||
@ -35,13 +38,7 @@ from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
|
||||
from litellm.proxy.utils import handle_exception_on_proxy
|
||||
from litellm.types.proxy.management_endpoints.common_daily_activity import (
|
||||
BreakdownMetrics,
|
||||
KeyMetadata,
|
||||
KeyMetricWithMetadata,
|
||||
LiteLLM_DailyUserSpend,
|
||||
MetricWithMetadata,
|
||||
SpendAnalyticsPaginatedResponse,
|
||||
SpendMetrics,
|
||||
)
|
||||
from litellm.types.proxy.management_endpoints.internal_user_endpoints import (
|
||||
BulkUpdateUserRequest,
|
||||
@ -1784,71 +1781,7 @@ async def ui_view_users(
|
||||
raise HTTPException(status_code=500, detail=f"Error searching users: {str(e)}")
|
||||
|
||||
|
||||
def update_metrics(
|
||||
group_metrics: SpendMetrics, record: LiteLLM_DailyUserSpend
|
||||
) -> SpendMetrics:
|
||||
group_metrics.spend += record.spend
|
||||
group_metrics.prompt_tokens += record.prompt_tokens
|
||||
group_metrics.completion_tokens += record.completion_tokens
|
||||
group_metrics.cache_read_input_tokens += record.cache_read_input_tokens
|
||||
group_metrics.cache_creation_input_tokens += record.cache_creation_input_tokens
|
||||
group_metrics.total_tokens += record.prompt_tokens + record.completion_tokens
|
||||
group_metrics.api_requests += record.api_requests
|
||||
group_metrics.successful_requests += record.successful_requests
|
||||
group_metrics.failed_requests += record.failed_requests
|
||||
return group_metrics
|
||||
|
||||
|
||||
def update_breakdown_metrics(
|
||||
breakdown: BreakdownMetrics,
|
||||
record: LiteLLM_DailyUserSpend,
|
||||
model_metadata: Dict[str, Dict[str, Any]],
|
||||
provider_metadata: Dict[str, Dict[str, Any]],
|
||||
api_key_metadata: Dict[str, Dict[str, Any]],
|
||||
) -> BreakdownMetrics:
|
||||
"""Updates breakdown metrics for a single record using the existing update_metrics function"""
|
||||
|
||||
# Update model breakdown
|
||||
if record.model:
|
||||
if record.model not in breakdown.models:
|
||||
breakdown.models[record.model] = MetricWithMetadata(
|
||||
metrics=SpendMetrics(),
|
||||
metadata=model_metadata.get(
|
||||
record.model, {}
|
||||
), # Add any model-specific metadata here
|
||||
)
|
||||
breakdown.models[record.model].metrics = update_metrics(
|
||||
breakdown.models[record.model].metrics, record
|
||||
)
|
||||
|
||||
# Update provider breakdown
|
||||
provider = record.custom_llm_provider or "unknown"
|
||||
if provider not in breakdown.providers:
|
||||
breakdown.providers[provider] = MetricWithMetadata(
|
||||
metrics=SpendMetrics(),
|
||||
metadata=provider_metadata.get(
|
||||
provider, {}
|
||||
), # Add any provider-specific metadata here
|
||||
)
|
||||
breakdown.providers[provider].metrics = update_metrics(
|
||||
breakdown.providers[provider].metrics, record
|
||||
)
|
||||
|
||||
# Update api key breakdown
|
||||
if record.api_key not in breakdown.api_keys:
|
||||
breakdown.api_keys[record.api_key] = KeyMetricWithMetadata(
|
||||
metrics=SpendMetrics(),
|
||||
metadata=KeyMetadata(
|
||||
key_alias=api_key_metadata.get(record.api_key, {}).get(
|
||||
"key_alias", None
|
||||
)
|
||||
), # Add any api_key-specific metadata here
|
||||
)
|
||||
breakdown.api_keys[record.api_key].metrics = update_metrics(
|
||||
breakdown.api_keys[record.api_key].metrics, record
|
||||
)
|
||||
|
||||
return breakdown
|
||||
# Using shared metric helper implementations from common_daily_activity
|
||||
|
||||
|
||||
@router.get(
|
||||
@ -1857,6 +1790,7 @@ def update_breakdown_metrics(
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_model=SpendAnalyticsPaginatedResponse,
|
||||
)
|
||||
@management_endpoint_wrapper
|
||||
async def get_user_daily_activity(
|
||||
start_date: Optional[str] = fastapi.Query(
|
||||
default=None,
|
||||
@ -1939,3 +1873,74 @@ async def get_user_daily_activity(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={"error": f"Failed to fetch analytics: {str(e)}"},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/user/daily/activity/aggregated",
|
||||
tags=["Budget & Spend Tracking", "Internal User management"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_model=SpendAnalyticsPaginatedResponse,
|
||||
)
|
||||
@management_endpoint_wrapper
|
||||
async def get_user_daily_activity_aggregated(
|
||||
start_date: Optional[str] = fastapi.Query(
|
||||
default=None,
|
||||
description="Start date in YYYY-MM-DD format",
|
||||
),
|
||||
end_date: Optional[str] = fastapi.Query(
|
||||
default=None,
|
||||
description="End date in YYYY-MM-DD format",
|
||||
),
|
||||
model: Optional[str] = fastapi.Query(
|
||||
default=None,
|
||||
description="Filter by specific model",
|
||||
),
|
||||
api_key: Optional[str] = fastapi.Query(
|
||||
default=None,
|
||||
description="Filter by specific API key",
|
||||
),
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
) -> SpendAnalyticsPaginatedResponse:
|
||||
"""
|
||||
Aggregated analytics for a user's daily activity without pagination.
|
||||
Returns the same response shape as the paginated endpoint with page metadata set to single-page.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail={"error": CommonProxyErrors.db_not_connected_error.value},
|
||||
)
|
||||
|
||||
if start_date is None or end_date is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={"error": "Please provide start_date and end_date"},
|
||||
)
|
||||
|
||||
try:
|
||||
entity_id: Optional[str] = None
|
||||
if not _user_has_admin_view(user_api_key_dict):
|
||||
entity_id = user_api_key_dict.user_id
|
||||
|
||||
return await get_daily_activity_aggregated(
|
||||
prisma_client=prisma_client,
|
||||
table_name="litellm_dailyuserspend",
|
||||
entity_id_field="user_id",
|
||||
entity_id=entity_id,
|
||||
entity_metadata_field=None,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.exception(
|
||||
"/user/daily/activity/aggregated: Exception occured - {}".format(str(e))
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={"error": f"Failed to fetch analytics: {str(e)}"},
|
||||
)
|
||||
|
||||
@ -51,6 +51,7 @@ from litellm.proxy.common_utils.admin_ui_utils import (
|
||||
from litellm.proxy.common_utils.html_forms.jwt_display_template import (
|
||||
jwt_display_template,
|
||||
)
|
||||
from litellm.proxy.common_utils.html_forms.ui_login import html_form
|
||||
from litellm.proxy.management_endpoints.internal_user_endpoints import new_user
|
||||
from litellm.proxy.management_endpoints.sso_helper_utils import (
|
||||
check_is_admin_only_access,
|
||||
@ -76,20 +77,16 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/sso/key/generate", tags=["experimental"], include_in_schema=False)
|
||||
async def serve_login_page(
|
||||
request: Request,
|
||||
source: Optional[str] = None,
|
||||
key: Optional[str] = None,
|
||||
error: Optional[str] = None,
|
||||
):
|
||||
async def google_login(request: Request, source: Optional[str] = None, key: Optional[str] = None): # noqa: PLR0915
|
||||
"""
|
||||
Create Proxy API Keys using Google Workspace SSO. Requires setting PROXY_BASE_URL in .env
|
||||
PROXY_BASE_URL should be the your deployed proxy endpoint, e.g. PROXY_BASE_URL="https://litellm-production-7002.up.railway.app/"
|
||||
Example:
|
||||
Serves a unified login page with options for both normal
|
||||
username/password login and SSO.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
from litellm.proxy.proxy_server import (
|
||||
premium_user,
|
||||
user_custom_ui_sso_sign_in_handler,
|
||||
)
|
||||
|
||||
microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
|
||||
google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
|
||||
@ -102,334 +99,6 @@ async def serve_login_page(
|
||||
if is_disabled:
|
||||
return admin_ui_disabled()
|
||||
|
||||
####### Check if user is a Enterprise / Premium User for SSO #######
|
||||
sso_available = False
|
||||
if (
|
||||
microsoft_client_id is not None
|
||||
or google_client_id is not None
|
||||
or generic_client_id is not None
|
||||
):
|
||||
if premium_user is True:
|
||||
sso_available = True
|
||||
|
||||
####### Detect DB + MASTER KEY in .env #######
|
||||
missing_env_vars = show_missing_vars_in_env()
|
||||
if missing_env_vars is not None:
|
||||
return missing_env_vars
|
||||
#########################################################
|
||||
# Construct Redirect URL
|
||||
base_url_to_redirect_to: Optional[str] = None
|
||||
base_url_to_redirect_to = os.getenv("PROXY_BASE_URL", "")
|
||||
server_root_path = os.getenv("SERVER_ROOT_PATH", "")
|
||||
if server_root_path != "":
|
||||
base_url_to_redirect_to += server_root_path
|
||||
#########################################################
|
||||
|
||||
# Build the unified login page HTML
|
||||
error_message = ""
|
||||
if error == "1":
|
||||
error_message = """
|
||||
<div style="
|
||||
background-color: #fef2f2;
|
||||
border-left: 4px solid #dc2626;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
color: #dc2626;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
">
|
||||
⚠️ Invalid username or password. Please try again.
|
||||
</div>
|
||||
"""
|
||||
|
||||
sso_button = ""
|
||||
if sso_available:
|
||||
sso_login_url = base_url_to_redirect_to
|
||||
if sso_login_url.endswith("/"):
|
||||
sso_login_url += "sso/login"
|
||||
else:
|
||||
sso_login_url += "/sso/login"
|
||||
|
||||
sso_button = f"""
|
||||
<div style="
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
text-align: center;
|
||||
">
|
||||
<p style="
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
">or</p>
|
||||
<a href="{sso_login_url}" style="
|
||||
display: inline-block;
|
||||
background-color: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #374151;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
" onmouseover="this.style.backgroundColor='#f1f5f9'; this.style.borderColor='#cbd5e1';"
|
||||
onmouseout="this.style.backgroundColor='#f8fafc'; this.style.borderColor='#e2e8f0';">
|
||||
🔐 Login with SSO
|
||||
</a>
|
||||
</div>
|
||||
"""
|
||||
|
||||
if base_url_to_redirect_to.endswith("/"):
|
||||
url_to_redirect_to = base_url_to_redirect_to + "login"
|
||||
else:
|
||||
url_to_redirect_to = base_url_to_redirect_to + "/login"
|
||||
|
||||
unified_login_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LiteLLM Login</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f8fafc;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}}
|
||||
|
||||
form {{
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
width: 450px;
|
||||
max-width: 100%;
|
||||
}}
|
||||
|
||||
.logo-container {{
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
|
||||
.logo {{
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}}
|
||||
|
||||
h2 {{
|
||||
margin: 0 0 10px;
|
||||
color: #1e293b;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.subtitle {{
|
||||
color: #64748b;
|
||||
margin: 0 0 20px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.info-box {{
|
||||
background-color: #f1f5f9;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #2563eb;
|
||||
}}
|
||||
|
||||
.info-header {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
color: #1e40af;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}}
|
||||
|
||||
.info-header svg {{
|
||||
margin-right: 8px;
|
||||
}}
|
||||
|
||||
.info-box p {{
|
||||
color: #475569;
|
||||
margin: 8px 0;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
}}
|
||||
|
||||
label {{
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
}}
|
||||
|
||||
.required {{
|
||||
color: #dc2626;
|
||||
margin-left: 2px;
|
||||
}}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {{
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
color: #1e293b;
|
||||
background-color: #fff;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus {{
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}}
|
||||
|
||||
.toggle-password {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: -15px;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
|
||||
.toggle-password input[type="checkbox"] {{
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}}
|
||||
|
||||
.toggle-password label {{
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}}
|
||||
|
||||
input[type="submit"] {{
|
||||
background-color: #6466E9;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
padding: 10px 16px;
|
||||
transition: background-color 0.2s;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}}
|
||||
|
||||
input[type="submit"]:hover {{
|
||||
background-color: #4138C2;
|
||||
}}
|
||||
|
||||
a {{
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}}
|
||||
|
||||
a:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
|
||||
code {{
|
||||
background-color: #f1f5f9;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="{url_to_redirect_to}" method="post">
|
||||
<div class="logo-container">
|
||||
<div class="logo">
|
||||
🚅 LiteLLM
|
||||
</div>
|
||||
</div>
|
||||
<h2>Login</h2>
|
||||
<p class="subtitle">Access your LiteLLM Admin UI.</p>
|
||||
|
||||
{error_message}
|
||||
|
||||
<div class="info-box">
|
||||
<div class="info-header">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="16" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
||||
</svg>
|
||||
Default Credentials
|
||||
</div>
|
||||
<p>By default, Username is <code>admin</code> and Password is your set LiteLLM Proxy <code>MASTER_KEY</code>.</p>
|
||||
<p>Need to set UI credentials or SSO? <a href="https://docs.litellm.ai/docs/proxy/ui" target="_blank">Check the documentation</a>.</p>
|
||||
</div>
|
||||
|
||||
<label for="username">Username<span class="required">*</span></label>
|
||||
<input type="text" id="username" name="username" required placeholder="Enter your username" autocomplete="username">
|
||||
|
||||
<label for="password">Password<span class="required">*</span></label>
|
||||
<input type="password" id="password" name="password" required placeholder="Enter your password" autocomplete="current-password">
|
||||
<div class="toggle-password">
|
||||
<input type="checkbox" id="show-password" onclick="togglePasswordVisibility()">
|
||||
<label for="show-password">Show password</label>
|
||||
</div>
|
||||
<input type="submit" value="Login">
|
||||
|
||||
{sso_button}
|
||||
</form>
|
||||
<script>
|
||||
function togglePasswordVisibility() {{
|
||||
var passwordField = document.getElementById("password");
|
||||
passwordField.type = passwordField.type === "password" ? "text" : "password";
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
return HTMLResponse(content=unified_login_html, status_code=200)
|
||||
|
||||
|
||||
@router.get("/sso/login", tags=["experimental"], include_in_schema=False)
|
||||
async def sso_login_redirect(
|
||||
request: Request, source: Optional[str] = None, key: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
Handles SSO login redirect - this is what the "Login with SSO" button points to
|
||||
"""
|
||||
from litellm.proxy.proxy_server import (
|
||||
premium_user,
|
||||
user_custom_ui_sso_sign_in_handler,
|
||||
)
|
||||
|
||||
microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
|
||||
google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
|
||||
generic_client_id = os.getenv("GENERIC_CLIENT_ID", None)
|
||||
|
||||
####### Check if user is a Enterprise / Premium User #######
|
||||
if (
|
||||
microsoft_client_id is not None
|
||||
@ -444,12 +113,18 @@ async def sso_login_redirect(
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
####### Detect DB + MASTER KEY in .env #######
|
||||
missing_env_vars = show_missing_vars_in_env()
|
||||
if missing_env_vars is not None:
|
||||
return missing_env_vars
|
||||
ui_username = os.getenv("UI_USERNAME")
|
||||
|
||||
# get url from request - always use regular callback, but set state for CLI
|
||||
redirect_url = SSOAuthenticationHandler.get_redirect_url_for_sso(
|
||||
request=request,
|
||||
sso_callback_route="sso/callback",
|
||||
)
|
||||
|
||||
|
||||
# Store CLI key in state for OAuth flow
|
||||
cli_state: Optional[str] = SSOAuthenticationHandler._get_cli_state(
|
||||
source=source,
|
||||
@ -462,14 +137,11 @@ async def sso_login_redirect(
|
||||
from litellm_enterprise.proxy.auth.custom_sso_handler import (
|
||||
EnterpriseCustomSSOHandler,
|
||||
)
|
||||
|
||||
return await EnterpriseCustomSSOHandler.handle_custom_ui_sso_sign_in(
|
||||
request=request,
|
||||
)
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Enterprise features are not available. Custom UI SSO sign-in requires LiteLLM Enterprise."
|
||||
)
|
||||
raise ValueError("Enterprise features are not available. Custom UI SSO sign-in requires LiteLLM Enterprise.")
|
||||
|
||||
# Check if we should use SSO handler
|
||||
if (
|
||||
@ -488,9 +160,16 @@ async def sso_login_redirect(
|
||||
generic_client_id=generic_client_id,
|
||||
state=cli_state,
|
||||
)
|
||||
elif ui_username is not None:
|
||||
# No Google, Microsoft SSO
|
||||
# Use UI Credentials set in .env
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
return HTMLResponse(content=html_form, status_code=200)
|
||||
else:
|
||||
# No SSO configured, redirect back to login page
|
||||
return RedirectResponse(url="/sso/key/generate", status_code=303)
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
return HTMLResponse(content=html_form, status_code=200)
|
||||
|
||||
|
||||
def generic_response_convertor(
|
||||
@ -846,16 +525,15 @@ async def check_and_update_if_proxy_admin_id(
|
||||
async def auth_callback(request: Request, state: Optional[str] = None): # noqa: PLR0915
|
||||
"""Verify login"""
|
||||
verbose_proxy_logger.info(f"Starting SSO callback with state: {state}")
|
||||
|
||||
|
||||
# Check if this is a CLI login (state starts with our CLI prefix)
|
||||
from litellm.constants import LITELLM_CLI_SESSION_TOKEN_PREFIX
|
||||
|
||||
if state and state.startswith(f"{LITELLM_CLI_SESSION_TOKEN_PREFIX}:"):
|
||||
# Extract the key ID from the state
|
||||
key_id = state.split(":", 1)[1]
|
||||
verbose_proxy_logger.info(f"CLI SSO callback detected for key: {key_id}")
|
||||
return await cli_sso_callback(request, key=key_id)
|
||||
|
||||
|
||||
from litellm.proxy._types import LiteLLM_JWTAuth
|
||||
from litellm.proxy.auth.handle_jwt import JWTHandler
|
||||
from litellm.proxy.proxy_server import (
|
||||
@ -930,7 +608,7 @@ async def auth_callback(request: Request, state: Optional[str] = None): # noqa:
|
||||
status_code=401,
|
||||
detail="Result not returned by SSO provider.",
|
||||
)
|
||||
|
||||
|
||||
return await SSOAuthenticationHandler.get_redirect_response_from_openid(
|
||||
result=result,
|
||||
request=request,
|
||||
@ -940,26 +618,28 @@ async def auth_callback(request: Request, state: Optional[str] = None): # noqa:
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
async def cli_sso_callback(request: Request, key: Optional[str] = None):
|
||||
"""CLI SSO callback - generates the key with pre-specified ID"""
|
||||
verbose_proxy_logger.info(f"CLI SSO callback for key: {key}")
|
||||
|
||||
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
generate_key_helper_fn,
|
||||
)
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if not key or not key.startswith("sk-"):
|
||||
|
||||
if not key or not key.startswith('sk-'):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid key parameter. Must be a valid key ID starting with 'sk-'",
|
||||
detail="Invalid key parameter. Must be a valid key ID starting with 'sk-'"
|
||||
)
|
||||
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=CommonProxyErrors.db_not_connected_error.value
|
||||
)
|
||||
|
||||
|
||||
# Generate a simple key for CLI usage with the pre-specified key ID
|
||||
try:
|
||||
await generate_key_helper_fn(
|
||||
@ -973,57 +653,63 @@ async def cli_sso_callback(request: Request, key: Optional[str] = None):
|
||||
table_name="key",
|
||||
token=key, # Use the pre-specified key ID
|
||||
)
|
||||
|
||||
|
||||
verbose_proxy_logger.info(f"Generated CLI key: {key}")
|
||||
|
||||
|
||||
# Return success page
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from litellm.proxy.common_utils.html_forms.cli_sso_success import (
|
||||
render_cli_sso_success_page,
|
||||
)
|
||||
|
||||
|
||||
html_content = render_cli_sso_success_page()
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(f"Error generating CLI key: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to generate key: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to generate key: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/sso/cli/poll/{key_id}", tags=["experimental"], include_in_schema=False)
|
||||
async def cli_poll_key(key_id: str):
|
||||
"""CLI polling endpoint - checks if key exists in DB"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if not key_id.startswith("sk-"):
|
||||
raise HTTPException(status_code=400, detail="Invalid key ID format")
|
||||
|
||||
|
||||
if not key_id.startswith('sk-'):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid key ID format"
|
||||
)
|
||||
|
||||
if prisma_client is None:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=CommonProxyErrors.db_not_connected_error.value
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# Check if key exists in database
|
||||
from litellm.proxy.utils import hash_token
|
||||
|
||||
hashed_token = hash_token(key_id)
|
||||
|
||||
|
||||
key_obj = await prisma_client.db.litellm_verificationtoken.find_unique(
|
||||
where={"token": hashed_token}
|
||||
)
|
||||
|
||||
|
||||
if key_obj:
|
||||
verbose_proxy_logger.info(f"CLI key found: {key_id}")
|
||||
return {"status": "ready", "key": key_id}
|
||||
else:
|
||||
return {"status": "pending"}
|
||||
|
||||
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(f"Error polling for CLI key: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Error checking key status: {str(e)}"
|
||||
status_code=500,
|
||||
detail=f"Error checking key status: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@ -1125,7 +811,6 @@ class SSOAuthenticationHandler:
|
||||
"""
|
||||
Handler for SSO Authentication across all SSO providers
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
async def get_sso_login_redirect(
|
||||
redirect_url: str,
|
||||
@ -1478,6 +1163,7 @@ class SSOAuthenticationHandler:
|
||||
_new_team_request.update(_default_team_params)
|
||||
team_request = NewTeamRequest(**_new_team_request)
|
||||
return team_request
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_cli_state(source: Optional[str], key: Optional[str]) -> Optional[str]:
|
||||
@ -1490,15 +1176,13 @@ class SSOAuthenticationHandler:
|
||||
LITELLM_CLI_SESSION_TOKEN_PREFIX,
|
||||
LITELLM_CLI_SOURCE_IDENTIFIER,
|
||||
)
|
||||
return f"{LITELLM_CLI_SESSION_TOKEN_PREFIX}:{key}" if source == LITELLM_CLI_SOURCE_IDENTIFIER and key else None
|
||||
|
||||
|
||||
|
||||
return (
|
||||
f"{LITELLM_CLI_SESSION_TOKEN_PREFIX}:{key}"
|
||||
if source == LITELLM_CLI_SOURCE_IDENTIFIER and key
|
||||
else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def get_redirect_response_from_openid( # noqa: PLR0915
|
||||
async def get_redirect_response_from_openid( # noqa: PLR0915
|
||||
result: Union[OpenID, dict, CustomOpenID],
|
||||
request: Request,
|
||||
received_response: Optional[dict] = None,
|
||||
@ -1518,18 +1202,14 @@ class SSOAuthenticationHandler:
|
||||
)
|
||||
from litellm.proxy.utils import get_prisma_client_or_throw
|
||||
from litellm.types.proxy.ui_sso import ReturnedUITokenObject
|
||||
prisma_client = get_prisma_client_or_throw("Prisma client is None, connect a database to your proxy")
|
||||
|
||||
prisma_client = get_prisma_client_or_throw(
|
||||
"Prisma client is None, connect a database to your proxy"
|
||||
)
|
||||
|
||||
# User is Authe'd in - generate key for the UI to access Proxy
|
||||
verbose_proxy_logger.info(f"SSO callback result: {result}")
|
||||
|
||||
user_email: Optional[str] = getattr(result, "email", None)
|
||||
user_id: Optional[str] = (
|
||||
getattr(result, "id", None) if result is not None else None
|
||||
)
|
||||
user_id: Optional[str] = getattr(result, "id", None) if result is not None else None
|
||||
|
||||
if user_email is not None and os.getenv("ALLOWED_EMAIL_DOMAINS") is not None:
|
||||
email_domain = user_email.split("@")[1]
|
||||
@ -1714,8 +1394,7 @@ class SSOAuthenticationHandler:
|
||||
redirect_response = RedirectResponse(url=litellm_dashboard_ui, status_code=303)
|
||||
redirect_response.set_cookie(key="token", value=jwt_token)
|
||||
return redirect_response
|
||||
|
||||
|
||||
|
||||
class MicrosoftSSOHandler:
|
||||
"""
|
||||
Handles Microsoft SSO callback response and returns a CustomOpenID object
|
||||
@ -2215,28 +1894,3 @@ async def debug_sso_callback(request: Request):
|
||||
)
|
||||
|
||||
return HTMLResponse(content=html_content)
|
||||
|
||||
|
||||
@router.post("/sso/key/generate", tags=["experimental"], include_in_schema=False)
|
||||
async def process_login(request: Request):
|
||||
"""
|
||||
Process username/password login from the unified login page
|
||||
"""
|
||||
try:
|
||||
# Get form data
|
||||
form_data = await request.form()
|
||||
username = form_data.get("username")
|
||||
password = form_data.get("password")
|
||||
|
||||
if not username or not password:
|
||||
return RedirectResponse(url="/sso/key/generate?error=1", status_code=303)
|
||||
|
||||
# Import the actual login function from proxy_server
|
||||
from litellm.proxy.proxy_server import login
|
||||
|
||||
# Call the real login function that handles all the authentication properly
|
||||
return await login(request)
|
||||
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(f"Error processing login: {e}")
|
||||
return RedirectResponse(url="/sso/key/generate?error=1", status_code=303)
|
||||
|
||||
@ -1,15 +1,5 @@
|
||||
model_list:
|
||||
- model_name: bedrock/*
|
||||
- model_name: gemini/*
|
||||
litellm_params:
|
||||
model: bedrock/*
|
||||
model: gemini/*
|
||||
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["s3_v2"]
|
||||
s3_callback_params:
|
||||
s3_bucket_name: litellm-logs # AWS Bucket Name for S3
|
||||
s3_region_name: us-west-2
|
||||
|
||||
general_settings:
|
||||
cold_storage_custom_logger: s3_v2
|
||||
store_prompts_in_cold_storage: true
|
||||
@ -84,6 +84,8 @@ class LiteLLMCompletionTransformationHandler:
|
||||
litellm_custom_stream_wrapper=litellm_completion_response,
|
||||
request_input=input,
|
||||
responses_api_request=responses_api_request,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
litellm_metadata=kwargs.get("litellm_metadata", {}),
|
||||
)
|
||||
|
||||
async def async_response_api_handler(
|
||||
@ -129,4 +131,6 @@ class LiteLLMCompletionTransformationHandler:
|
||||
litellm_custom_stream_wrapper=litellm_completion_response,
|
||||
request_input=request_input,
|
||||
responses_api_request=responses_api_request,
|
||||
custom_llm_provider=litellm_completion_request.get("custom_llm_provider"),
|
||||
litellm_metadata=kwargs.get("litellm_metadata", {}),
|
||||
)
|
||||
|
||||
@ -6,6 +6,7 @@ from litellm.responses.litellm_completion_transformation.transformation import (
|
||||
LiteLLMCompletionResponsesConfig,
|
||||
)
|
||||
from litellm.responses.streaming_iterator import ResponsesAPIStreamingIterator
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
from litellm.types.llms.openai import (
|
||||
OutputTextDeltaEvent,
|
||||
ReasoningSummaryTextDeltaEvent,
|
||||
@ -34,6 +35,8 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
||||
litellm_custom_stream_wrapper: litellm.CustomStreamWrapper,
|
||||
request_input: Union[str, ResponseInputParam],
|
||||
responses_api_request: ResponsesAPIOptionalRequestParams,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
litellm_metadata: Optional[dict] = None,
|
||||
):
|
||||
self.litellm_custom_stream_wrapper: litellm.CustomStreamWrapper = (
|
||||
litellm_custom_stream_wrapper
|
||||
@ -42,6 +45,8 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
||||
self.responses_api_request: ResponsesAPIOptionalRequestParams = (
|
||||
responses_api_request
|
||||
)
|
||||
self.custom_llm_provider: Optional[str] = custom_llm_provider
|
||||
self.litellm_metadata: Optional[dict] = litellm_metadata or {}
|
||||
self.collected_chat_completion_chunks: List[ModelResponseStream] = []
|
||||
self.finished: bool = False
|
||||
|
||||
@ -164,14 +169,23 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
||||
Union[ModelResponse, TextCompletionResponse]
|
||||
] = stream_chunk_builder(chunks=self.collected_chat_completion_chunks)
|
||||
if litellm_model_response and isinstance(litellm_model_response, ModelResponse):
|
||||
# Transform the response
|
||||
responses_api_response = LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response(
|
||||
request_input=self.request_input,
|
||||
chat_completion_response=litellm_model_response,
|
||||
responses_api_request=self.responses_api_request,
|
||||
)
|
||||
|
||||
# Encode the response ID to match non-streaming behavior
|
||||
encoded_response = ResponsesAPIRequestUtils._update_responses_api_response_id_with_model_id(
|
||||
responses_api_response=responses_api_response,
|
||||
custom_llm_provider=self.custom_llm_provider,
|
||||
litellm_metadata=self.litellm_metadata,
|
||||
)
|
||||
|
||||
return ResponseCompletedEvent(
|
||||
type=ResponsesAPIStreamEvents.RESPONSE_COMPLETED,
|
||||
response=LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response(
|
||||
request_input=self.request_input,
|
||||
chat_completion_response=litellm_model_response,
|
||||
responses_api_request=self.responses_api_request,
|
||||
),
|
||||
response=encoded_response,
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -2912,6 +2912,39 @@ def _remove_strict_from_schema(schema):
|
||||
return schema
|
||||
|
||||
|
||||
def _remove_json_schema_refs(schema, max_depth=10):
|
||||
"""
|
||||
Remove JSON schema reference fields like '$id' and '$schema' that can cause issues with some providers.
|
||||
|
||||
These fields are used for schema validation but can cause problems when the schema references
|
||||
are not accessible to the provider's validation system.
|
||||
|
||||
Args:
|
||||
schema: The schema object to clean (dict, list, or other)
|
||||
max_depth: Maximum recursion depth to prevent infinite loops (default: 10)
|
||||
|
||||
Relevant Issues: Mistral API grammar validation fails when schema contains $id and $schema references
|
||||
"""
|
||||
if max_depth <= 0:
|
||||
return schema
|
||||
|
||||
if isinstance(schema, dict):
|
||||
# Remove JSON schema reference fields
|
||||
schema.pop("$id", None)
|
||||
schema.pop("$schema", None)
|
||||
|
||||
# Recursively process all dictionary values
|
||||
for key, value in schema.items():
|
||||
_remove_json_schema_refs(value, max_depth - 1)
|
||||
|
||||
elif isinstance(schema, list):
|
||||
# Recursively process all items in the list
|
||||
for item in schema:
|
||||
_remove_json_schema_refs(item, max_depth - 1)
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def _remove_unsupported_params(
|
||||
non_default_params: dict, supported_openai_params: Optional[List[str]]
|
||||
) -> dict:
|
||||
|
||||
@ -296,60 +296,6 @@
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gpt-5-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gpt-5-mini-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gpt-5-nano-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"watsonx/ibm/granite-3-8b-instruct": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
@ -666,6 +612,263 @@
|
||||
"search_context_size_high": 0.03
|
||||
}
|
||||
},
|
||||
"gpt-5": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-mini": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-nano": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-chat": {
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 1047576,
|
||||
"max_output_tokens": 32768,
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 2e-05,
|
||||
"input_cost_per_token_batches": 2.5e-06,
|
||||
"output_cost_per_token_batches": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-06,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"gpt-5-chat-latest": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-mini-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"gpt-5-nano-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "openai",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"codex-mini-latest": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
@ -2061,6 +2264,263 @@
|
||||
"/v1/audio/speech"
|
||||
]
|
||||
},
|
||||
"azure/gpt-5": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-mini": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-mini-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 2e-06,
|
||||
"cache_read_input_token_cost": 2.5e-08,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-nano-2025-08-07": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-nano": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"cache_read_input_token_cost": 5e-09,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-5-chat": {
|
||||
"max_tokens": 32768,
|
||||
"max_input_tokens": 1047576,
|
||||
"max_output_tokens": 32768,
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 2e-05,
|
||||
"input_cost_per_token_batches": 2.5e-06,
|
||||
"output_cost_per_token_batches": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-06,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"azure/gpt-5-chat-latest": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"input_cost_per_token": 1.25e-06,
|
||||
"output_cost_per_token": 1e-05,
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_pdf_input": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"azure/gpt-4o-mini-tts": {
|
||||
"mode": "audio_speech",
|
||||
"input_cost_per_token": 2.5e-06,
|
||||
@ -5825,6 +6285,32 @@
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-opus-4-1": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "anthropic",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-opus-4-1-20250805": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
@ -17758,7 +18244,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-4-scout-17b-16e-instruct": {
|
||||
@ -17771,7 +18256,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.3-70b-instruct": {
|
||||
@ -17784,7 +18268,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.2-90b-vision-instruct": {
|
||||
@ -17797,7 +18280,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/meta.llama-3.1-405b-instruct": {
|
||||
@ -17810,7 +18292,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
|
||||
@ -17824,7 +18305,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3": {
|
||||
@ -17837,7 +18317,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-mini": {
|
||||
@ -17850,7 +18329,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-fast": {
|
||||
@ -17863,7 +18341,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
},
|
||||
"oci/xai.grok-3-mini-fast": {
|
||||
@ -17876,7 +18353,6 @@
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": false,
|
||||
"supports_tool_choice": false,
|
||||
"source": "https://www.oracle.com/artificial-intelligence/generative-ai/generative-ai-service/pricing"
|
||||
}
|
||||
}
|
||||
|
||||
7
poetry.lock
generated
7
poetry.lock
generated
@ -2538,15 +2538,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "litellm-proxy-extras"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package."
|
||||
optional = true
|
||||
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"proxy\""
|
||||
files = [
|
||||
{file = "litellm_proxy_extras-0.2.15-py3-none-any.whl", hash = "sha256:25e7d7cabe3f10233e2802d4521576bef438eea8b0800b1dbf38ba83ba751bb8"},
|
||||
{file = "litellm_proxy_extras-0.2.15.tar.gz", hash = "sha256:62a9fdcb77d25aa7bfdfa04ac878e452f185dfb0b9538d4d8988a1b512360649"},
|
||||
{file = "litellm_proxy_extras-0.2.16.tar.gz", hash = "sha256:81a1e8a172feb7da86985f529e891ca7be66ba293ae3e716bf69b266fa776a04"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6524,4 +6523,4 @@ utils = ["numpydoc"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.8.1,<4.0, !=3.9.7"
|
||||
content-hash = "8caa7dd3ee7d56562ccb799b97de7f2d206010d8300139bdc3614853c9ea1d31"
|
||||
content-hash = "91f8d8cba2aa02a3eb205e91118891612c510790e828b56d19057fc509e6d25d"
|
||||
|
||||
@ -137,6 +137,14 @@ model_list:
|
||||
model: openai/my-fake-model
|
||||
api_key: my-fake-key
|
||||
api_base: https://exampleopenaiendpoint-production.up.railway.appxxxx/
|
||||
- model_name: gemini-1.5-flash
|
||||
litellm_params:
|
||||
model: gemini/gemini-1.5-flash
|
||||
api_key: os.environ/GOOGLE_API_KEY
|
||||
- model_name: gpt-4o
|
||||
litellm_params:
|
||||
model: gpt-4o
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
|
||||
|
||||
litellm_settings:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "litellm"
|
||||
version = "1.75.2"
|
||||
version = "1.75.3"
|
||||
description = "Library to easily interface with LLM API providers"
|
||||
authors = ["BerriAI"]
|
||||
license = "MIT"
|
||||
@ -58,7 +58,7 @@ websockets = {version = "^13.1.0", optional = true}
|
||||
boto3 = {version = "1.34.34", optional = true}
|
||||
redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"}
|
||||
mcp = {version = "^1.10.0", optional = true, python = ">=3.10"}
|
||||
litellm-proxy-extras = {version = "0.2.15", optional = true}
|
||||
litellm-proxy-extras = {version = "0.2.16", optional = true}
|
||||
rich = {version = "13.7.1", optional = true}
|
||||
litellm-enterprise = {version = "0.1.19", optional = true}
|
||||
diskcache = {version = "^5.6.1", optional = true}
|
||||
@ -154,7 +154,7 @@ requires = ["poetry-core", "wheel"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "1.75.2"
|
||||
version = "1.75.3"
|
||||
version_files = [
|
||||
"pyproject.toml:^version"
|
||||
]
|
||||
|
||||
@ -41,7 +41,7 @@ sentry_sdk==2.21.0 # for sentry error handling
|
||||
detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests
|
||||
cryptography==43.0.1
|
||||
tzdata==2025.1 # IANA time zone database
|
||||
litellm-proxy-extras==0.2.15 # for proxy extras - e.g. prisma migrations
|
||||
litellm-proxy-extras==0.2.16 # for proxy extras - e.g. prisma migrations
|
||||
### LITELLM PACKAGE DEPENDENCIES
|
||||
python-dotenv==1.0.1 # for env
|
||||
tiktoken==0.8.0 # for calculating usage
|
||||
|
||||
@ -25,6 +25,7 @@ IGNORE_FUNCTIONS = [
|
||||
"filter_value_from_dict", # max depth set.
|
||||
"normalize_json_schema_types", # max depth set.
|
||||
"_extract_fields_recursive", # max depth set.
|
||||
"_remove_json_schema_refs", # max depth set.
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,239 @@
|
||||
"""
|
||||
Unit tests for BaseResponsesAPIStreamingIterator
|
||||
|
||||
Tests core functionality including:
|
||||
1. Processing chunks and handling ResponseCompletedEvent
|
||||
2. Ensuring _update_responses_api_response_id_with_model_id is called for final chunk
|
||||
3. Verifying ID update is NOT called for non-final chunks (delta events)
|
||||
4. Edge case handling for invalid JSON, empty chunks, and [DONE] markers
|
||||
|
||||
These tests ensure the streaming iterator correctly processes response chunks
|
||||
and applies model ID updates only to completed responses, as required for proper
|
||||
response tracking and logging.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
from litellm.constants import STREAM_SSE_DONE_STRING
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
|
||||
from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
from litellm.types.llms.openai import (
|
||||
ResponseCompletedEvent,
|
||||
ResponsesAPIResponse,
|
||||
ResponsesAPIStreamEvents,
|
||||
OutputTextDeltaEvent
|
||||
)
|
||||
|
||||
|
||||
class TestBaseResponsesAPIStreamingIterator:
|
||||
"""Test cases for BaseResponsesAPIStreamingIterator"""
|
||||
|
||||
def test_process_chunk_with_response_completed_event(self):
|
||||
"""
|
||||
Test that _process_chunk correctly processes a ResponseCompletedEvent
|
||||
and calls _update_responses_api_response_id_with_model_id for the final chunk.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_response = Mock()
|
||||
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
|
||||
mock_config = Mock(spec=BaseResponsesAPIConfig)
|
||||
|
||||
# Create a mock ResponsesAPIResponse for the completed event
|
||||
mock_responses_api_response = Mock(spec=ResponsesAPIResponse)
|
||||
mock_responses_api_response.id = "original_response_id"
|
||||
|
||||
# Create a mock ResponseCompletedEvent
|
||||
mock_completed_event = Mock(spec=ResponseCompletedEvent)
|
||||
mock_completed_event.type = ResponsesAPIStreamEvents.RESPONSE_COMPLETED
|
||||
mock_completed_event.response = mock_responses_api_response
|
||||
|
||||
# Set up the mock transform method to return our completed event
|
||||
mock_config.transform_streaming_response.return_value = mock_completed_event
|
||||
|
||||
# Mock the _update_responses_api_response_id_with_model_id method
|
||||
updated_response = Mock(spec=ResponsesAPIResponse)
|
||||
updated_response.id = "updated_response_id"
|
||||
|
||||
# Create the iterator instance
|
||||
iterator = BaseResponsesAPIStreamingIterator(
|
||||
response=mock_response,
|
||||
model="gpt-4",
|
||||
responses_api_provider_config=mock_config,
|
||||
logging_obj=mock_logging_obj,
|
||||
litellm_metadata={"model_info": {"id": "model_123"}},
|
||||
custom_llm_provider="openai"
|
||||
)
|
||||
|
||||
# Prepare test chunk data
|
||||
test_chunk_data = {
|
||||
"type": "response.completed",
|
||||
"response": {
|
||||
"id": "original_response_id",
|
||||
"output": [{"type": "message", "content": [{"text": "Hello World"}]}]
|
||||
}
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
ResponsesAPIRequestUtils,
|
||||
'_update_responses_api_response_id_with_model_id',
|
||||
return_value=updated_response
|
||||
) as mock_update_id:
|
||||
# Process the chunk
|
||||
result = iterator._process_chunk(json.dumps(test_chunk_data))
|
||||
|
||||
# Assertions
|
||||
assert result is not None
|
||||
assert result.type == ResponsesAPIStreamEvents.RESPONSE_COMPLETED
|
||||
|
||||
# Verify that _update_responses_api_response_id_with_model_id was called
|
||||
mock_update_id.assert_called_once_with(
|
||||
responses_api_response=mock_responses_api_response,
|
||||
litellm_metadata={"model_info": {"id": "model_123"}},
|
||||
custom_llm_provider="openai"
|
||||
)
|
||||
|
||||
# Verify the completed response was stored
|
||||
assert iterator.completed_response == result
|
||||
|
||||
# Verify the response was updated on the event
|
||||
assert result.response == updated_response
|
||||
|
||||
def test_process_chunk_with_delta_event_no_id_update(self):
|
||||
"""
|
||||
Test that _process_chunk correctly processes a delta event
|
||||
and does NOT call _update_responses_api_response_id_with_model_id.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_response = Mock()
|
||||
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
|
||||
mock_config = Mock(spec=BaseResponsesAPIConfig)
|
||||
|
||||
# Create a mock OutputTextDeltaEvent (not a completed event)
|
||||
mock_delta_event = Mock(spec=OutputTextDeltaEvent)
|
||||
mock_delta_event.type = ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA
|
||||
mock_delta_event.delta = "Hello"
|
||||
# Delta events don't have a response attribute
|
||||
delattr(mock_delta_event, 'response') if hasattr(mock_delta_event, 'response') else None
|
||||
|
||||
# Set up the mock transform method to return our delta event
|
||||
mock_config.transform_streaming_response.return_value = mock_delta_event
|
||||
|
||||
# Create the iterator instance
|
||||
iterator = BaseResponsesAPIStreamingIterator(
|
||||
response=mock_response,
|
||||
model="gpt-4",
|
||||
responses_api_provider_config=mock_config,
|
||||
logging_obj=mock_logging_obj,
|
||||
litellm_metadata={"model_info": {"id": "model_123"}},
|
||||
custom_llm_provider="openai"
|
||||
)
|
||||
|
||||
# Prepare test chunk data for a delta event
|
||||
test_chunk_data = {
|
||||
"type": "response.output_text.delta",
|
||||
"delta": "Hello",
|
||||
"item_id": "item_123",
|
||||
"output_index": 0,
|
||||
"content_index": 0
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
ResponsesAPIRequestUtils,
|
||||
'_update_responses_api_response_id_with_model_id'
|
||||
) as mock_update_id:
|
||||
# Process the chunk
|
||||
result = iterator._process_chunk(json.dumps(test_chunk_data))
|
||||
|
||||
# Assertions
|
||||
assert result is not None
|
||||
assert result.type == ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA
|
||||
|
||||
# Verify that _update_responses_api_response_id_with_model_id was NOT called
|
||||
mock_update_id.assert_not_called()
|
||||
|
||||
# Verify no completed response was stored (since this is not a completed event)
|
||||
assert iterator.completed_response is None
|
||||
|
||||
def test_process_chunk_handles_invalid_json(self):
|
||||
"""
|
||||
Test that _process_chunk gracefully handles invalid JSON.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_response = Mock()
|
||||
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
|
||||
mock_config = Mock(spec=BaseResponsesAPIConfig)
|
||||
|
||||
# Create the iterator instance
|
||||
iterator = BaseResponsesAPIStreamingIterator(
|
||||
response=mock_response,
|
||||
model="gpt-4",
|
||||
responses_api_provider_config=mock_config,
|
||||
logging_obj=mock_logging_obj
|
||||
)
|
||||
|
||||
# Test with invalid JSON
|
||||
result = iterator._process_chunk("invalid json {")
|
||||
|
||||
# Should return None for invalid JSON
|
||||
assert result is None
|
||||
assert iterator.completed_response is None
|
||||
|
||||
def test_process_chunk_handles_done_marker(self):
|
||||
"""
|
||||
Test that _process_chunk correctly handles the [DONE] marker.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_response = Mock()
|
||||
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
|
||||
mock_config = Mock(spec=BaseResponsesAPIConfig)
|
||||
|
||||
# Create the iterator instance
|
||||
iterator = BaseResponsesAPIStreamingIterator(
|
||||
response=mock_response,
|
||||
model="gpt-4",
|
||||
responses_api_provider_config=mock_config,
|
||||
logging_obj=mock_logging_obj
|
||||
)
|
||||
|
||||
# Test with [DONE] marker
|
||||
result = iterator._process_chunk(STREAM_SSE_DONE_STRING)
|
||||
|
||||
# Should return None and set finished flag
|
||||
assert result is None
|
||||
assert iterator.finished is True
|
||||
|
||||
def test_process_chunk_handles_empty_chunk(self):
|
||||
"""
|
||||
Test that _process_chunk correctly handles empty or None chunks.
|
||||
"""
|
||||
# Mock dependencies
|
||||
mock_response = Mock()
|
||||
mock_logging_obj = Mock(spec=LiteLLMLoggingObj)
|
||||
mock_config = Mock(spec=BaseResponsesAPIConfig)
|
||||
|
||||
# Create the iterator instance
|
||||
iterator = BaseResponsesAPIStreamingIterator(
|
||||
response=mock_response,
|
||||
model="gpt-4",
|
||||
responses_api_provider_config=mock_config,
|
||||
logging_obj=mock_logging_obj
|
||||
)
|
||||
|
||||
# Test with empty chunk
|
||||
result = iterator._process_chunk("")
|
||||
assert result is None
|
||||
|
||||
# Test with None chunk
|
||||
result = iterator._process_chunk(None)
|
||||
assert result is None
|
||||
@ -430,7 +430,7 @@ def test_gemini_with_empty_function_call_arguments():
|
||||
async def test_claude_tool_use_with_gemini():
|
||||
response = await litellm.anthropic.messages.acreate(
|
||||
messages=[
|
||||
{"role": "user", "content": "Hello, can you tell me the weather in Boston?"}
|
||||
{"role": "user", "content": "Hello, can you tell me the weather in Boston. Please respond with a tool call?"}
|
||||
],
|
||||
model="gemini/gemini-2.5-flash",
|
||||
stream=True,
|
||||
|
||||
@ -504,75 +504,6 @@ async def test_async_vertexai_streaming_response():
|
||||
pytest.fail(f"An exception occurred: {e}")
|
||||
|
||||
|
||||
# asyncio.run(test_async_vertexai_streaming_response())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ["vertex_ai"]) # "vertex_ai_beta"
|
||||
@pytest.mark.parametrize("sync_mode", [True, False])
|
||||
@pytest.mark.flaky(retries=3, delay=1)
|
||||
@pytest.mark.asyncio
|
||||
async def test_gemini_pro_vision(provider, sync_mode):
|
||||
try:
|
||||
load_vertex_ai_credentials()
|
||||
litellm.set_verbose = True
|
||||
litellm.num_retries = 3
|
||||
if sync_mode:
|
||||
resp = litellm.completion(
|
||||
model="{}/gemini-2.5-flash-lite".format(provider),
|
||||
messages=[
|
||||
{"role": "system", "content": "Be a good bot"},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Whats in this image?"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "gs://cloud-samples-data/generative-ai/image/boats.jpeg"
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
else:
|
||||
resp = await litellm.acompletion(
|
||||
model="{}/gemini-2.5-flash-lite".format(provider),
|
||||
messages=[
|
||||
{"role": "system", "content": "Be a good bot"},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Whats in this image?"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "gs://cloud-samples-data/generative-ai/image/boats.jpeg"
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
print(resp)
|
||||
|
||||
prompt_tokens = resp.usage.prompt_tokens
|
||||
|
||||
# DO Not DELETE this ASSERT
|
||||
# Google counts the prompt tokens for us, we should ensure we use the tokens from the orignal response
|
||||
assert prompt_tokens == 267 # the gemini api returns 267 to us
|
||||
|
||||
except litellm.RateLimitError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
if "500 Internal error encountered.'" in str(e):
|
||||
pass
|
||||
else:
|
||||
pytest.fail(f"An exception occurred - {str(e)}")
|
||||
|
||||
|
||||
# test_gemini_pro_vision()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_pdf", [False]) # True,
|
||||
@pytest.mark.flaky(retries=3, delay=1)
|
||||
|
||||
@ -168,56 +168,6 @@ def test_stream_chunk_builder_litellm_tool_call_regular_message():
|
||||
# test_stream_chunk_builder_litellm_tool_call_regular_message()
|
||||
|
||||
|
||||
def test_stream_chunk_builder_litellm_usage_chunks():
|
||||
"""
|
||||
Checks if stream_chunk_builder is able to correctly rebuild with given metadata from streaming chunks
|
||||
"""
|
||||
from litellm.types.utils import Usage
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "Tell me the funniest joke you know."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Why did the chicken cross the road?\nYou will not guess this one I bet\n",
|
||||
},
|
||||
{"role": "user", "content": "I do not know, why?"},
|
||||
{"role": "assistant", "content": "uhhhh\n\n\nhmmmm.....\nthinking....\n"},
|
||||
{"role": "user", "content": "\nI am waiting...\n\n...\n"},
|
||||
]
|
||||
|
||||
usage: litellm.Usage = Usage(
|
||||
completion_tokens=27,
|
||||
prompt_tokens=50,
|
||||
total_tokens=82,
|
||||
completion_tokens_details=None,
|
||||
prompt_tokens_details=None,
|
||||
)
|
||||
|
||||
gemini_pt = usage.prompt_tokens
|
||||
|
||||
# make a streaming gemini call
|
||||
try:
|
||||
response = completion(
|
||||
model="gemini/gemini-2.5-flash-lite",
|
||||
messages=messages,
|
||||
stream=True,
|
||||
complete_response=True,
|
||||
stream_options={"include_usage": True},
|
||||
)
|
||||
except litellm.InternalServerError as e:
|
||||
pytest.skip(f"Skipping test due to internal server error - {str(e)}")
|
||||
|
||||
usage: litellm.Usage = response.usage
|
||||
|
||||
stream_rebuilt_pt = usage.prompt_tokens
|
||||
|
||||
# assert prompt tokens are the same
|
||||
|
||||
assert (
|
||||
gemini_pt == stream_rebuilt_pt
|
||||
), f"Stream builder is not able to rebuild usage correctly. Got={stream_rebuilt_pt}, expected={gemini_pt}"
|
||||
|
||||
|
||||
def test_stream_chunk_builder_litellm_mixed_calls():
|
||||
response = stream_chunk_builder(stream_chunk_testdata.chunks)
|
||||
assert (
|
||||
|
||||
@ -701,7 +701,7 @@ async def test_completion_gemini_stream(sync_mode):
|
||||
},
|
||||
}
|
||||
]
|
||||
messages = [{"role": "user", "content": "What is the weather like in Boston?"}]
|
||||
messages = [{"role": "user", "content": "What is the weather like in Boston, MA?. You must provide me with a tool call in your response."}]
|
||||
print("testing gemini streaming")
|
||||
complete_response = ""
|
||||
# Add any assertions here to check the response
|
||||
@ -817,7 +817,7 @@ async def test_completion_gemini_stream_accumulated_json(sync_mode):
|
||||
},
|
||||
}
|
||||
]
|
||||
messages = [{"role": "user", "content": "What is the weather like in Boston?"}]
|
||||
messages = [{"role": "user", "content": "What is the weather like in Boston, MA?. You must provide me with a tool call in your response."}]
|
||||
print("testing gemini streaming")
|
||||
complete_response = ""
|
||||
# Add any assertions here to check the response
|
||||
|
||||
@ -243,3 +243,85 @@ def test_cache_read_input_tokens_retained():
|
||||
assert usage.cache_creation_input_tokens == 4
|
||||
assert usage.cache_read_input_tokens == 11775
|
||||
assert usage.prompt_tokens_details.cached_tokens == 11775
|
||||
|
||||
|
||||
def test_stream_chunk_builder_litellm_usage_chunks():
|
||||
"""
|
||||
Validate ChunkProcessor.calculate_usage uses provided usage fields from streaming chunks
|
||||
and reconstructs prompt and completion tokens without making any upstream API calls.
|
||||
"""
|
||||
# Prepare two mocked streaming chunks with usage split across them
|
||||
chunk1 = ModelResponseStream(
|
||||
id="chatcmpl-mocked-usage-1",
|
||||
created=1745513206,
|
||||
model="gemini/gemini-2.5-flash-lite",
|
||||
object="chat.completion.chunk",
|
||||
system_fingerprint=None,
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
index=0,
|
||||
delta=Delta(
|
||||
provider_specific_fields=None,
|
||||
content="",
|
||||
role=None,
|
||||
function_call=None,
|
||||
tool_calls=None,
|
||||
audio=None,
|
||||
),
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
provider_specific_fields=None,
|
||||
stream_options={"include_usage": True},
|
||||
usage=Usage(
|
||||
completion_tokens=0,
|
||||
prompt_tokens=50,
|
||||
total_tokens=50,
|
||||
completion_tokens_details=None,
|
||||
prompt_tokens_details=None,
|
||||
),
|
||||
)
|
||||
|
||||
chunk2 = ModelResponseStream(
|
||||
id="chatcmpl-mocked-usage-1",
|
||||
created=1745513207,
|
||||
model="gemini/gemini-2.5-flash-lite",
|
||||
object="chat.completion.chunk",
|
||||
system_fingerprint=None,
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
delta=Delta(
|
||||
provider_specific_fields=None,
|
||||
content=None,
|
||||
role=None,
|
||||
function_call=None,
|
||||
tool_calls=None,
|
||||
audio=None,
|
||||
),
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
provider_specific_fields=None,
|
||||
stream_options={"include_usage": True},
|
||||
usage=Usage(
|
||||
completion_tokens=27,
|
||||
prompt_tokens=0,
|
||||
total_tokens=27,
|
||||
completion_tokens_details=None,
|
||||
prompt_tokens_details=None,
|
||||
),
|
||||
)
|
||||
|
||||
chunks = [chunk1, chunk2]
|
||||
processor = ChunkProcessor(chunks=chunks)
|
||||
|
||||
usage = processor.calculate_usage(
|
||||
chunks=chunks, model="gemini/gemini-2.5-flash-lite", completion_output=""
|
||||
)
|
||||
|
||||
assert usage.prompt_tokens == 50
|
||||
assert usage.completion_tokens == 27
|
||||
assert usage.total_tokens == 77
|
||||
|
||||
40
tests/test_litellm/llms/openai/test_gpt5_transformation.py
Normal file
40
tests/test_litellm/llms/openai/test_gpt5_transformation.py
Normal file
@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.openai.openai import OpenAIConfig
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def config() -> OpenAIConfig:
|
||||
return OpenAIConfig()
|
||||
|
||||
|
||||
def test_gpt5_maps_max_tokens(config: OpenAIConfig):
|
||||
params = config.map_openai_params(
|
||||
non_default_params={"max_tokens": 10},
|
||||
optional_params={},
|
||||
model="gpt-5",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["max_completion_tokens"] == 10
|
||||
assert "max_tokens" not in params
|
||||
|
||||
|
||||
def test_gpt5_temperature_drop(config: OpenAIConfig):
|
||||
params = config.map_openai_params(
|
||||
non_default_params={"temperature": 0.2},
|
||||
optional_params={},
|
||||
model="gpt-5",
|
||||
drop_params=True,
|
||||
)
|
||||
assert "temperature" not in params
|
||||
|
||||
|
||||
def test_gpt5_temperature_error(config: OpenAIConfig):
|
||||
with pytest.raises(litellm.utils.UnsupportedParamsError):
|
||||
config.map_openai_params(
|
||||
non_default_params={"temperature": 0.2},
|
||||
optional_params={},
|
||||
model="gpt-5",
|
||||
drop_params=False,
|
||||
)
|
||||
@ -938,10 +938,10 @@ class TestUISSO_FunctionsExistence:
|
||||
from litellm.proxy.management_endpoints.ui_sso import auth_callback
|
||||
assert callable(auth_callback)
|
||||
|
||||
def test_sso_login_redirect_exists(self):
|
||||
"""Test that sso_login_redirect function exists"""
|
||||
from litellm.proxy.management_endpoints.ui_sso import sso_login_redirect
|
||||
assert callable(sso_login_redirect)
|
||||
def test_google_login_exists(self):
|
||||
"""Test that google_login function exists"""
|
||||
from litellm.proxy.management_endpoints.ui_sso import google_login
|
||||
assert callable(google_login)
|
||||
|
||||
def test_sso_authentication_handler_exists(self):
|
||||
"""Test that SSOAuthenticationHandler class exists with new methods"""
|
||||
@ -1054,7 +1054,7 @@ class TestCustomUISSO:
|
||||
"""Test that proper error is raised when enterprise module is not available"""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from litellm.proxy.management_endpoints.ui_sso import sso_login_redirect
|
||||
from litellm.proxy.management_endpoints.ui_sso import google_login
|
||||
|
||||
# Mock request
|
||||
mock_request = MagicMock()
|
||||
@ -1246,46 +1246,3 @@ class TestCustomUISSO:
|
||||
assert result == mock_redirect_response
|
||||
assert result.status_code == 303
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_serve_login_page_server_root_path():
|
||||
"""
|
||||
Test that serve_login_page includes SERVER_ROOT_PATH in the SSO login URL
|
||||
when SERVER_ROOT_PATH is set.
|
||||
"""
|
||||
# Arrange
|
||||
mock_request = MagicMock(spec=Request)
|
||||
captured_html = ""
|
||||
|
||||
# Mock environment variables
|
||||
env_vars = {
|
||||
"PROXY_BASE_URL": "https://example.com",
|
||||
"SERVER_ROOT_PATH": "/api/v1",
|
||||
"GOOGLE_CLIENT_ID": "mock_google_client_id", # Enable SSO
|
||||
"DATABASE_URL": "mock_db_url", # Satisfy show_missing_vars_in_env
|
||||
"LITELLM_MASTER_KEY": "mock_master_key", # Satisfy show_missing_vars_in_env
|
||||
}
|
||||
|
||||
# Patch HTMLResponse to capture the content
|
||||
def mock_html_response(content, status_code=200):
|
||||
nonlocal captured_html
|
||||
captured_html = content
|
||||
return MagicMock()
|
||||
|
||||
with patch.dict(os.environ, env_vars):
|
||||
with patch("litellm.proxy.proxy_server.premium_user", True):
|
||||
with patch("litellm.proxy.proxy_server.prisma_client", MagicMock()):
|
||||
with patch("litellm.proxy.proxy_server.master_key", "mock_master_key"):
|
||||
with patch("fastapi.responses.HTMLResponse", side_effect=mock_html_response):
|
||||
# Import the function to test
|
||||
from litellm.proxy.management_endpoints.ui_sso import (
|
||||
serve_login_page,
|
||||
)
|
||||
|
||||
# Act
|
||||
result = await serve_login_page(request=mock_request)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
expected_url = "https://example.com/api/v1/sso/login"
|
||||
assert expected_url in captured_html, f"Expected URL '{expected_url}' not found in HTML content"
|
||||
|
||||
@ -253,3 +253,34 @@ class TestReasoningContentFinalResponse:
|
||||
]
|
||||
assert len(reasoning_items) == 1, "Should have exactly one reasoning item"
|
||||
assert reasoning_items[0].content[0].text == "Reasoning for first answer"
|
||||
|
||||
|
||||
def test_streaming_chunk_id_raw():
|
||||
"""Test that streaming chunk IDs are raw (not encoded) to match OpenAI format"""
|
||||
chunk = ModelResponseStream(
|
||||
id="chunk-123",
|
||||
created=1234567890,
|
||||
model="test-model",
|
||||
object="chat.completion.chunk",
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
index=0,
|
||||
delta=Delta(content="Hello", role="assistant"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
iterator = LiteLLMCompletionStreamingIterator(
|
||||
litellm_custom_stream_wrapper=AsyncMock(),
|
||||
request_input="Test input",
|
||||
responses_api_request={},
|
||||
custom_llm_provider="openai",
|
||||
litellm_metadata={"model_info": {"id": "gpt-4"}},
|
||||
)
|
||||
|
||||
result = iterator._transform_chat_completion_chunk_to_response_api_chunk(chunk)
|
||||
|
||||
# Streaming chunk IDs should be raw (like OpenAI's msg_xxx format)
|
||||
assert result.item_id == "chunk-123" # Should be raw, not encoded
|
||||
assert not result.item_id.startswith("resp_") # Should NOT have resp_ prefix
|
||||
|
||||
@ -844,6 +844,7 @@ async def test_supports_tool_choice():
|
||||
or "o1" in model_name
|
||||
or "o3" in model_name
|
||||
or "mistral" in model_name
|
||||
or "oci" in model_name
|
||||
):
|
||||
continue
|
||||
|
||||
@ -2317,8 +2318,9 @@ def test_block_key_hashing_logic():
|
||||
Test that block_key() function only hashes keys that start with "sk-"
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
from litellm.proxy.utils import hash_token
|
||||
|
||||
|
||||
# Test cases: (input_key, should_be_hashed, expected_output)
|
||||
test_cases = [
|
||||
("sk-1234567890abcdef", True, hash_token("sk-1234567890abcdef")),
|
||||
@ -2394,7 +2396,7 @@ def test_generate_gcp_iam_access_token_import_error():
|
||||
"""
|
||||
# Import the function first, before mocking
|
||||
from litellm._redis import _generate_gcp_iam_access_token
|
||||
|
||||
|
||||
# Mock the import to fail when the function tries to import google.cloud.iam_credentials_v1
|
||||
original_import = __builtins__['__import__']
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[19813,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-9e1803a09e9ae8da.js","498","static/chunks/498-ee02f9b58491d7a9.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","172","static/chunks/172-08ae62d50ce1f0e7.js","931","static/chunks/app/page-0a9a9f137522a76c.js"],"default",1]
|
||||
3:I[6691,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-3523e0e07cf314f6.js","683","static/chunks/683-07087d813e7eeb43.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","172","static/chunks/172-1c7afccd96ceca39.js","931","static/chunks/app/page-1d51309983956823.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[52829,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","418","static/chunks/app/model_hub/page-b26e0d313b582dbf.js"],"default",1]
|
||||
3:I[52829,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","418","static/chunks/app/model_hub/page-b26e0d313b582dbf.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[22775,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-9e1803a09e9ae8da.js","154","static/chunks/154-78c3416dcb61977f.js","162","static/chunks/162-8529572226f208c5.js","172","static/chunks/172-08ae62d50ce1f0e7.js","25","static/chunks/app/model_hub_table/page-d080c5775ebaf3a1.js"],"default",1]
|
||||
3:I[22775,["416","static/chunks/416-ad6bd55a20a586bd.js","90","static/chunks/90-d2b5ed6f7f6e342e.js","866","static/chunks/866-3523e0e07cf314f6.js","154","static/chunks/154-66d79df6143c694f.js","162","static/chunks/162-9e6f5133e328d61f.js","172","static/chunks/172-1c7afccd96ceca39.js","25","static/chunks/app/model_hub_table/page-d080c5775ebaf3a1.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
2:I[19107,[],"ClientPageRoot"]
|
||||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","416","static/chunks/416-ad6bd55a20a586bd.js","154","static/chunks/154-78c3416dcb61977f.js","461","static/chunks/app/onboarding/page-883c32e6b072b842.js"],"default",1]
|
||||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","416","static/chunks/416-ad6bd55a20a586bd.js","154","static/chunks/154-66d79df6143c694f.js","461","static/chunks/app/onboarding/page-7e4cd2bb92dbf9ce.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["rkJYRcYqQ8OBbL83KQCc4",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/fe37e928fd602d9e.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["poVnZDt3J0aYERGVwpJKO",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0dbff0867726409d.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Table, Input } from "antd";
|
||||
import { Text, TextInput } from "@tremor/react";
|
||||
import { Row, Col } from "antd";
|
||||
import { Form, Table } from "antd";
|
||||
import { TextInput } from "@tremor/react";
|
||||
import { Tooltip } from "../atoms/index";
|
||||
|
||||
const ConditionalPublicModelName: React.FC = () => {
|
||||
// Access the form instance
|
||||
const form = Form.useFormInstance();
|
||||
const [tableKey, setTableKey] = useState(0); // Add a key to force table re-render
|
||||
const [tableKey, setTableKey] = useState(0);// Add a key to force table re-render
|
||||
|
||||
// Watch the 'model' field for changes and ensure it's always an array
|
||||
const modelValue = Form.useWatch('model', form) || [];
|
||||
@ -14,7 +13,6 @@ const ConditionalPublicModelName: React.FC = () => {
|
||||
const customModelName = Form.useWatch('custom_model_name', form);
|
||||
const showPublicModelName = !selectedModels.includes('all-wildcard');
|
||||
|
||||
|
||||
// Force table to re-render when custom model name changes
|
||||
useEffect(() => {
|
||||
if (customModelName && selectedModels.includes('custom')) {
|
||||
@ -71,9 +69,39 @@ const ConditionalPublicModelName: React.FC = () => {
|
||||
|
||||
if (!showPublicModelName) return null;
|
||||
|
||||
const publicNameTooltipContent = (
|
||||
<>
|
||||
<div className="mb-2 font-normal">
|
||||
The name you specify in your API calls to LiteLLM Proxy
|
||||
</div>
|
||||
<div className="mb-2 font-normal">
|
||||
<strong>Example:</strong> If you name your public model <code className="bg-gray-700 px-1 py-0.5 rounded text-xs">example-name</code>
|
||||
, and choose <code className="bg-gray-700 px-1 py-0.5 rounded text-xs">openai/qwen-plus-latest</code> as the LiteLLM model
|
||||
</div>
|
||||
<div className="mb-2 font-normal">
|
||||
<strong>Usage:</strong> You make an API call to the LiteLLM proxy with <code className="bg-gray-700 px-1 py-0.5 rounded text-xs">model = "example-name"</code>
|
||||
</div>
|
||||
<div className="font-normal">
|
||||
<strong>Result:</strong> LiteLLM sends <code className="bg-gray-700 px-1 py-0.5 rounded text-xs">qwen-plus-latest</code> to the provider
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const liteLLMModelTooltipContent = (
|
||||
<div>The model name LiteLLM will send to the LLM API</div>
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Public Name',
|
||||
title: (
|
||||
<span className="flex items-center">
|
||||
Public Model Name
|
||||
<Tooltip
|
||||
content={publicNameTooltipContent}
|
||||
width="500px"
|
||||
/>
|
||||
</span>
|
||||
),
|
||||
dataIndex: 'public_name',
|
||||
key: 'public_name',
|
||||
render: (text: string, record: any, index: number) => {
|
||||
@ -90,7 +118,15 @@ const ConditionalPublicModelName: React.FC = () => {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'LiteLLM Model',
|
||||
title: (
|
||||
<span className="flex items-center">
|
||||
LiteLLM Model Name
|
||||
<Tooltip
|
||||
content={liteLLMModelTooltipContent}
|
||||
width="360px"
|
||||
/>
|
||||
</span>
|
||||
),
|
||||
dataIndex: 'litellm_model',
|
||||
key: 'litellm_model',
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ const LiteLLMModelNameField: React.FC<LiteLLMModelNameFieldProps> = ({
|
||||
<>
|
||||
<Form.Item
|
||||
label="LiteLLM Model Name(s)"
|
||||
tooltip="Actual model name used for making litellm.completion() / litellm.embedding() call."
|
||||
tooltip="The model name LiteLLM will send to the LLM API"
|
||||
className="mb-0"
|
||||
>
|
||||
<Form.Item
|
||||
@ -145,9 +145,9 @@ const LiteLLMModelNameField: React.FC<LiteLLMModelNameFieldProps> = ({
|
||||
</Form.Item>
|
||||
<Row>
|
||||
<Col span={10}></Col>
|
||||
<Col span={10}>
|
||||
<Col span={14}>
|
||||
<Text className="mb-3 mt-1">
|
||||
Actual model name used for making litellm.completion() call. We loadbalance models with the same public name
|
||||
The model name LiteLLM will send to the LLM API
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
70
ui/litellm-dashboard/src/components/atoms/Tooltip.tsx
Normal file
70
ui/litellm-dashboard/src/components/atoms/Tooltip.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { useState, useRef } from "react"
|
||||
import { QuestionCircleOutlined } from "@ant-design/icons"
|
||||
|
||||
interface TooltipProps {
|
||||
content: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
width?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const Tooltip: React.FC<TooltipProps> = ({ content, children, width = "auto", className = "" }) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
const [tooltipPosition, setTooltipPosition] = useState<"top" | "bottom">("top")
|
||||
const tooltipRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Function to check if tooltip would fit above
|
||||
const checkTooltipPosition = () => {
|
||||
if (tooltipRef.current) {
|
||||
const rect = tooltipRef.current.getBoundingClientRect()
|
||||
const tooltipHeight = 300 // Approximate height of the tooltip
|
||||
const spaceAbove = rect.top
|
||||
const spaceBelow = window.innerHeight - rect.bottom
|
||||
|
||||
if (spaceAbove < tooltipHeight && spaceBelow > tooltipHeight) {
|
||||
setTooltipPosition("bottom")
|
||||
} else {
|
||||
setTooltipPosition("top")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative inline-block" ref={tooltipRef}>
|
||||
{children || (
|
||||
<QuestionCircleOutlined
|
||||
className="ml-1 text-gray-500 cursor-help"
|
||||
onMouseEnter={() => {
|
||||
checkTooltipPosition()
|
||||
setShowTooltip(true)
|
||||
}}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
/>
|
||||
)}
|
||||
{showTooltip && (
|
||||
<div
|
||||
className={`absolute left-1/2 -translate-x-1/2 z-50 bg-black/90 text-white p-2 rounded-md text-sm font-normal shadow-lg ${className}`}
|
||||
style={{
|
||||
[tooltipPosition === "top" ? "bottom" : "top"]: "100%",
|
||||
width: width,
|
||||
marginBottom: tooltipPosition === "top" ? "8px" : "0",
|
||||
marginTop: tooltipPosition === "bottom" ? "8px" : "0",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<div
|
||||
className="absolute left-1/2 -translate-x-1/2 w-0 h-0"
|
||||
style={{
|
||||
top: tooltipPosition === "top" ? "100%" : "auto",
|
||||
bottom: tooltipPosition === "bottom" ? "100%" : "auto",
|
||||
borderTop: tooltipPosition === "top" ? "6px solid rgba(0, 0, 0, 0.9)" : "6px solid transparent",
|
||||
borderBottom: tooltipPosition === "bottom" ? "6px solid rgba(0, 0, 0, 0.9)" : "6px solid transparent",
|
||||
borderLeft: "6px solid transparent",
|
||||
borderRight: "6px solid transparent",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
ui/litellm-dashboard/src/components/atoms/index.ts
Normal file
1
ui/litellm-dashboard/src/components/atoms/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Tooltip } from './Tooltip';
|
||||
@ -1,3 +1,10 @@
|
||||
// Shared date formatter for daily activity endpoints
|
||||
export const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
/**
|
||||
* Helper file for calls being made to proxy
|
||||
*/
|
||||
@ -1457,13 +1464,6 @@ export const userDailyActivityCall = async (
|
||||
? `${proxyBaseUrl}/user/daily/activity`
|
||||
: `/user/daily/activity`;
|
||||
const queryParams = new URLSearchParams();
|
||||
// Format dates as YYYY-MM-DD for the API
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
queryParams.append("start_date", formatDate(startTime));
|
||||
queryParams.append("end_date", formatDate(endTime));
|
||||
queryParams.append("page_size", "1000");
|
||||
@ -1510,13 +1510,6 @@ export const tagDailyActivityCall = async (
|
||||
? `${proxyBaseUrl}/tag/daily/activity`
|
||||
: `/tag/daily/activity`;
|
||||
const queryParams = new URLSearchParams();
|
||||
// Format dates as YYYY-MM-DD for the API
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
queryParams.append("start_date", formatDate(startTime));
|
||||
queryParams.append("end_date", formatDate(endTime));
|
||||
queryParams.append("page_size", "1000");
|
||||
@ -1566,13 +1559,6 @@ export const teamDailyActivityCall = async (
|
||||
? `${proxyBaseUrl}/team/daily/activity`
|
||||
: `/team/daily/activity`;
|
||||
const queryParams = new URLSearchParams();
|
||||
// Format dates as YYYY-MM-DD for the API
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
queryParams.append("start_date", formatDate(startTime));
|
||||
queryParams.append("end_date", formatDate(endTime));
|
||||
queryParams.append("page_size", "1000");
|
||||
@ -3198,6 +3184,55 @@ export interface User {
|
||||
[key: string]: string; // Include any other potential keys in the dictionary
|
||||
}
|
||||
|
||||
export const userDailyActivityAggregatedCall = async (
|
||||
accessToken: String,
|
||||
startTime: Date,
|
||||
endTime: Date
|
||||
) => {
|
||||
/**
|
||||
* Get aggregated daily user activity (no pagination)
|
||||
*/
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/user/daily/activity/aggregated`
|
||||
: `/user/daily/activity/aggregated`;
|
||||
const queryParams = new URLSearchParams();
|
||||
// Format dates as YYYY-MM-DD for the API
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
queryParams.append("start_date", formatDate(startTime));
|
||||
queryParams.append("end_date", formatDate(endTime));
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch aggregated user daily activity:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const userGetAllUsersCall = async (
|
||||
accessToken: String,
|
||||
role: String
|
||||
|
||||
@ -34,7 +34,7 @@ import {
|
||||
import AdvancedDatePicker from "./shared/advanced_date_picker"
|
||||
import { AreaChart } from "@tremor/react"
|
||||
|
||||
import { userDailyActivityCall, tagListCall } from "./networking"
|
||||
import { userDailyActivityCall, userDailyActivityAggregatedCall, tagListCall } from "./networking"
|
||||
import { Tag } from "./tag_management/types"
|
||||
import ViewUserSpend from "./view_user_spend"
|
||||
import TopKeyView from "./top_key_view"
|
||||
@ -304,16 +304,22 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({ accessToken, userRole, user
|
||||
const endTime = new Date(dateValue.to)
|
||||
|
||||
try {
|
||||
// Get first page
|
||||
// Prefer aggregated endpoint to avoid many page requests
|
||||
try {
|
||||
const aggregated = await userDailyActivityAggregatedCall(accessToken, startTime, endTime)
|
||||
setUserSpendData(aggregated)
|
||||
return
|
||||
} catch (e) {
|
||||
// Fallback to paginated calls if aggregated endpoint is unavailable
|
||||
}
|
||||
|
||||
const firstPageData = await userDailyActivityCall(accessToken, startTime, endTime)
|
||||
|
||||
// If only one page, just set the data
|
||||
if (firstPageData.metadata.total_pages <= 1) {
|
||||
setUserSpendData(firstPageData)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch all pages
|
||||
const allResults = [...firstPageData.results]
|
||||
const aggregatedMetadata = { ...firstPageData.metadata }
|
||||
|
||||
@ -329,7 +335,6 @@ const NewUsagePage: React.FC<NewUsagePageProps> = ({ accessToken, userRole, user
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all results with the first page's metadata
|
||||
setUserSpendData({
|
||||
results: allResults,
|
||||
metadata: aggregatedMetadata,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user