mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
fix(clinepass): address external review -- logging, unicode, dead code, UI, README
Fixes from two independent reviews of the provider addition: * `main.py`: drop the provider's own `logging.post_call`. The shared `BaseLLMHTTPHandler` already logs post-call, so this double-fired callbacks and overwrote raw-response metadata; on the async path `response` is still a coroutine there, so callbacks ran before the request completed. Also adopt the `Final` annotations and `_dispatch_client_http(ctx)` used by the sibling `_complete_*` blocks. * `transformation.py`: `json.dumps(..., ensure_ascii=False)` when rebuilding the unwrapped body -- the default escapes non-ASCII to `\uXXXX`, and `OpenAIGPTConfig.transform_response` hands `raw_response.text` straight to `logging.post_call`, so observability backends received mangled text. * `transformation.py`: remove two unreachable branches. `httpx.StreamError` cannot arrive, because `BaseLLMHTTPHandler` returns `get_model_response_iterator()` without calling `transform_response` when streaming (the only paths that do call it set `stream=False`, so the body is fully buffered). The `max_completion_tokens` fallback cannot fire either, because `request_data` is the post-`map_openai_params` payload and the mapping has already rewritten that key to `max_tokens`. * `transformation.py`: modernise typing to builtin generics and PEP 604 unions, matching the repo's UP006/UP007/UP035/UP045 rules and the CometAPI house style. * UI: add `CLINEPASS` to the `Providers` enum and `provider_map`. `CredentialModal` builds its options from `Object.entries(Providers)`, so backend credential metadata alone left the provider unselectable. * README: add the provider table row, ticking only the endpoints `provider_endpoints_support.json` declares. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b9d159fcc9
commit
bf18f22f9c
4 changed files with 32 additions and 35 deletions
|
|
@ -317,6 +317,7 @@ Set `LITELLM_PROXY_API_BASE` and `LITELLM_PROXY_API_KEY` and every model call th
|
|||
| [Bytez (`bytez`)](https://docs.litellm.ai/docs/providers/bytez) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Cerebras (`cerebras`)](https://docs.litellm.ai/docs/providers/cerebras) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Clarifai (`clarifai`)](https://docs.litellm.ai/docs/providers/clarifai) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [ClinePass (`clinepass`)](https://docs.litellm.ai/docs/providers/clinepass) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Cloudflare AI Workers (`cloudflare`)](https://docs.litellm.ai/docs/providers/cloudflare_workers) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Codestral (`codestral`)](https://docs.litellm.ai/docs/providers/codestral) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
| [Cognition (`cognition`)](https://docs.litellm.ai/docs/providers/cognition) | ✅ | ✅ | ✅ | | | | | | | |
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Documentation: https://docs.cline.bot/
|
|||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
from typing import Any, Final
|
||||
|
||||
import httpx
|
||||
|
||||
|
|
@ -27,10 +27,10 @@ from litellm.types.utils import ModelResponse
|
|||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
from ..common_utils import ClinePassException
|
||||
|
||||
CLINEPASS_API_BASE = "https://api.cline.bot/api/v1"
|
||||
CLINEPASS_API_BASE: Final = "https://api.cline.bot/api/v1"
|
||||
|
||||
# ClinePass nests the completion under this key on non-streaming responses.
|
||||
CLINEPASS_RESPONSE_ENVELOPE_KEY = "data"
|
||||
CLINEPASS_RESPONSE_ENVELOPE_KEY: Final = "data"
|
||||
|
||||
# The qualifier ClinePass expects on outbound model ids.
|
||||
#
|
||||
|
|
@ -42,14 +42,14 @@ CLINEPASS_RESPONSE_ENVELOPE_KEY = "data"
|
|||
# model an unrecognised namespace resolves to a different, date-pinned snapshot
|
||||
# (``cline-pass/deepseek-v4-flash`` -> ``deepseek/deepseek-v4-flash``, while
|
||||
# ``clinepass/deepseek-v4-flash`` -> ``deepseek/deepseek-v4-flash-0731``).
|
||||
CLINEPASS_MODEL_PREFIX = "cline-pass/"
|
||||
CLINEPASS_MODEL_PREFIX: Final = "cline-pass/"
|
||||
|
||||
# Headers that describe the original byte stream and would be wrong once the
|
||||
# body is rewritten by _unwrap_response_envelope().
|
||||
_BODY_SPECIFIC_HEADERS = ("content-length", "content-encoding")
|
||||
_BODY_SPECIFIC_HEADERS: Final = ("content-length", "content-encoding")
|
||||
|
||||
|
||||
def _as_positive_number(value: Any) -> Optional[float]:
|
||||
def _as_positive_number(value: Any) -> float | None:
|
||||
"""Return ``value`` as a positive number, or ``None`` if it is not one.
|
||||
|
||||
``bool`` is rejected explicitly: it is a subclass of ``int``, and ``True``
|
||||
|
|
@ -99,8 +99,6 @@ def _correct_truncated_finish_reason(response: ModelResponse, request_data: dict
|
|||
return response
|
||||
|
||||
max_tokens = _as_positive_number(request_data.get("max_tokens"))
|
||||
if max_tokens is None:
|
||||
max_tokens = _as_positive_number(request_data.get("max_completion_tokens"))
|
||||
if max_tokens is None:
|
||||
return response
|
||||
|
||||
|
|
@ -128,9 +126,8 @@ def _unwrap_response_envelope(raw_response: httpx.Response) -> httpx.Response:
|
|||
"""
|
||||
try:
|
||||
payload = raw_response.json()
|
||||
except (ValueError, httpx.StreamError):
|
||||
# Not a JSON body, or a streaming response that has not been read --
|
||||
# either way there is no envelope to strip.
|
||||
except ValueError:
|
||||
# Not a JSON body -- there is no envelope to strip.
|
||||
return raw_response
|
||||
|
||||
if not isinstance(payload, dict) or "choices" in payload:
|
||||
|
|
@ -153,7 +150,7 @@ def _unwrap_response_envelope(raw_response: httpx.Response) -> httpx.Response:
|
|||
return httpx.Response(
|
||||
status_code=raw_response.status_code,
|
||||
headers=headers,
|
||||
content=json.dumps(inner).encode("utf-8"),
|
||||
content=json.dumps(inner, ensure_ascii=False).encode("utf-8"),
|
||||
request=original_request,
|
||||
)
|
||||
|
||||
|
|
@ -180,7 +177,7 @@ class ClinePassConfig(OpenAIGPTConfig):
|
|||
|
||||
def _get_openai_compatible_provider_info(
|
||||
self, api_base: str | None, api_key: str | None
|
||||
) -> Tuple[str | None, str | None]:
|
||||
) -> tuple[str | None, str | None]:
|
||||
api_base = api_base or get_secret_str("CLINEPASS_API_BASE") or CLINEPASS_API_BASE
|
||||
dynamic_api_key = api_key or get_secret_str("CLINEPASS_API_KEY")
|
||||
return api_base, dynamic_api_key
|
||||
|
|
@ -203,7 +200,7 @@ class ClinePassConfig(OpenAIGPTConfig):
|
|||
|
||||
return f"{api_base}/chat/completions"
|
||||
|
||||
def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> List[str]:
|
||||
def get_models(self, api_key: str | None = None, api_base: str | None = None) -> list[str]:
|
||||
"""ClinePass exposes no model catalog.
|
||||
|
||||
``GET https://api.cline.bot/api/v1/models`` returns HTTP 404, and the
|
||||
|
|
@ -235,7 +232,7 @@ class ClinePassConfig(OpenAIGPTConfig):
|
|||
def transform_request(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[AllMessageValues],
|
||||
messages: list[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
headers: dict,
|
||||
|
|
@ -259,7 +256,7 @@ class ClinePassConfig(OpenAIGPTConfig):
|
|||
model_response: ModelResponse,
|
||||
logging_obj: Any,
|
||||
request_data: dict,
|
||||
messages: List[AllMessageValues],
|
||||
messages: list[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
encoding: Any,
|
||||
|
|
@ -282,7 +279,7 @@ class ClinePassConfig(OpenAIGPTConfig):
|
|||
return _correct_truncated_finish_reason(response, request_data)
|
||||
|
||||
def get_error_class(
|
||||
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
|
||||
self, error_message: str, status_code: int, headers: dict | httpx.Headers
|
||||
) -> BaseLLMException:
|
||||
return ClinePassException(
|
||||
message=error_message,
|
||||
|
|
|
|||
|
|
@ -2449,22 +2449,22 @@ def _complete_aiohttp_openai(
|
|||
|
||||
|
||||
def _complete_clinepass(ctx: _CompletionDispatchContext) -> _CompletionDispatchResult:
|
||||
acompletion = ctx.acompletion
|
||||
acompletion: Final = ctx.acompletion
|
||||
api_base = ctx.api_base
|
||||
api_key = ctx.api_key
|
||||
client = ctx.client
|
||||
custom_llm_provider = ctx.custom_llm_provider
|
||||
headers = ctx.headers
|
||||
litellm_params = ctx.litellm_params
|
||||
logging = ctx.logging
|
||||
messages = ctx.messages
|
||||
model = ctx.model
|
||||
model_response = ctx.model_response
|
||||
optional_params = ctx.optional_params
|
||||
provider_config = ctx.provider_config
|
||||
shared_session = ctx.shared_session
|
||||
stream = ctx.stream
|
||||
timeout = ctx.timeout
|
||||
client: Final = _dispatch_client_http(ctx)
|
||||
custom_llm_provider: Final = ctx.custom_llm_provider
|
||||
headers: Final = ctx.headers
|
||||
litellm_params: Final = ctx.litellm_params
|
||||
logging: Final = ctx.logging
|
||||
messages: Final = ctx.messages
|
||||
model: Final = ctx.model
|
||||
model_response: Final = ctx.model_response
|
||||
optional_params: Final = ctx.optional_params
|
||||
provider_config: Final = ctx.provider_config
|
||||
shared_session: Final = ctx.shared_session
|
||||
stream: Final = ctx.stream
|
||||
timeout: Final = ctx.timeout
|
||||
|
||||
api_key = api_key or get_secret_str("CLINEPASS_API_KEY") or litellm.api_key
|
||||
|
||||
|
|
@ -2473,7 +2473,7 @@ def _complete_clinepass(ctx: _CompletionDispatchContext) -> _CompletionDispatchR
|
|||
)
|
||||
|
||||
## COMPLETION CALL
|
||||
response = base_llm_http_handler.completion(
|
||||
response: Final = base_llm_http_handler.completion(
|
||||
model=model,
|
||||
messages=messages,
|
||||
headers=headers,
|
||||
|
|
@ -2493,9 +2493,6 @@ def _complete_clinepass(ctx: _CompletionDispatchContext) -> _CompletionDispatchR
|
|||
provider_config=provider_config,
|
||||
)
|
||||
|
||||
## LOGGING
|
||||
logging.post_call(input=messages, api_key=api_key, original_response=response)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export enum Providers {
|
|||
Cerebras = "Cerebras",
|
||||
CHATGPT = "ChatGPT Subscription",
|
||||
CLARIFAI = "Clarifai",
|
||||
CLINEPASS = "ClinePass",
|
||||
CLOUDFLARE = "Cloudflare",
|
||||
CODESTRAL = "Codestral",
|
||||
Cognition = "Cognition",
|
||||
|
|
@ -208,6 +209,7 @@ export const provider_map: Record<string, string> = {
|
|||
Cerebras: "cerebras",
|
||||
CHATGPT: "chatgpt",
|
||||
CLARIFAI: "clarifai",
|
||||
CLINEPASS: "clinepass",
|
||||
CLOUDFLARE: "cloudflare",
|
||||
CODESTRAL: "codestral",
|
||||
Cognition: "cognition",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue