mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
lint url encoding
Some checks failed
Unit Tests: Caching (Redis) / caching-redis (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Some checks failed
Unit Tests: Caching (Redis) / caching-redis (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
This commit is contained in:
parent
c05de83f1c
commit
aa294dfb4b
62 changed files with 967 additions and 139 deletions
|
|
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cas
|
|||
import httpx
|
||||
from httpx import Headers, Response
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.batches.transformation import BaseBatchesConfig
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.types.llms.openai import AllMessageValues, CreateBatchRequest
|
||||
|
|
@ -122,7 +123,7 @@ class AnthropicBatchesConfig(BaseBatchesConfig):
|
|||
Complete URL for Anthropic batch retrieval: {api_base}/v1/messages/batches/{batch_id}
|
||||
"""
|
||||
api_base = api_base or self.anthropic_model_info.get_api_base(api_base)
|
||||
return f"{api_base.rstrip('/')}/v1/messages/batches/{batch_id}"
|
||||
return f"{api_base.rstrip('/')}/v1/messages/batches/{encode_path_segment(batch_id)}"
|
||||
|
||||
def transform_retrieve_batch_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import litellm
|
|||
from litellm._logging import verbose_logger
|
||||
from litellm._uuid import uuid
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
from litellm.types.llms.openai import (
|
||||
FileContentRequest,
|
||||
|
|
@ -89,7 +90,7 @@ class AnthropicFilesHandler:
|
|||
raise ValueError("Missing Anthropic API Key")
|
||||
|
||||
# Construct the Anthropic batch results URL
|
||||
results_url = f"{api_base.rstrip('/')}/v1/messages/batches/{batch_id}/results"
|
||||
results_url = f"{api_base.rstrip('/')}/v1/messages/batches/{encode_path_segment(batch_id)}/results"
|
||||
|
||||
# Prepare headers
|
||||
headers = {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import httpx
|
|||
from openai.types.file_deleted import FileDeleted
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.files.transformation import (
|
||||
BaseFilesConfig,
|
||||
|
|
@ -185,7 +186,7 @@ class AnthropicFilesConfig(BaseFilesConfig):
|
|||
AnthropicModelInfo.get_api_base(litellm_params.get("api_base"))
|
||||
or ANTHROPIC_FILES_API_BASE
|
||||
)
|
||||
return f"{api_base.rstrip('/')}/v1/files/{file_id}", {}
|
||||
return f"{api_base.rstrip('/')}/v1/files/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_retrieve_file_response(
|
||||
self,
|
||||
|
|
@ -206,7 +207,7 @@ class AnthropicFilesConfig(BaseFilesConfig):
|
|||
AnthropicModelInfo.get_api_base(litellm_params.get("api_base"))
|
||||
or ANTHROPIC_FILES_API_BASE
|
||||
)
|
||||
return f"{api_base.rstrip('/')}/v1/files/{file_id}", {}
|
||||
return f"{api_base.rstrip('/')}/v1/files/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_delete_file_response(
|
||||
self,
|
||||
|
|
@ -268,7 +269,10 @@ class AnthropicFilesConfig(BaseFilesConfig):
|
|||
AnthropicModelInfo.get_api_base(litellm_params.get("api_base"))
|
||||
or ANTHROPIC_FILES_API_BASE
|
||||
)
|
||||
return f"{api_base.rstrip('/')}/v1/files/{file_id}/content", {}
|
||||
return (
|
||||
f"{api_base.rstrip('/')}/v1/files/{encode_path_segment(file_id)}/content",
|
||||
{},
|
||||
)
|
||||
|
||||
def transform_file_content_response(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Any, Dict, Optional, Tuple
|
|||
import httpx
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.skills.transformation import (
|
||||
BaseSkillsAPIConfig,
|
||||
LiteLLMLoggingObj,
|
||||
|
|
@ -81,7 +82,7 @@ class AnthropicSkillsConfig(BaseSkillsAPIConfig):
|
|||
api_base = AnthropicModelInfo.get_api_base()
|
||||
|
||||
if skill_id:
|
||||
return f"{api_base}/v1/skills/{skill_id}"
|
||||
return f"{api_base}/v1/skills/{encode_path_segment(skill_id)}"
|
||||
return f"{api_base}/v1/{endpoint}"
|
||||
|
||||
def transform_create_skill_request(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.caching.caching import DualCache
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.openai.common_utils import BaseOpenAILLM
|
||||
from litellm.secret_managers.get_azure_ad_token_provider import (
|
||||
|
|
@ -211,7 +212,7 @@ def get_azure_ad_token_from_oidc(
|
|||
client = litellm.module_level_client
|
||||
|
||||
req_token = client.post(
|
||||
f"{azure_authority_host}/{azure_tenant_id}/oauth2/v2.0/token",
|
||||
f"{azure_authority_host}/{encode_path_segment(azure_tenant_id)}/oauth2/v2.0/token",
|
||||
data={
|
||||
"client_id": azure_client_id,
|
||||
"grant_type": "client_credentials",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from openai.types.responses import ResponseReasoningItem
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.types.llms.openai import *
|
||||
from litellm.types.responses.main import *
|
||||
|
|
@ -201,7 +202,7 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
# Insert the response_id at the end of the path component
|
||||
# Remove trailing slash if present to avoid double slashes
|
||||
path = parsed_url.path.rstrip("/")
|
||||
new_path = f"{path}/{response_id}"
|
||||
new_path = f"{path}/{encode_path_segment(response_id)}"
|
||||
|
||||
# Reconstruct the URL with all original components but with the modified path
|
||||
constructed_url = urlunparse(
|
||||
|
|
@ -322,7 +323,7 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
# Insert the response_id and /cancel at the end of the path component
|
||||
# Remove trailing slash if present to avoid double slashes
|
||||
path = parsed_url.path.rstrip("/")
|
||||
new_path = f"{path}/{response_id}/cancel"
|
||||
new_path = f"{path}/{encode_path_segment(response_id)}/cancel"
|
||||
|
||||
# Reconstruct the URL with all original components but with the modified path
|
||||
cancel_url = urlunparse(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from litellm.llms.azure_ai.agents.transformation import (
|
|||
AzureAIAgentsConfig,
|
||||
AzureAIAgentsError,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -75,20 +76,20 @@ class AzureAIAgentsHandler:
|
|||
def _build_messages_url(
|
||||
self, api_base: str, thread_id: str, api_version: str
|
||||
) -> str:
|
||||
return f"{api_base}/threads/{thread_id}/messages?api-version={api_version}"
|
||||
return f"{api_base}/threads/{encode_path_segment(thread_id)}/messages?api-version={api_version}"
|
||||
|
||||
def _build_runs_url(self, api_base: str, thread_id: str, api_version: str) -> str:
|
||||
return f"{api_base}/threads/{thread_id}/runs?api-version={api_version}"
|
||||
return f"{api_base}/threads/{encode_path_segment(thread_id)}/runs?api-version={api_version}"
|
||||
|
||||
def _build_run_status_url(
|
||||
self, api_base: str, thread_id: str, run_id: str, api_version: str
|
||||
) -> str:
|
||||
return f"{api_base}/threads/{thread_id}/runs/{run_id}?api-version={api_version}"
|
||||
return f"{api_base}/threads/{encode_path_segment(thread_id)}/runs/{encode_path_segment(run_id)}?api-version={api_version}"
|
||||
|
||||
def _build_list_messages_url(
|
||||
self, api_base: str, thread_id: str, api_version: str
|
||||
) -> str:
|
||||
return f"{api_base}/threads/{thread_id}/messages?api-version={api_version}"
|
||||
return f"{api_base}/threads/{encode_path_segment(thread_id)}/messages?api-version={api_version}"
|
||||
|
||||
def _build_create_thread_and_run_url(self, api_base: str, api_version: str) -> str:
|
||||
"""URL for the create-thread-and-run endpoint (supports streaming)."""
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from litellm.constants import (
|
|||
AZURE_DOCUMENT_INTELLIGENCE_DEFAULT_DPI,
|
||||
AZURE_OPERATION_POLLING_TIMEOUT,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.ocr.transformation import (
|
||||
BaseOCRConfig,
|
||||
DocumentType,
|
||||
|
|
@ -220,10 +221,7 @@ class AzureDocumentIntelligenceOCRConfig(BaseOCRConfig):
|
|||
|
||||
# Azure Document Intelligence analyze endpoint
|
||||
# Note: API version 2024-11-30+ uses /documentintelligence/ (not /formrecognizer/)
|
||||
url = (
|
||||
f"{api_base}/documentintelligence/documentModels/{model_id}:analyze"
|
||||
f"?api-version={AZURE_DOCUMENT_INTELLIGENCE_API_VERSION}"
|
||||
)
|
||||
url = f"{api_base}/documentintelligence/documentModels/{encode_path_segment(model_id)}:analyze?api-version={AZURE_DOCUMENT_INTELLIGENCE_API_VERSION}"
|
||||
|
||||
# Azure DI accepts `pages` as a query param (1-based, e.g. "1-3,5").
|
||||
# `optional_params` has already been normalized in `map_ocr_params`.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import httpx
|
|||
|
||||
import litellm
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.vector_stores import (
|
||||
|
|
@ -139,7 +140,7 @@ class AzureAIVectorStoreConfig(BaseVectorStoreConfig, BaseAzureLLM):
|
|||
|
||||
# Azure AI Search endpoint for search
|
||||
index_name = vector_store_id # vector_store_id is the index name
|
||||
url = f"{api_base}/indexes/{index_name}/docs/search?api-version=2024-07-01"
|
||||
url = f"{api_base}/indexes/{encode_path_segment(index_name)}/docs/search?api-version=2024-07-01"
|
||||
|
||||
# Build the request body for Azure AI Search with vector search
|
||||
request_body = {
|
||||
|
|
|
|||
37
litellm/llms/base_llm/_url_utils.py
Normal file
37
litellm/llms/base_llm/_url_utils.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""URL-encoding helpers for provider transformations."""
|
||||
|
||||
from typing import Optional
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
def _reject_dot_segment(segment: str, full: str) -> None:
|
||||
if segment in ("..", "."):
|
||||
raise ValueError(f"Illegal path segment in identifier: {full!r}")
|
||||
|
||||
|
||||
def encode_path_segment(segment: Optional[str], safe: str = "") -> str:
|
||||
"""Percent-encode a single path segment. Raises on empty / ``None`` / ``.`` / ``..``.
|
||||
|
||||
``safe`` is forwarded to ``urllib.parse.quote`` for callers that need to
|
||||
preserve specific characters (e.g. ``:`` in Bedrock model IDs).
|
||||
"""
|
||||
if segment is None or segment == "":
|
||||
raise ValueError("identifier is required, got empty or None")
|
||||
str_segment = str(segment)
|
||||
_reject_dot_segment(str_segment, str_segment)
|
||||
return quote(str_segment, safe=safe)
|
||||
|
||||
|
||||
def encode_url_path(path: Optional[str]) -> str:
|
||||
"""Percent-encode a multi-segment path; preserves ``/`` and ``@``.
|
||||
|
||||
``None`` / ``""`` → ``""``. Rejects ``.``, ``..``, or empty segments.
|
||||
"""
|
||||
if path is None or path == "":
|
||||
return ""
|
||||
str_path = str(path)
|
||||
for segment in str_path.split("/"):
|
||||
if segment == "":
|
||||
raise ValueError(f"Empty path segment in identifier: {str_path!r}")
|
||||
_reject_dot_segment(segment, str_path)
|
||||
return quote(str_path, safe="/@")
|
||||
|
|
@ -15,6 +15,7 @@ from litellm._uuid import uuid
|
|||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
convert_content_list_to_str,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.bedrock.common_utils import BedrockError
|
||||
|
|
@ -98,7 +99,7 @@ class AmazonInvokeAgentConfig(BaseConfig, BaseAWSLLM):
|
|||
agent_id, agent_alias_id = self._get_agent_id_and_alias_id(model)
|
||||
session_id = self._get_session_id(optional_params)
|
||||
|
||||
endpoint_url = f"{endpoint_url}/agents/{agent_id}/agentAliases/{agent_alias_id}/sessions/{session_id}/text"
|
||||
endpoint_url = f"{endpoint_url}/agents/{encode_path_segment(agent_id)}/agentAliases/{encode_path_segment(agent_alias_id)}/sessions/{encode_path_segment(session_id)}/text"
|
||||
|
||||
return endpoint_url
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.bedrock.common_utils import BedrockError
|
||||
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
|
|
@ -98,6 +99,9 @@ class AmazonBedrockOpenAIConfig(OpenAIGPTConfig, BaseAWSLLM):
|
|||
|
||||
# Encode model ID for ARNs (e.g., :imported-model/ -> :imported-model%2F)
|
||||
model_id = CommonUtils.encode_bedrock_runtime_modelid_arn(model_id)
|
||||
if "arn:aws:" not in model_id:
|
||||
# Bedrock model IDs use ':' for version (e.g. amazon.nova-pro-v1:0)
|
||||
model_id = encode_path_segment(model_id, safe=":")
|
||||
|
||||
# Build the invoke URL
|
||||
if stream:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ to AWS Bedrock's CountTokens API format and vice versa.
|
|||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.bedrock.common_utils import get_bedrock_base_model
|
||||
|
||||
|
|
@ -207,7 +208,10 @@ class BedrockCountTokensConfig(BaseAWSLLM):
|
|||
aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint,
|
||||
aws_region_name=aws_region_name,
|
||||
)
|
||||
endpoint = f"{base_url}/model/{model_id}/count-tokens"
|
||||
# Bedrock model IDs use ':' for version (e.g. amazon.nova-pro-v1:0)
|
||||
endpoint = (
|
||||
f"{base_url}/model/{encode_path_segment(model_id, safe=':')}/count-tokens"
|
||||
)
|
||||
|
||||
return endpoint
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from litellm._logging import verbose_logger
|
|||
from litellm._uuid import uuid
|
||||
from litellm.files.utils import FilesAPIUtils
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.files.transformation import (
|
||||
BaseFilesConfig,
|
||||
|
|
@ -195,7 +196,7 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig):
|
|||
or f"https://s3.{aws_region_name}.amazonaws.com"
|
||||
)
|
||||
|
||||
return f"{s3_endpoint_url}/{bucket_name}/{object_name}"
|
||||
return f"{s3_endpoint_url}/{encode_path_segment(bucket_name)}/{encode_url_path(object_name)}"
|
||||
|
||||
def get_supported_openai_params(
|
||||
self, model: str
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from urllib.parse import urlparse
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.types.integrations.rag.bedrock_knowledgebase import (
|
||||
|
|
@ -206,7 +207,7 @@ class BedrockVectorStoreConfig(BaseVectorStoreConfig, BaseAWSLLM):
|
|||
if isinstance(query, list):
|
||||
query = " ".join(query)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}/retrieve"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}/retrieve"
|
||||
|
||||
request_body: Dict[str, Any] = {
|
||||
"retrievalQuery": BedrockKBRetrievalQuery(text=query),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import httpx
|
|||
|
||||
from litellm.litellm_core_utils.exception_mapping_utils import exception_type
|
||||
from litellm.litellm_core_utils.logging_utils import track_llm_api_timing
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
|
|
@ -149,7 +150,7 @@ class BytezChatConfig(BaseConfig):
|
|||
litellm_params: dict,
|
||||
stream: Optional[bool] = None,
|
||||
) -> str:
|
||||
return f"{API_BASE}/{model}"
|
||||
return f"{API_BASE}/{encode_url_path(model)}"
|
||||
|
||||
def transform_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import AsyncIterator, Iterator, List, Optional, Union
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator
|
||||
from litellm.llms.base_llm.chat.transformation import (
|
||||
BaseConfig,
|
||||
|
|
@ -89,7 +90,7 @@ class CloudflareChatConfig(BaseConfig):
|
|||
api_base = (
|
||||
f"https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run/"
|
||||
)
|
||||
return api_base + model
|
||||
return api_base + encode_url_path(model)
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> List[str]:
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Type, Union
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
HTTPHandler,
|
||||
|
|
@ -72,7 +73,9 @@ def _build_url(
|
|||
|
||||
# Substitute path parameters
|
||||
for param, value in path_params.items():
|
||||
path_template = path_template.replace(f"{{{param}}}", value)
|
||||
path_template = path_template.replace(
|
||||
f"{{{param}}}", encode_path_segment(value)
|
||||
)
|
||||
|
||||
# Parse the api_base to extract existing query params
|
||||
parsed_base = httpx.URL(api_base)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from litellm._logging import _redact_string, verbose_logger
|
|||
from litellm.anthropic_beta_headers_manager import update_headers_with_filtered_beta
|
||||
from litellm.constants import REALTIME_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES
|
||||
from litellm.litellm_core_utils.realtime_streaming import RealTimeStreaming
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.anthropic_messages.transformation import (
|
||||
BaseAnthropicMessagesConfig,
|
||||
)
|
||||
|
|
@ -8907,7 +8908,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
|
|
@ -8974,7 +8975,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
|
|
@ -9173,7 +9174,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
request_body: Dict[str, Any] = dict(vector_store_update_optional_params)
|
||||
|
||||
|
|
@ -9256,7 +9257,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
request_body: Dict[str, Any] = dict(vector_store_update_optional_params)
|
||||
|
||||
|
|
@ -9322,7 +9323,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
|
|
@ -9387,7 +9388,7 @@ class BaseLLMHTTPHandler:
|
|||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url = f"{api_base}/{vector_store_id}"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}"
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from httpx import Headers
|
|||
|
||||
import litellm
|
||||
from litellm.types.utils import all_litellm_params
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.text_to_speech.transformation import (
|
||||
BaseTextToSpeechConfig,
|
||||
|
|
@ -321,7 +322,7 @@ class ElevenLabsTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
"ElevenLabs voice_id is required. Pass `voice` when calling `litellm.speech()`."
|
||||
)
|
||||
|
||||
url = f"{base_url}{self.TTS_ENDPOINT_PATH}/{voice_id}"
|
||||
url = f"{base_url}{self.TTS_ENDPOINT_PATH}/{encode_path_segment(voice_id)}"
|
||||
|
||||
query_params = litellm_params.get(self.ELEVENLABS_QUERY_PARAMS_KEY, {})
|
||||
if query_params:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
|
|||
from litellm.litellm_core_utils.llm_response_utils.get_headers import (
|
||||
get_response_headers,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import (
|
||||
AllMessageValues,
|
||||
|
|
@ -438,7 +439,7 @@ class FireworksAIConfig(OpenAIGPTConfig):
|
|||
if base.endswith("/v1"):
|
||||
base = base[: -len("/v1")]
|
||||
response = litellm.module_level_client.get(
|
||||
url=f"{base}/v1/accounts/{account_id}/models",
|
||||
url=f"{base}/v1/accounts/{encode_path_segment(account_id)}/models",
|
||||
headers={"Authorization": f"Bearer {api_key}"},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ class GoogleAIStudioTokenCounter:
|
|||
Construct the URL for the Google Gen AI Studio countTokens endpoint.
|
||||
"""
|
||||
base_url = api_base or "https://generativelanguage.googleapis.com"
|
||||
return f"{base_url}/v1beta/models/{model}:countTokens"
|
||||
return f"{base_url}/v1beta/models/{encode_url_path(model)}:countTokens"
|
||||
|
||||
async def validate_environment(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from openai.types.file_deleted import FileDeleted
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.files.transformation import (
|
||||
BaseFilesConfig,
|
||||
LiteLLMLoggingObj,
|
||||
|
|
@ -232,7 +233,7 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig):
|
|||
)
|
||||
api_base = api_base.rstrip("/")
|
||||
|
||||
url = f"{api_base}/v1beta/{file_part}"
|
||||
url = f"{api_base}/v1beta/{encode_url_path(file_part)}"
|
||||
|
||||
# API key is passed via x-goog-api-key header (set in validate_environment)
|
||||
return url, {}
|
||||
|
|
@ -346,7 +347,7 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig):
|
|||
file_name = file_id if file_id.startswith("files/") else f"files/{file_id}"
|
||||
|
||||
# Construct the delete URL
|
||||
url = f"{api_base}/v1beta/{file_name}"
|
||||
url = f"{api_base}/v1beta/{encode_url_path(file_name)}"
|
||||
|
||||
# Add API key as header (Google AI Studio uses x-goog-api-key header)
|
||||
params: dict = {}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import httpx
|
|||
from httpx._types import RequestFiles
|
||||
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.images.main import ImageEditOptionalRequestParams
|
||||
|
|
@ -79,7 +80,7 @@ class GeminiImageEditConfig(BaseImageEditConfig):
|
|||
api_base or get_secret_str("GEMINI_API_BASE") or self.DEFAULT_BASE_URL
|
||||
)
|
||||
base_url = base_url.rstrip("/")
|
||||
return f"{base_url}/models/{model}:generateContent"
|
||||
return f"{base_url}/models/{encode_url_path(model)}:generateContent"
|
||||
|
||||
def transform_image_edit_request( # type: ignore[override]
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
|
@ -127,11 +128,12 @@ class GoogleImageGenConfig(BaseImageGenerationConfig):
|
|||
complete_url = complete_url.rstrip("/")
|
||||
|
||||
# Gemini Flash Image Preview models use generateContent endpoint
|
||||
encoded_model = encode_url_path(model)
|
||||
if "gemini" in model:
|
||||
complete_url = f"{complete_url}/models/{model}:generateContent"
|
||||
complete_url = f"{complete_url}/models/{encoded_model}:generateContent"
|
||||
else:
|
||||
# All other Imagen models use predict endpoint
|
||||
complete_url = f"{complete_url}/models/{model}:predict"
|
||||
complete_url = f"{complete_url}/models/{encoded_model}:predict"
|
||||
|
||||
return complete_url
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import httpx
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.core_helpers import process_response_headers
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.interactions.transformation import BaseInteractionsAPIConfig
|
||||
from litellm.llms.gemini.common_utils import GeminiError, GeminiModelInfo
|
||||
from litellm.types.interactions import (
|
||||
|
|
@ -206,7 +207,7 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
|
|||
if not GeminiModelInfo.get_api_key(litellm_params.api_key):
|
||||
raise ValueError("Google API key is required")
|
||||
return (
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{interaction_id}",
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{encode_path_segment(interaction_id)}",
|
||||
{},
|
||||
)
|
||||
|
||||
|
|
@ -239,7 +240,7 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
|
|||
if not GeminiModelInfo.get_api_key(litellm_params.api_key):
|
||||
raise ValueError("Google API key is required")
|
||||
return (
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{interaction_id}",
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{encode_path_segment(interaction_id)}",
|
||||
{},
|
||||
)
|
||||
|
||||
|
|
@ -269,7 +270,7 @@ class GoogleAIStudioInteractionsConfig(BaseInteractionsAPIConfig):
|
|||
if not GeminiModelInfo.get_api_key(litellm_params.api_key):
|
||||
raise ValueError("Google API key is required")
|
||||
return (
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{interaction_id}:cancel",
|
||||
f"{resolved_api_base}/{self.api_version}/interactions/{encode_path_segment(interaction_id)}:cancel",
|
||||
{},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.llms.gemini.common_utils import (
|
||||
GeminiError,
|
||||
|
|
@ -137,7 +138,7 @@ class GeminiVectorStoreConfig(BaseVectorStoreConfig):
|
|||
api_key = litellm_params.get("api_key") or GeminiModelInfo.get_api_key()
|
||||
if not api_key:
|
||||
raise ValueError("GEMINI_API_KEY or GOOGLE_API_KEY is required")
|
||||
url = f"{api_base}/models/{model}:generateContent"
|
||||
url = f"{api_base}/models/{encode_url_path(model)}:generateContent"
|
||||
|
||||
# Build file_search tool configuration (using snake_case as per Gemini docs)
|
||||
file_search_config: Dict[str, Any] = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from httpx._types import RequestFiles
|
|||
import litellm
|
||||
from litellm.constants import DEFAULT_GOOGLE_VIDEO_DURATION_SECONDS
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.videos.transformation import BaseVideoConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.gemini import (
|
||||
|
|
@ -245,7 +246,7 @@ class GeminiVideoConfig(BaseVideoConfig):
|
|||
return api_base.rstrip("/")
|
||||
|
||||
model_name = model.replace("gemini/", "")
|
||||
url = f"{api_base.rstrip('/')}/v1beta/models/{model_name}:predictLongRunning"
|
||||
url = f"{api_base.rstrip('/')}/v1beta/models/{encode_url_path(model_name)}:predictLongRunning"
|
||||
|
||||
return url
|
||||
|
||||
|
|
@ -376,7 +377,7 @@ class GeminiVideoConfig(BaseVideoConfig):
|
|||
GET https://generativelanguage.googleapis.com/v1beta/{operation_name}
|
||||
"""
|
||||
operation_name = extract_original_video_id(video_id)
|
||||
url = f"{api_base.rstrip('/')}/v1beta/{operation_name}"
|
||||
url = f"{api_base.rstrip('/')}/v1beta/{encode_url_path(operation_name)}"
|
||||
params: Dict[str, Any] = {}
|
||||
|
||||
return url, params
|
||||
|
|
@ -451,7 +452,7 @@ class GeminiVideoConfig(BaseVideoConfig):
|
|||
"""
|
||||
operation_name = extract_original_video_id(video_id)
|
||||
|
||||
status_url = f"{api_base.rstrip('/')}/v1beta/{operation_name}"
|
||||
status_url = f"{api_base.rstrip('/')}/v1beta/{encode_url_path(operation_name)}"
|
||||
client = litellm.module_level_client
|
||||
status_response = client.get(url=status_url, headers=headers)
|
||||
status_response.raise_for_status()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ if TYPE_CHECKING:
|
|||
else:
|
||||
LoggingClass = Any
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
|
||||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
|
|
@ -114,7 +115,7 @@ class HuggingFaceChatConfig(OpenAIGPTConfig):
|
|||
if "/" in remaining:
|
||||
provider = first_part
|
||||
if provider == "hf-inference":
|
||||
route = f"{provider}/models/{model}/v1/chat/completions"
|
||||
route = f"{provider}/models/{encode_url_path(model)}/v1/chat/completions"
|
||||
elif provider == "novita":
|
||||
route = f"{provider}/v3/openai/chat/completions"
|
||||
elif provider == "fireworks-ai":
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Literal, Optional, Union
|
|||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
|
||||
HF_HUB_URL = "https://huggingface.co"
|
||||
|
|
@ -76,7 +77,7 @@ def _fetch_inference_provider_mapping(model: str) -> dict:
|
|||
if os.getenv("HUGGINGFACE_API_KEY"):
|
||||
headers["Authorization"] = f"Bearer {os.getenv('HUGGINGFACE_API_KEY')}"
|
||||
|
||||
path = f"{HF_HUB_URL}/api/models/{model}"
|
||||
path = f"{HF_HUB_URL}/api/models/{encode_url_path(model)}"
|
||||
params = {"expand": ["inferenceProviderMapping"]}
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import httpx
|
|||
|
||||
import litellm
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
HTTPHandler,
|
||||
|
|
@ -40,7 +41,7 @@ def get_hf_task_embedding_for_model(
|
|||
)
|
||||
http_client = HTTPHandler(concurrent_limit=1)
|
||||
|
||||
model_info = http_client.get(url=f"{api_base}/api/models/{model}")
|
||||
model_info = http_client.get(url=f"{api_base}/api/models/{encode_url_path(model)}")
|
||||
|
||||
model_info_dict = model_info.json()
|
||||
|
||||
|
|
@ -65,7 +66,9 @@ async def async_get_hf_task_embedding_for_model(
|
|||
llm_provider=litellm.LlmProviders.HUGGINGFACE,
|
||||
)
|
||||
|
||||
model_info = await http_client.get(url=f"{api_base}/api/models/{model}")
|
||||
model_info = await http_client.get(
|
||||
url=f"{api_base}/api/models/{encode_url_path(model)}"
|
||||
)
|
||||
|
||||
model_info_dict = model_info.json()
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import (
|
|||
prompt_factory,
|
||||
)
|
||||
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
|
@ -345,7 +346,9 @@ class HuggingFaceEmbeddingConfig(BaseConfig):
|
|||
elif "HUGGINGFACE_API_BASE" in os.environ:
|
||||
completion_url = os.getenv("HUGGINGFACE_API_BASE", "")
|
||||
else:
|
||||
completion_url = f"https://api-inference.huggingface.co/models/{model}"
|
||||
completion_url = (
|
||||
f"https://api-inference.huggingface.co/models/{encode_url_path(model)}"
|
||||
)
|
||||
|
||||
return completion_url
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from openai.types.file_deleted import FileDeleted
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.files.transformation import (
|
||||
BaseFilesConfig,
|
||||
|
|
@ -306,7 +307,7 @@ class ManusFilesConfig(BaseFilesConfig):
|
|||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
return f"{api_base}/{file_id}", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_retrieve_file_response(
|
||||
self,
|
||||
|
|
@ -336,7 +337,7 @@ class ManusFilesConfig(BaseFilesConfig):
|
|||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
return f"{api_base}/{file_id}", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_delete_file_response(
|
||||
self,
|
||||
|
|
@ -422,7 +423,7 @@ class ManusFilesConfig(BaseFilesConfig):
|
|||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
return f"{api_base}/{file_id}/content", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}/content", {}
|
||||
|
||||
def transform_file_content_response(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from litellm.litellm_core_utils.core_helpers import process_response_headers
|
|||
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
|
||||
_safe_convert_created_field,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.openai.common_utils import OpenAIError
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
|
@ -270,7 +271,7 @@ class ManusResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
|
||||
Reference: https://open.manus.im/docs/openai-compatibility
|
||||
"""
|
||||
url = f"{api_base}/{response_id}"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from typing_extensions import Required, TypedDict
|
|||
import litellm
|
||||
from litellm._uuid import uuid
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.base_llm._url_utils import encode_url_path
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
|
@ -92,7 +93,7 @@ class NvidiaNimRerankConfig(BaseRerankConfig):
|
|||
# Strip nvidia_nim/ prefix from model name if present
|
||||
clean_model = self._get_clean_model_name(model)
|
||||
|
||||
return f"{api_base}/v1/retrieval/{clean_model}/reranking"
|
||||
return f"{api_base}/v1/retrieval/{encode_url_path(clean_model)}/reranking"
|
||||
|
||||
def get_supported_cohere_rerank_params(self, model: str) -> list:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from litellm.types.containers.main import (
|
|||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
from ...base_llm._url_utils import encode_path_segment
|
||||
from ...base_llm.containers.transformation import BaseContainerConfig
|
||||
from .utils import join_container_api_base_path
|
||||
|
||||
|
|
@ -198,7 +199,9 @@ class OpenAIContainerConfig(BaseContainerConfig):
|
|||
) -> Tuple[str, Dict]:
|
||||
"""Transform the OpenAI container retrieve request."""
|
||||
# For container retrieve, we just need to construct the URL
|
||||
url = join_container_api_base_path(api_base, f"/{container_id}")
|
||||
url = join_container_api_base_path(
|
||||
api_base, f"/{encode_path_segment(container_id)}"
|
||||
)
|
||||
|
||||
# No additional data needed for GET request
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
@ -230,7 +233,9 @@ class OpenAIContainerConfig(BaseContainerConfig):
|
|||
- DELETE /v1/containers/{container_id}
|
||||
"""
|
||||
# Construct the URL for container delete
|
||||
url = join_container_api_base_path(api_base, f"/{container_id}")
|
||||
url = join_container_api_base_path(
|
||||
api_base, f"/{encode_path_segment(container_id)}"
|
||||
)
|
||||
|
||||
# No data needed for DELETE request
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
@ -267,7 +272,9 @@ class OpenAIContainerConfig(BaseContainerConfig):
|
|||
- GET /v1/containers/{container_id}/files
|
||||
"""
|
||||
# Construct the URL for container files
|
||||
url = join_container_api_base_path(api_base, f"/{container_id}/files")
|
||||
url = join_container_api_base_path(
|
||||
api_base, f"/{encode_path_segment(container_id)}/files"
|
||||
)
|
||||
|
||||
# Prepare query parameters
|
||||
params: Dict[str, Any] = {}
|
||||
|
|
@ -312,7 +319,8 @@ class OpenAIContainerConfig(BaseContainerConfig):
|
|||
"""
|
||||
# Construct the URL for container file content
|
||||
url = join_container_api_base_path(
|
||||
api_base, f"/{container_id}/files/{file_id}/content"
|
||||
api_base,
|
||||
f"/{encode_path_segment(container_id)}/files/{encode_path_segment(file_id)}/content",
|
||||
)
|
||||
|
||||
# No query parameters needed
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Any, Dict, Optional, Tuple
|
|||
import httpx
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.evals.transformation import (
|
||||
BaseEvalsAPIConfig,
|
||||
LiteLLMLoggingObj,
|
||||
|
|
@ -76,7 +77,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
api_base = "https://api.openai.com"
|
||||
|
||||
if eval_id:
|
||||
return f"{api_base}/v1/evals/{eval_id}"
|
||||
return f"{api_base}/v1/evals/{encode_path_segment(eval_id)}"
|
||||
return f"{api_base}/v1/{endpoint}"
|
||||
|
||||
def transform_create_eval_request(
|
||||
|
|
@ -276,7 +277,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
if litellm_params and litellm_params.api_base:
|
||||
api_base = litellm_params.api_base
|
||||
|
||||
url = f"{api_base}/v1/evals/{eval_id}/runs"
|
||||
url = f"{api_base}/v1/evals/{encode_path_segment(eval_id)}/runs"
|
||||
|
||||
# Build request body
|
||||
request_body = {k: v for k, v in create_request.items() if v is not None}
|
||||
|
|
@ -310,7 +311,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
if litellm_params and litellm_params.api_base:
|
||||
api_base = litellm_params.api_base
|
||||
|
||||
url = f"{api_base}/v1/evals/{eval_id}/runs"
|
||||
url = f"{api_base}/v1/evals/{encode_path_segment(eval_id)}/runs"
|
||||
|
||||
# Build query parameters
|
||||
query_params: Dict[str, Any] = {}
|
||||
|
|
@ -350,7 +351,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
headers: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
"""Transform get run request for OpenAI"""
|
||||
url = f"{api_base}/v1/evals/{eval_id}/runs/{run_id}"
|
||||
url = f"{api_base}/v1/evals/{encode_path_segment(eval_id)}/runs/{encode_path_segment(run_id)}"
|
||||
|
||||
verbose_logger.debug("Get run request - URL: %s", url)
|
||||
|
||||
|
|
@ -376,7 +377,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
headers: dict,
|
||||
) -> Tuple[str, Dict, Dict]:
|
||||
"""Transform cancel run request for OpenAI"""
|
||||
url = f"{api_base}/v1/evals/{eval_id}/runs/{run_id}/cancel"
|
||||
url = f"{api_base}/v1/evals/{encode_path_segment(eval_id)}/runs/{encode_path_segment(run_id)}/cancel"
|
||||
|
||||
# Empty body for cancel request
|
||||
request_body: Dict[str, Any] = {}
|
||||
|
|
@ -405,7 +406,7 @@ class OpenAIEvalsConfig(BaseEvalsAPIConfig):
|
|||
headers: dict,
|
||||
) -> Tuple[str, Dict, Dict]:
|
||||
"""Transform delete run request for OpenAI"""
|
||||
url = f"{api_base}/v1/evals/{eval_id}/runs/{run_id}"
|
||||
url = f"{api_base}/v1/evals/{encode_path_segment(eval_id)}/runs/{encode_path_segment(run_id)}"
|
||||
|
||||
# Empty body for delete request
|
||||
request_body: Dict[str, Any] = {}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from litellm.litellm_core_utils.core_helpers import process_response_headers
|
|||
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
|
||||
_safe_convert_created_field,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import *
|
||||
|
|
@ -421,7 +422,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
OpenAI API expects the following request
|
||||
- DELETE /v1/responses/{response_id}
|
||||
"""
|
||||
url = f"{api_base}/{response_id}"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
@ -457,7 +458,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
OpenAI API expects the following request
|
||||
- GET /v1/responses/{response_id}
|
||||
"""
|
||||
url = f"{api_base}/{response_id}"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
@ -498,7 +499,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
limit: int = 20,
|
||||
order: Literal["asc", "desc"] = "desc",
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{response_id}/input_items"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}/input_items"
|
||||
params: Dict[str, Any] = {}
|
||||
if after is not None:
|
||||
params["after"] = after
|
||||
|
|
@ -540,7 +541,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
OpenAI API expects the following request
|
||||
- POST /v1/responses/{response_id}/cancel
|
||||
"""
|
||||
url = f"{api_base}/{response_id}/cancel"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}/cancel"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Tuple, cast
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.vector_store_files.transformation import (
|
||||
BaseVectorStoreFilesConfig,
|
||||
)
|
||||
|
|
@ -98,7 +99,7 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig):
|
|||
or "https://api.openai.com/v1"
|
||||
)
|
||||
base_url = base_url.rstrip("/")
|
||||
return f"{base_url}/vector_stores/{vector_store_id}/files"
|
||||
return f"{base_url}/vector_stores/{encode_path_segment(vector_store_id)}/files"
|
||||
|
||||
def transform_create_vector_store_file_request(
|
||||
self,
|
||||
|
|
@ -163,7 +164,7 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig):
|
|||
file_id: str,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
return f"{api_base}/{file_id}", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_retrieve_vector_store_file_response(
|
||||
self,
|
||||
|
|
@ -186,7 +187,7 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig):
|
|||
file_id: str,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
return f"{api_base}/{file_id}/content", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}/content", {}
|
||||
|
||||
def transform_retrieve_vector_store_file_content_response(
|
||||
self,
|
||||
|
|
@ -218,7 +219,7 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig):
|
|||
payload["attributes"] = filtered_attributes
|
||||
else:
|
||||
payload.pop("attributes", None)
|
||||
return f"{api_base}/{file_id}", payload
|
||||
return f"{api_base}/{encode_path_segment(file_id)}", payload
|
||||
|
||||
def transform_update_vector_store_file_response(
|
||||
self,
|
||||
|
|
@ -241,7 +242,7 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig):
|
|||
file_id: str,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
return f"{api_base}/{file_id}", {}
|
||||
return f"{api_base}/{encode_path_segment(file_id)}", {}
|
||||
|
||||
def transform_delete_vector_store_file_response(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
|
@ -107,7 +108,7 @@ class OpenAIVectorStoreConfig(BaseVectorStoreConfig):
|
|||
litellm_logging_obj: LiteLLMLoggingObj,
|
||||
litellm_params: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{vector_store_id}/search"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}/search"
|
||||
typed_request_body = VectorStoreSearchRequest(
|
||||
query=query,
|
||||
filters=vector_store_search_optional_params.get("filters", None),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import mimetypes
|
||||
from io import BufferedReader, BytesIO
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
from httpx._types import RequestFiles
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.videos.transformation import BaseVideoConfig
|
||||
from litellm.llms.openai.image_edit.transformation import ImageEditRequestUtils
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
|
@ -222,9 +224,9 @@ class OpenAIVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Construct the URL for video content download
|
||||
url = f"{api_base.rstrip('/')}/{original_video_id}/content"
|
||||
url = f"{api_base.rstrip('/')}/{encode_path_segment(original_video_id)}/content"
|
||||
if variant is not None:
|
||||
url = f"{url}?variant={variant}"
|
||||
url = f"{url}?variant={quote(str(variant), safe='')}"
|
||||
|
||||
# No additional data needed for GET content request
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
@ -249,7 +251,7 @@ class OpenAIVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Construct the URL for video remix
|
||||
url = f"{api_base.rstrip('/')}/{original_video_id}/remix"
|
||||
url = f"{api_base.rstrip('/')}/{encode_path_segment(original_video_id)}/remix"
|
||||
|
||||
# Prepare the request data
|
||||
data = {"prompt": prompt}
|
||||
|
|
@ -393,7 +395,7 @@ class OpenAIVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Construct the URL for video delete
|
||||
url = f"{api_base.rstrip('/')}/{original_video_id}"
|
||||
url = f"{api_base.rstrip('/')}/{encode_path_segment(original_video_id)}"
|
||||
|
||||
# No data needed for DELETE request
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
@ -429,7 +431,7 @@ class OpenAIVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# For video retrieve, we just need to construct the URL
|
||||
url = f"{api_base.rstrip('/')}/{original_video_id}"
|
||||
url = f"{api_base.rstrip('/')}/{encode_path_segment(original_video_id)}"
|
||||
|
||||
# No additional data needed for GET request
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
@ -494,7 +496,7 @@ class OpenAIVideoConfig(BaseVideoConfig):
|
|||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base.rstrip('/')}/characters/{character_id}"
|
||||
url = f"{api_base.rstrip('/')}/characters/{encode_path_segment(character_id)}"
|
||||
return url, {}
|
||||
|
||||
def transform_video_get_character_response(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.openai.vector_stores.transformation import OpenAIVectorStoreConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
|
@ -81,7 +82,7 @@ class PGVectorStoreConfig(OpenAIVectorStoreConfig):
|
|||
litellm_logging_obj: LiteLLMLoggingObj,
|
||||
litellm_params: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{vector_store_id}/search"
|
||||
url = f"{api_base}/{encode_path_segment(vector_store_id)}/search"
|
||||
_, request_body = super().transform_search_vector_store_request(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import (
|
|||
custom_prompt,
|
||||
prompt_factory,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
get_async_httpx_client,
|
||||
|
|
@ -254,7 +255,7 @@ class PredibaseChatCompletion:
|
|||
elif "PREDIBASE_API_BASE" in os.environ:
|
||||
base_url = os.getenv("PREDIBASE_API_BASE", "")
|
||||
|
||||
completion_url = f"{base_url}/{tenant_id}/deployments/v2/llms/{model}"
|
||||
completion_url = f"{base_url}/{encode_path_segment(tenant_id)}/deployments/v2/llms/{encode_path_segment(model)}"
|
||||
|
||||
if optional_params.get("stream", False) is True:
|
||||
completion_url += "/generate_stream"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Model name format:
|
|||
from typing import List, Optional, Tuple
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.openai.openai import OpenAIConfig
|
||||
from litellm.secret_managers.main import get_secret, get_secret_str
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
|
@ -126,14 +127,11 @@ class RAGFlowConfig(OpenAIConfig):
|
|||
api_base = api_base[:-3] # Remove /v1
|
||||
|
||||
# Construct the RAGFlow-specific path
|
||||
encoded_entity_id = encode_path_segment(entity_id)
|
||||
if endpoint_type == "chat":
|
||||
path = f"/api/v1/chats_openai/{entity_id}/chat/completions"
|
||||
path = f"/api/v1/chats_openai/{encoded_entity_id}/chat/completions"
|
||||
else: # agent
|
||||
path = f"/api/v1/agents_openai/{entity_id}/chat/completions"
|
||||
|
||||
# Ensure path starts with /
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
path = f"/api/v1/agents_openai/{encoded_entity_id}/chat/completions"
|
||||
|
||||
return f"{api_base}{path}"
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from litellm.constants import (
|
|||
RUNWAYML_DEFAULT_API_VERSION,
|
||||
RUNWAYML_POLLING_TIMEOUT,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
|
@ -222,7 +223,7 @@ class RunwayMLImageGenerationConfig(BaseImageGenerationConfig):
|
|||
|
||||
# Build task status URL
|
||||
api_base = api_base.rstrip("/")
|
||||
task_url = f"{api_base}/v1/tasks/{task_id}"
|
||||
task_url = f"{api_base}/v1/tasks/{encode_path_segment(task_id)}"
|
||||
|
||||
verbose_logger.debug(f"Polling RunwayML task: {task_url}")
|
||||
|
||||
|
|
@ -271,7 +272,7 @@ class RunwayMLImageGenerationConfig(BaseImageGenerationConfig):
|
|||
|
||||
# Build task status URL
|
||||
api_base = api_base.rstrip("/")
|
||||
task_url = f"{api_base}/v1/tasks/{task_id}"
|
||||
task_url = f"{api_base}/v1/tasks/{encode_path_segment(task_id)}"
|
||||
|
||||
verbose_logger.debug(f"Polling RunwayML task (async): {task_url}")
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from litellm.constants import (
|
|||
RUNWAYML_DEFAULT_API_VERSION,
|
||||
RUNWAYML_POLLING_TIMEOUT,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.text_to_speech.transformation import (
|
||||
BaseTextToSpeechConfig,
|
||||
TextToSpeechRequestData,
|
||||
|
|
@ -312,7 +313,7 @@ class RunwayMLTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
|
||||
# Build task status URL
|
||||
api_base = api_base.rstrip("/")
|
||||
task_url = f"{api_base}/v1/tasks/{task_id}"
|
||||
task_url = f"{api_base}/v1/tasks/{encode_path_segment(task_id)}"
|
||||
|
||||
verbose_logger.debug(f"Polling RunwayML TTS task: {task_url}")
|
||||
|
||||
|
|
@ -360,7 +361,7 @@ class RunwayMLTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
|
||||
# Build task status URL
|
||||
api_base = api_base.rstrip("/")
|
||||
task_url = f"{api_base}/v1/tasks/{task_id}"
|
||||
task_url = f"{api_base}/v1/tasks/{encode_path_segment(task_id)}"
|
||||
|
||||
verbose_logger.debug(f"Polling RunwayML TTS task (async): {task_url}")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from httpx._types import RequestFiles
|
|||
|
||||
import litellm
|
||||
from litellm.constants import RUNWAYML_DEFAULT_API_VERSION
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.videos.transformation import BaseVideoConfig
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
|
|
@ -336,7 +337,7 @@ class RunwayMLVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Get task status to retrieve video URL
|
||||
url = f"{api_base}/tasks/{original_video_id}"
|
||||
url = f"{api_base}/tasks/{encode_path_segment(original_video_id)}"
|
||||
|
||||
params: Dict[str, Any] = {}
|
||||
|
||||
|
|
@ -497,7 +498,7 @@ class RunwayMLVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Construct the URL for task cancellation
|
||||
url = f"{api_base}/tasks/{original_video_id}/cancel"
|
||||
url = f"{api_base}/tasks/{encode_path_segment(original_video_id)}/cancel"
|
||||
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
|
|
@ -535,7 +536,7 @@ class RunwayMLVideoConfig(BaseVideoConfig):
|
|||
original_video_id = extract_original_video_id(video_id)
|
||||
|
||||
# Construct the full URL for task status retrieval
|
||||
url = f"{api_base}/tasks/{original_video_id}"
|
||||
url = f"{api_base}/tasks/{encode_path_segment(original_video_id)}"
|
||||
|
||||
# Empty dict for GET request (no body)
|
||||
data: Dict[str, Any] = {}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Any, Coroutine, Dict, Optional, Union
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
_get_httpx_client,
|
||||
get_async_httpx_client,
|
||||
|
|
@ -169,7 +170,7 @@ class VertexAIBatchPrediction(VertexLLM):
|
|||
)
|
||||
|
||||
# Append batch_id to the URL
|
||||
default_api_base = f"{default_api_base}/{batch_id}"
|
||||
default_api_base = f"{default_api_base}/{encode_path_segment(batch_id)}"
|
||||
|
||||
if len(default_api_base.split(":")) > 1:
|
||||
endpoint = default_api_base.split(":")[-1]
|
||||
|
|
@ -401,7 +402,9 @@ class VertexAIBatchPrediction(VertexLLM):
|
|||
vertex_project=vertex_project or project_id,
|
||||
)
|
||||
|
||||
retrieve_api_base_default = f"{default_api_base}/{batch_id}"
|
||||
retrieve_api_base_default = (
|
||||
f"{default_api_base}/{encode_path_segment(batch_id)}"
|
||||
)
|
||||
cancel_api_base_default = f"{retrieve_api_base_default}:cancel"
|
||||
|
||||
_, api_base = self._check_custom_proxy(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import litellm
|
|||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.base_utils import BaseLLMModelInfo, BaseTokenCounter
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
|
@ -275,11 +276,14 @@ def _get_embedding_url(
|
|||
endpoint = "embedContent" if uses_embed_content else "predict"
|
||||
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
proj = encode_path_segment(vertex_project)
|
||||
loc = encode_path_segment(vertex_location)
|
||||
model_seg = encode_url_path(model)
|
||||
|
||||
if model.isdigit():
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}"
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{proj}/locations/{loc}/endpoints/{model_seg}:{endpoint}"
|
||||
else:
|
||||
url = f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}"
|
||||
url = f"{base_url}/v1/projects/{proj}/locations/{loc}/publishers/google/models/{model_seg}:{endpoint}"
|
||||
|
||||
return url, endpoint
|
||||
|
||||
|
|
@ -297,6 +301,10 @@ def _get_vertex_url(
|
|||
|
||||
model = litellm.VertexGeminiConfig.get_model_for_vertex_ai_url(model=model)
|
||||
|
||||
proj = encode_path_segment(vertex_project)
|
||||
loc = encode_path_segment(vertex_location)
|
||||
model_seg = encode_url_path(model)
|
||||
|
||||
if mode == "chat":
|
||||
### SET RUNTIME ENDPOINT ###
|
||||
endpoint = "generateContent"
|
||||
|
|
@ -310,10 +318,10 @@ def _get_vertex_url(
|
|||
# send to this url: url = f"{base_url}/{version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}"
|
||||
if model.isdigit():
|
||||
# It's a fine-tuned Gemini model - use endpoints/ path
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}"
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{proj}/locations/{loc}/endpoints/{model_seg}:{endpoint}"
|
||||
else:
|
||||
# Regular model - use publishers/google/models/ path
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}"
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{proj}/locations/{loc}/publishers/google/models/{model_seg}:{endpoint}"
|
||||
|
||||
if stream is True:
|
||||
url += "?alt=sse"
|
||||
|
|
@ -329,14 +337,14 @@ def _get_vertex_url(
|
|||
base_url = get_vertex_base_url(vertex_location)
|
||||
if model.isdigit():
|
||||
# Numeric model -> custom endpoint
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/endpoints/{model}:{endpoint}"
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{proj}/locations/{loc}/endpoints/{model_seg}:{endpoint}"
|
||||
else:
|
||||
# Regular model -> publisher model
|
||||
url = f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}"
|
||||
url = f"{base_url}/v1/projects/{proj}/locations/{loc}/publishers/google/models/{model_seg}:{endpoint}"
|
||||
elif mode == "count_tokens":
|
||||
endpoint = "countTokens"
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model}:{endpoint}"
|
||||
url = f"{base_url}/{vertex_api_version}/projects/{proj}/locations/{loc}/publishers/google/models/{model_seg}:{endpoint}"
|
||||
if not url or not endpoint:
|
||||
raise ValueError(f"Unable to get vertex url/endpoint for mode: {mode}")
|
||||
return url, endpoint
|
||||
|
|
@ -357,7 +365,7 @@ def _get_gemini_url(
|
|||
VertexGeminiConfig,
|
||||
)
|
||||
|
||||
_gemini_model_name = "models/{}".format(model)
|
||||
_gemini_model_name = "models/{}".format(encode_url_path(model))
|
||||
api_version = (
|
||||
"v1alpha" if VertexGeminiConfig._is_gemini_3_or_newer(model) else "v1beta"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from httpx._types import RequestFiles
|
|||
|
||||
import litellm
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
||||
from litellm.llms.vertex_ai.common_utils import get_vertex_base_url
|
||||
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM
|
||||
|
|
@ -161,7 +162,7 @@ class VertexAIGeminiImageEditConfig(BaseImageEditConfig, VertexLLM):
|
|||
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:generateContent"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/google/models/{encode_url_path(model_name)}:generateContent"
|
||||
|
||||
def transform_image_edit_request( # type: ignore[override]
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from httpx._types import RequestFiles
|
|||
|
||||
import litellm
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
||||
from litellm.llms.vertex_ai.common_utils import get_vertex_base_url
|
||||
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM
|
||||
|
|
@ -161,7 +162,7 @@ class VertexAIImagenImageEditConfig(BaseImageEditConfig, VertexLLM):
|
|||
else:
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:predict"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/google/models/{encode_url_path(model_name)}:predict"
|
||||
|
||||
def transform_image_edit_request( # type: ignore[override]
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
|
@ -162,7 +163,7 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
|
|||
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:generateContent"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/google/models/{encode_url_path(model_name)}:generateContent"
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
|
@ -148,7 +149,7 @@ class VertexAIImagenImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
|
|||
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}:predict"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/google/models/{encode_url_path(model_name)}:predict"
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
|
|||
import httpx
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.base_llm.ocr.transformation import (
|
||||
BaseOCRConfig,
|
||||
DocumentType,
|
||||
|
|
@ -125,7 +126,7 @@ class VertexAIDeepSeekOCRConfig(BaseOCRConfig):
|
|||
|
||||
# Vertex AI DeepSeek OCR endpoint format
|
||||
# Format: https://{region}-aiplatform.googleapis.com/v1/projects/{project}/locations/{region}/endpoints/openapi/chat/completions
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/endpoints/openapi/chat/completions"
|
||||
return f"{api_base}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/endpoints/openapi/chat/completions"
|
||||
|
||||
def transform_ocr_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from litellm.litellm_core_utils.prompt_templates.image_handling import (
|
|||
async_convert_url_to_base64,
|
||||
convert_url_to_base64,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.ocr.transformation import DocumentType, OCRRequestData
|
||||
from litellm.llms.mistral.ocr.transformation import MistralOCRConfig
|
||||
from litellm.llms.vertex_ai.common_utils import get_vertex_base_url
|
||||
|
|
@ -121,7 +122,7 @@ class VertexAIOCRConfig(MistralOCRConfig):
|
|||
|
||||
# Vertex AI OCR endpoint format for Mistral publisher
|
||||
# Format: https://{region}-aiplatform.googleapis.com/v1/projects/{project}/locations/{region}/publishers/mistralai/models/{model}:rawPredict
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/mistralai/models/{model}:rawPredict"
|
||||
return f"{api_base}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/mistralai/models/{encode_url_path(model)}:rawPredict"
|
||||
|
||||
def _convert_url_to_data_uri_sync(self, url: str) -> str:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import Any, Dict, Optional
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.vertex_ai.common_utils import get_vertex_base_url
|
||||
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
|
||||
from litellm.types.rag import RAGChunkingStrategy
|
||||
|
|
@ -39,7 +40,7 @@ class VertexAIRAGTransformation(VertexBase):
|
|||
Vertex AI RAG Engine primarily uses gRPC-based SDK.
|
||||
"""
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/ragCorpora/{corpus_id}:importRagFiles"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/ragCorpora/{encode_path_segment(corpus_id)}:importRagFiles"
|
||||
|
||||
def get_retrieve_contexts_url(
|
||||
self,
|
||||
|
|
@ -48,7 +49,7 @@ class VertexAIRAGTransformation(VertexBase):
|
|||
) -> str:
|
||||
"""Get the URL for retrieving contexts (search)."""
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
return f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}:retrieveContexts"
|
||||
return f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}:retrieveContexts"
|
||||
|
||||
def transform_chunking_strategy_to_vertex_format(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ their respective publisher-specific count-tokens endpoints.
|
|||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
from litellm.llms.vertex_ai.common_utils import get_vertex_base_url
|
||||
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
|
||||
|
|
@ -72,10 +73,7 @@ class VertexAIPartnerModelsTokenCounter(VertexBase):
|
|||
|
||||
# Construct the count-tokens endpoint
|
||||
# Format: /v1/projects/{project}/locations/{location}/publishers/{publisher}/models/count-tokens:rawPredict
|
||||
endpoint = (
|
||||
f"{base_url}/v1/projects/{project_id}/locations/{vertex_location}/"
|
||||
f"publishers/{publisher}/models/count-tokens:rawPredict"
|
||||
)
|
||||
endpoint = f"{base_url}/v1/projects/{encode_path_segment(project_id)}/locations/{encode_path_segment(vertex_location)}/publishers/{encode_path_segment(publisher)}/models/count-tokens:rawPredict"
|
||||
|
||||
return endpoint
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.litellm_core_utils.asyncify import asyncify
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.vertex_ai import VERTEX_CREDENTIALS_TYPES, VertexPartnerProvider
|
||||
|
|
@ -273,23 +274,20 @@ class VertexBase:
|
|||
|
||||
if api_base is None:
|
||||
api_base = get_vertex_base_url(vertex_location)
|
||||
proj = encode_path_segment(vertex_project)
|
||||
loc = encode_path_segment(vertex_location)
|
||||
model_seg = encode_url_path(model)
|
||||
if partner == VertexPartnerProvider.llama:
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/endpoints/openapi/chat/completions"
|
||||
return f"{api_base}/v1/projects/{proj}/locations/{loc}/endpoints/openapi/chat/completions"
|
||||
elif partner == VertexPartnerProvider.mistralai:
|
||||
if stream:
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/mistralai/models/{model}:streamRawPredict"
|
||||
else:
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/mistralai/models/{model}:rawPredict"
|
||||
action = "streamRawPredict" if stream else "rawPredict"
|
||||
return f"{api_base}/v1/projects/{proj}/locations/{loc}/publishers/mistralai/models/{model_seg}:{action}"
|
||||
elif partner == VertexPartnerProvider.ai21:
|
||||
if stream:
|
||||
return f"{api_base}/v1beta1/projects/{vertex_project}/locations/{vertex_location}/publishers/ai21/models/{model}:streamRawPredict"
|
||||
else:
|
||||
return f"{api_base}/v1beta1/projects/{vertex_project}/locations/{vertex_location}/publishers/ai21/models/{model}:rawPredict"
|
||||
action = "streamRawPredict" if stream else "rawPredict"
|
||||
return f"{api_base}/v1beta1/projects/{proj}/locations/{loc}/publishers/ai21/models/{model_seg}:{action}"
|
||||
elif partner == VertexPartnerProvider.claude:
|
||||
if stream:
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/anthropic/models/{model}:streamRawPredict"
|
||||
else:
|
||||
return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/anthropic/models/{model}:rawPredict"
|
||||
action = "streamRawPredict" if stream else "rawPredict"
|
||||
return f"{api_base}/v1/projects/{proj}/locations/{loc}/publishers/anthropic/models/{model_seg}:{action}"
|
||||
|
||||
def get_complete_vertex_url(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from httpx._types import RequestFiles
|
|||
|
||||
from litellm.constants import DEFAULT_GOOGLE_VIDEO_DURATION_SECONDS
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
from litellm.llms.base_llm.videos.transformation import BaseVideoConfig
|
||||
from litellm.llms.vertex_ai.common_utils import (
|
||||
_convert_vertex_datetime_to_openai_datetime,
|
||||
|
|
@ -238,7 +239,7 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase):
|
|||
else:
|
||||
base_url = get_vertex_base_url(vertex_location)
|
||||
|
||||
url = f"{base_url}/v1/projects/{vertex_project}/locations/{vertex_location}/publishers/google/models/{model_name}"
|
||||
url = f"{base_url}/v1/projects/{encode_path_segment(vertex_project)}/locations/{encode_path_segment(vertex_location)}/publishers/google/models/{encode_url_path(model_name)}"
|
||||
|
||||
return url
|
||||
|
||||
|
|
@ -406,7 +407,7 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase):
|
|||
# Construct the full URL including model ID
|
||||
# URL format: https://LOCATION-aiplatform.googleapis.com/v1/projects/PROJECT/locations/LOCATION/publishers/google/models/MODEL:fetchPredictOperation
|
||||
# Strip trailing slashes from api_base and append model
|
||||
url = f"{api_base.rstrip('/')}/{model}:fetchPredictOperation"
|
||||
url = f"{api_base.rstrip('/')}/{encode_url_path(model)}:fetchPredictOperation"
|
||||
|
||||
# Request body contains the operation name
|
||||
params = {"operationName": operation_name}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from litellm.litellm_core_utils.core_helpers import process_response_headers
|
|||
from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import (
|
||||
_safe_convert_created_field,
|
||||
)
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import (
|
||||
|
|
@ -300,7 +301,7 @@ class VolcEngineResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{response_id}"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
@ -333,7 +334,7 @@ class VolcEngineResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{response_id}"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
@ -372,7 +373,7 @@ class VolcEngineResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
limit: int = 20,
|
||||
order: Literal["asc", "desc"] = "desc",
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{response_id}/input_items"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}/input_items"
|
||||
params: Dict[str, Any] = {}
|
||||
if after is not None:
|
||||
params["after"] = after
|
||||
|
|
@ -408,7 +409,7 @@ class VolcEngineResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{response_id}/cancel"
|
||||
url = f"{api_base}/{encode_path_segment(response_id)}/cancel"
|
||||
data: Dict = {}
|
||||
return url, data
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
|
|
@ -231,7 +232,7 @@ class GeminiRAGIngestion(BaseRAGIngestion):
|
|||
# base_url is like: https://generativelanguage.googleapis.com/v1beta
|
||||
# We need: https://generativelanguage.googleapis.com/upload/v1beta/{store_id}:uploadToFileSearchStore
|
||||
api_base = base_url.replace("/v1beta", "") # Get base without version
|
||||
url = f"{api_base}/upload/v1beta/{vector_store_id}:uploadToFileSearchStore"
|
||||
url = f"{api_base}/upload/v1beta/{encode_path_segment(vector_store_id)}:uploadToFileSearchStore"
|
||||
|
||||
# Build request body with chunking config and metadata if provided
|
||||
request_body: Dict[str, Any] = {"displayName": filename}
|
||||
|
|
|
|||
710
tests/test_litellm/llms/base_llm/test_url_utils.py
Normal file
710
tests/test_litellm/llms/base_llm/test_url_utils.py
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
"""Tests for ``encode_path_segment`` / ``encode_url_path`` and provider call sites."""
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.base_llm._url_utils import encode_path_segment, encode_url_path
|
||||
|
||||
|
||||
SAMPLE_INPUT = "../../v1/messages/batches"
|
||||
SAMPLE_INPUT_ENCODED = "..%2F..%2Fv1%2Fmessages%2Fbatches"
|
||||
|
||||
|
||||
def _assert_input_is_encoded(url: str, prefix: str) -> None:
|
||||
assert SAMPLE_INPUT_ENCODED in url
|
||||
tail = url.split(prefix, 1)[1]
|
||||
assert "../" not in tail, f"raw ``../`` after {prefix!r}: {tail!r}"
|
||||
|
||||
|
||||
class TestEncodePathSegment:
|
||||
def test_encodes_dot_slash_sequences(self):
|
||||
assert encode_path_segment(SAMPLE_INPUT) == SAMPLE_INPUT_ENCODED
|
||||
|
||||
def test_encodes_query_and_fragment(self):
|
||||
assert encode_path_segment("file?admin=1") == "file%3Fadmin%3D1"
|
||||
assert encode_path_segment("file#frag") == "file%23frag"
|
||||
|
||||
def test_leaves_normal_ids_unchanged(self):
|
||||
assert encode_path_segment("file-abc123") == "file-abc123"
|
||||
assert encode_path_segment("file_abc123") == "file_abc123"
|
||||
|
||||
def test_rejects_bare_dotdot(self):
|
||||
with pytest.raises(ValueError):
|
||||
encode_path_segment("..")
|
||||
with pytest.raises(ValueError):
|
||||
encode_path_segment(".")
|
||||
|
||||
def test_rejects_none_and_empty(self):
|
||||
with pytest.raises(ValueError, match="identifier is required"):
|
||||
encode_path_segment(None)
|
||||
with pytest.raises(ValueError, match="identifier is required"):
|
||||
encode_path_segment("")
|
||||
|
||||
|
||||
class TestEncodeUrlPath:
|
||||
def test_preserves_legitimate_slashes_and_at(self):
|
||||
assert (
|
||||
encode_url_path("@cf/meta/llama-3.1-8b-instruct")
|
||||
== "@cf/meta/llama-3.1-8b-instruct"
|
||||
)
|
||||
assert encode_url_path("google/gemma-3-4b-it") == "google/gemma-3-4b-it"
|
||||
|
||||
def test_rejects_dotdot_segment(self):
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("../../etc/passwd")
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("@cf/../secret")
|
||||
|
||||
def test_rejects_single_dot_segment(self):
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("./secret")
|
||||
|
||||
def test_rejects_empty_segments(self):
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("foo//bar")
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("/foo")
|
||||
with pytest.raises(ValueError):
|
||||
encode_url_path("foo/")
|
||||
|
||||
def test_encodes_query_fragment_and_colon(self):
|
||||
assert encode_url_path("model?x=1") == "model%3Fx%3D1"
|
||||
assert encode_url_path("model#frag") == "model%23frag"
|
||||
assert encode_url_path("evil.com:80/x") == "evil.com%3A80/x"
|
||||
|
||||
def test_none_becomes_empty(self):
|
||||
assert encode_url_path(None) == ""
|
||||
|
||||
|
||||
class TestAnthropicFilesEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.anthropic.files.transformation import AnthropicFilesConfig
|
||||
|
||||
return AnthropicFilesConfig()
|
||||
|
||||
def test_retrieve(self):
|
||||
url, _ = self._config().transform_retrieve_file_request(
|
||||
file_id=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/files/")
|
||||
|
||||
def test_delete(self):
|
||||
url, _ = self._config().transform_delete_file_request(
|
||||
file_id=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/files/")
|
||||
|
||||
def test_content(self):
|
||||
url, _ = self._config().transform_file_content_request(
|
||||
file_content_request={"file_id": SAMPLE_INPUT},
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/files/")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method_name",
|
||||
[
|
||||
"transform_retrieve_file_request",
|
||||
"transform_delete_file_request",
|
||||
],
|
||||
)
|
||||
def test_missing_file_id_raises(self, method_name):
|
||||
with pytest.raises(ValueError, match="identifier is required"):
|
||||
getattr(self._config(), method_name)(
|
||||
file_id="", optional_params={}, litellm_params={}
|
||||
)
|
||||
|
||||
def test_missing_file_id_content_raises(self):
|
||||
with pytest.raises(ValueError, match="identifier is required"):
|
||||
self._config().transform_file_content_request(
|
||||
file_content_request={}, # no file_id
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
|
||||
|
||||
class TestAnthropicBatchesEncoding:
|
||||
def test_retrieve_url(self):
|
||||
from litellm.llms.anthropic.batches.transformation import AnthropicBatchesConfig
|
||||
|
||||
url = AnthropicBatchesConfig().get_retrieve_batch_url(
|
||||
api_base="https://api.anthropic.com",
|
||||
batch_id=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/batches/")
|
||||
|
||||
|
||||
class TestAnthropicSkillsEncoding:
|
||||
def test_get_complete_url(self):
|
||||
from litellm.llms.anthropic.skills.transformation import AnthropicSkillsConfig
|
||||
|
||||
url = AnthropicSkillsConfig().get_complete_url(
|
||||
api_base="https://api.anthropic.com",
|
||||
endpoint="skills",
|
||||
skill_id=SAMPLE_INPUT,
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/skills/")
|
||||
|
||||
|
||||
class TestOpenAIVideosEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.openai.videos.transformation import OpenAIVideoConfig
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
return OpenAIVideoConfig(), GenericLiteLLMParams()
|
||||
|
||||
def test_content(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_video_content_request(
|
||||
video_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/videos",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/videos/")
|
||||
|
||||
def test_content_variant_is_encoded(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_video_content_request(
|
||||
video_id="vid_ok",
|
||||
api_base="https://api.openai.com/v1/videos",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
variant="bad&inject=1",
|
||||
)
|
||||
assert "?variant=bad%26inject%3D1" in url
|
||||
|
||||
def test_delete(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_video_delete_request(
|
||||
video_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/videos",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/videos/")
|
||||
|
||||
def test_status_retrieve(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_video_status_retrieve_request(
|
||||
video_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/videos",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/videos/")
|
||||
|
||||
def test_remix(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_video_remix_request(
|
||||
video_id=SAMPLE_INPUT,
|
||||
prompt="x",
|
||||
api_base="https://api.openai.com/v1/videos",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/videos/")
|
||||
|
||||
|
||||
class TestOpenAIContainersEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.openai.containers.transformation import (
|
||||
OpenAIContainerConfig,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
return OpenAIContainerConfig(), GenericLiteLLMParams()
|
||||
|
||||
def test_retrieve(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_container_retrieve_request(
|
||||
container_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/containers",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/containers/")
|
||||
|
||||
def test_delete(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_container_delete_request(
|
||||
container_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/containers",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/containers/")
|
||||
|
||||
def test_file_list(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_container_file_list_request(
|
||||
container_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/containers",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/containers/")
|
||||
|
||||
def test_file_content(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_container_file_content_request(
|
||||
container_id="cntr_ok",
|
||||
file_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/containers",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/cntr_ok/files/")
|
||||
|
||||
|
||||
class TestOpenAIVectorStoresEncoding:
|
||||
def test_search(self):
|
||||
from litellm.llms.openai.vector_stores.transformation import (
|
||||
OpenAIVectorStoreConfig,
|
||||
)
|
||||
|
||||
url, _ = OpenAIVectorStoreConfig().transform_search_vector_store_request(
|
||||
vector_store_id=SAMPLE_INPUT,
|
||||
query="x",
|
||||
vector_store_search_optional_params={},
|
||||
api_base="https://api.openai.com/v1/vector_stores",
|
||||
litellm_logging_obj=None,
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/vector_stores/")
|
||||
|
||||
|
||||
class TestOpenAIVectorStoreFilesEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.openai.vector_store_files.transformation import (
|
||||
OpenAIVectorStoreFilesConfig,
|
||||
)
|
||||
|
||||
return OpenAIVectorStoreFilesConfig()
|
||||
|
||||
def test_get_complete_url(self):
|
||||
url = self._config().get_complete_url(
|
||||
api_base="https://api.openai.com/v1",
|
||||
vector_store_id=SAMPLE_INPUT,
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/vector_stores/")
|
||||
|
||||
def test_retrieve(self):
|
||||
url, _ = self._config().transform_retrieve_vector_store_file_request(
|
||||
vector_store_id="vs_ok",
|
||||
file_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/vector_stores/vs_ok/files",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
def test_content(self):
|
||||
url, _ = self._config().transform_retrieve_vector_store_file_content_request(
|
||||
vector_store_id="vs_ok",
|
||||
file_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/vector_stores/vs_ok/files",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
def test_update(self):
|
||||
url, _ = self._config().transform_update_vector_store_file_request(
|
||||
vector_store_id="vs_ok",
|
||||
file_id=SAMPLE_INPUT,
|
||||
update_request={"attributes": None},
|
||||
api_base="https://api.openai.com/v1/vector_stores/vs_ok/files",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
def test_delete(self):
|
||||
url, _ = self._config().transform_delete_vector_store_file_request(
|
||||
vector_store_id="vs_ok",
|
||||
file_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/vector_stores/vs_ok/files",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
|
||||
class TestGeminiInteractionsEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.gemini.interactions.transformation import (
|
||||
GoogleAIStudioInteractionsConfig,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
return (
|
||||
GoogleAIStudioInteractionsConfig(),
|
||||
GenericLiteLLMParams(api_key="sk-test"),
|
||||
)
|
||||
|
||||
def test_get(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_get_interaction_request(
|
||||
interaction_id=SAMPLE_INPUT,
|
||||
api_base="https://generativelanguage.googleapis.com",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/interactions/")
|
||||
|
||||
def test_delete(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_delete_interaction_request(
|
||||
interaction_id=SAMPLE_INPUT,
|
||||
api_base="https://generativelanguage.googleapis.com",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/interactions/")
|
||||
|
||||
def test_cancel(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_cancel_interaction_request(
|
||||
interaction_id=SAMPLE_INPUT,
|
||||
api_base="https://generativelanguage.googleapis.com",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/interactions/")
|
||||
|
||||
|
||||
class TestBedrockCountTokensEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.bedrock.count_tokens.transformation import (
|
||||
BedrockCountTokensConfig,
|
||||
)
|
||||
|
||||
return BedrockCountTokensConfig()
|
||||
|
||||
def test_endpoint(self):
|
||||
url = self._config().get_bedrock_count_tokens_endpoint(
|
||||
model=SAMPLE_INPUT,
|
||||
aws_region_name="us-east-1",
|
||||
api_base=None,
|
||||
aws_bedrock_runtime_endpoint=None,
|
||||
)
|
||||
_assert_input_is_encoded(url, "/model/")
|
||||
|
||||
def test_versioned_model_id_preserves_colon(self):
|
||||
url = self._config().get_bedrock_count_tokens_endpoint(
|
||||
model="amazon.nova-pro-v1:0",
|
||||
aws_region_name="us-east-1",
|
||||
api_base=None,
|
||||
aws_bedrock_runtime_endpoint=None,
|
||||
)
|
||||
assert "/model/amazon.nova-pro-v1:0/count-tokens" in url
|
||||
|
||||
|
||||
class TestBedrockInvokeOpenAIEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.bedrock.chat.invoke_transformations.amazon_openai_transformation import (
|
||||
AmazonBedrockOpenAIConfig,
|
||||
)
|
||||
|
||||
return AmazonBedrockOpenAIConfig()
|
||||
|
||||
def test_versioned_model_id_preserves_colon(self):
|
||||
cfg = self._config()
|
||||
url = cfg.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="bedrock/openai/amazon.nova-pro-v1:0",
|
||||
optional_params={"aws_region_name": "us-east-1"},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
assert "/model/amazon.nova-pro-v1:0/invoke" in url
|
||||
|
||||
def test_traversal_input_is_encoded(self):
|
||||
cfg = self._config()
|
||||
url = cfg.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="bedrock/openai/../../foo",
|
||||
optional_params={"aws_region_name": "us-east-1"},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
assert "..%2F..%2Ffoo" in url
|
||||
assert "/model/../" not in url
|
||||
|
||||
|
||||
class TestCloudflareEncoding:
|
||||
def test_rejects_dot_segment(self, monkeypatch):
|
||||
from litellm.llms.cloudflare.chat.transformation import CloudflareChatConfig
|
||||
|
||||
monkeypatch.setenv("CLOUDFLARE_ACCOUNT_ID", "acct123")
|
||||
cfg = CloudflareChatConfig()
|
||||
with pytest.raises(ValueError):
|
||||
cfg.get_complete_url(
|
||||
api_base=None,
|
||||
api_key="x",
|
||||
model=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
|
||||
def test_legitimate_model_preserved(self, monkeypatch):
|
||||
from litellm.llms.cloudflare.chat.transformation import CloudflareChatConfig
|
||||
|
||||
monkeypatch.setenv("CLOUDFLARE_ACCOUNT_ID", "acct123")
|
||||
cfg = CloudflareChatConfig()
|
||||
url = cfg.get_complete_url(
|
||||
api_base=None,
|
||||
api_key="x",
|
||||
model="@cf/meta/llama-3.1-8b-instruct",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
assert url.endswith("@cf/meta/llama-3.1-8b-instruct")
|
||||
|
||||
|
||||
class TestBytezEncoding:
|
||||
def test_rejects_dot_segment(self):
|
||||
from litellm.llms.bytez.chat.transformation import BytezChatConfig
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
BytezChatConfig().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="x",
|
||||
model=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
|
||||
def test_legitimate_model_preserved(self):
|
||||
from litellm.llms.bytez.chat.transformation import BytezChatConfig
|
||||
|
||||
url = BytezChatConfig().get_complete_url(
|
||||
api_base=None,
|
||||
api_key="x",
|
||||
model="google/gemma-3-4b-it",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
assert url.endswith("google/gemma-3-4b-it")
|
||||
|
||||
|
||||
class TestRagflowEncoding:
|
||||
def test_rejects_dot_segment(self):
|
||||
from litellm.llms.ragflow.chat.transformation import RAGFlowConfig
|
||||
|
||||
cfg = RAGFlowConfig()
|
||||
with pytest.raises(ValueError):
|
||||
cfg.get_complete_url(
|
||||
api_base="http://ragflow.example",
|
||||
api_key="x",
|
||||
model="ragflow/chat/../../v1/messages/batches/llama",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
|
||||
def test_query_chars_in_entity_id_are_encoded(self):
|
||||
from litellm.llms.ragflow.chat.transformation import RAGFlowConfig
|
||||
|
||||
cfg = RAGFlowConfig()
|
||||
url = cfg.get_complete_url(
|
||||
api_base="http://ragflow.example",
|
||||
api_key="x",
|
||||
model="ragflow/chat/id?admin=1/llama",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
stream=False,
|
||||
)
|
||||
assert "id%3Fadmin%3D1" in url
|
||||
assert "?admin=1" not in url
|
||||
|
||||
|
||||
class TestPGVectorEncoding:
|
||||
def test_search(self):
|
||||
from litellm.llms.pg_vector.vector_stores.transformation import (
|
||||
PGVectorStoreConfig,
|
||||
)
|
||||
|
||||
url, _ = PGVectorStoreConfig().transform_search_vector_store_request(
|
||||
vector_store_id=SAMPLE_INPUT,
|
||||
query="x",
|
||||
vector_store_search_optional_params={},
|
||||
api_base="http://pg.example/v1/vector_stores",
|
||||
litellm_logging_obj=None,
|
||||
litellm_params={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/vector_stores/")
|
||||
|
||||
|
||||
class TestContainerHandlerBuildUrl:
|
||||
def test_path_params_encoded(self):
|
||||
from litellm.llms.custom_httpx.container_handler import _build_url
|
||||
|
||||
url = _build_url(
|
||||
api_base="https://api.openai.com/v1/containers",
|
||||
path_template="/containers/{container_id}/files/{file_id}",
|
||||
path_params={"container_id": "cntr_ok", "file_id": SAMPLE_INPUT},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
|
||||
class TestOpenAIEvalsEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.openai.evals.transformation import OpenAIEvalsConfig
|
||||
|
||||
return OpenAIEvalsConfig()
|
||||
|
||||
def test_get_complete_url(self):
|
||||
cfg = self._config()
|
||||
url = cfg.get_complete_url(
|
||||
api_base="https://api.openai.com",
|
||||
endpoint="evals",
|
||||
eval_id=SAMPLE_INPUT,
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/evals/")
|
||||
|
||||
def test_get_run(self):
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
url, _ = self._config().transform_get_run_request(
|
||||
eval_id="eval_ok",
|
||||
run_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com",
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/runs/")
|
||||
|
||||
|
||||
class TestOpenAIResponsesEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.openai.responses.transformation import (
|
||||
OpenAIResponsesAPIConfig,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
return OpenAIResponsesAPIConfig(), GenericLiteLLMParams()
|
||||
|
||||
def test_delete(self):
|
||||
cfg, params = self._config()
|
||||
url, _ = cfg.transform_delete_response_api_request(
|
||||
response_id=SAMPLE_INPUT,
|
||||
api_base="https://api.openai.com/v1/responses",
|
||||
litellm_params=params,
|
||||
headers={},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/v1/responses/")
|
||||
|
||||
|
||||
class TestManusFilesEncoding:
|
||||
def _config(self):
|
||||
from litellm.llms.manus.files.transformation import ManusFilesConfig
|
||||
|
||||
return ManusFilesConfig()
|
||||
|
||||
def test_retrieve(self):
|
||||
url, _ = self._config().transform_retrieve_file_request(
|
||||
file_id=SAMPLE_INPUT,
|
||||
optional_params={},
|
||||
litellm_params={"api_base": "https://api.manus.im/v1/files"},
|
||||
)
|
||||
_assert_input_is_encoded(url, "/files/")
|
||||
|
||||
|
||||
class TestAzureAIAgentsEncoding:
|
||||
def _handler(self):
|
||||
from litellm.llms.azure_ai.agents.handler import AzureAIAgentsHandler
|
||||
|
||||
return AzureAIAgentsHandler.__new__(AzureAIAgentsHandler)
|
||||
|
||||
def test_messages_url(self):
|
||||
handler = self._handler()
|
||||
url = handler._build_messages_url(
|
||||
api_base="https://ai.example/api/projects/p",
|
||||
thread_id=SAMPLE_INPUT,
|
||||
api_version="2025-05-01",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/threads/")
|
||||
|
||||
def test_run_status_url(self):
|
||||
handler = self._handler()
|
||||
url = handler._build_run_status_url(
|
||||
api_base="https://ai.example/api/projects/p",
|
||||
thread_id="thread_ok",
|
||||
run_id=SAMPLE_INPUT,
|
||||
api_version="2025-05-01",
|
||||
)
|
||||
_assert_input_is_encoded(url, "/runs/")
|
||||
|
||||
|
||||
class TestVertexVideosEncoding:
|
||||
def test_operation_status_rejects_dot_segments(self):
|
||||
from litellm.llms.vertex_ai.videos.transformation import (
|
||||
VertexAIVideoConfig,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
cfg = VertexAIVideoConfig()
|
||||
with pytest.raises(ValueError):
|
||||
cfg.transform_video_status_retrieve_request(
|
||||
video_id="../../v1/models",
|
||||
api_base="https://aiplatform.googleapis.com",
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
|
||||
class TestNvidiaNimEncoding:
|
||||
def test_rejects_dot_segment(self):
|
||||
from litellm.llms.nvidia_nim.rerank.transformation import (
|
||||
NvidiaNimRerankConfig,
|
||||
)
|
||||
|
||||
cfg = NvidiaNimRerankConfig()
|
||||
with pytest.raises(ValueError):
|
||||
cfg.get_complete_url(
|
||||
api_base="https://integrate.api.nvidia.com/v1",
|
||||
model="nvidia_nim/../../v1/models",
|
||||
optional_params={},
|
||||
)
|
||||
|
||||
def test_legitimate_model_preserved(self):
|
||||
from litellm.llms.nvidia_nim.rerank.transformation import (
|
||||
NvidiaNimRerankConfig,
|
||||
)
|
||||
|
||||
url = NvidiaNimRerankConfig().get_complete_url(
|
||||
api_base="https://integrate.api.nvidia.com",
|
||||
model="nvidia_nim/nvidia/nv-rerankqa-mistral-4b-v3",
|
||||
optional_params={},
|
||||
)
|
||||
assert url.endswith("nvidia/nv-rerankqa-mistral-4b-v3/reranking")
|
||||
|
||||
|
||||
class TestHuggingfaceEmbeddingEncoding:
|
||||
def test_ids_encoded(self):
|
||||
assert encode_url_path("BAAI/bge-large-en-v1.5") == "BAAI/bge-large-en-v1.5"
|
||||
|
||||
|
||||
class TestElevenLabsEncoding:
|
||||
def test_voice_id_encoded(self):
|
||||
from litellm.llms.elevenlabs.text_to_speech.transformation import (
|
||||
ElevenLabsTextToSpeechConfig,
|
||||
)
|
||||
|
||||
cfg = ElevenLabsTextToSpeechConfig()
|
||||
url = cfg.get_complete_url(
|
||||
model="eleven_multilingual_v2",
|
||||
api_base="https://api.elevenlabs.io",
|
||||
litellm_params={
|
||||
cfg.ELEVENLABS_VOICE_ID_KEY: SAMPLE_INPUT,
|
||||
},
|
||||
)
|
||||
assert SAMPLE_INPUT_ENCODED in url
|
||||
assert "../" not in url.split("/text-to-speech/", 1)[1]
|
||||
Loading…
Add table
Reference in a new issue