litellm/litellm/systemone/main.py
devin-ai-integration[bot] 2f3ccbe7c7
feat(decisions)!: send images to Cloudflare Clef through its images field (#45734)
* feat(decisions): send images to Cloudflare Clef

OpenAI-format input_image parts and the System One images field now reach Cloudflare clef and clef-flash in its images field, with remote URLs downloaded and inlined first. Providers that cannot take images drop them under drop_params and answer 400 otherwise.

* fix(decisions): follow drop_params for images and use the call timeout for image downloads

* fix(decisions): drop a malformed images entry on its own under drop_params

* fix(decisions): refuse images next to input whatever drop_params says

* fix(decisions): bound clef image fetches, honor drop_params for images, and log caller URLs

Refuse more than 4 images for Cloudflare clef before any download, keep the
caller's timeout as one deadline across image fetch retries, accept images
next to input under drop_params, validate image strings and objects, and pass
the caller's image URLs (not inlined bytes) to logging callbacks.

* fix(decisions): check input_image URLs and clef image sizes

* fix(decisions): accept data URL media type parameters and check clef image types

* fix(decisions): count clef images after dropping unsupported types and flag clef vision

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-14 12:14:48 +00:00

125 lines
3.8 KiB
Python

from collections.abc import Mapping
from typing import Final
import httpx
from litellm.decisions.call import (
DecisionsAudioClips,
DecisionsCall,
DecisionsImages,
DecisionsVideoClips,
SystemOneQuestions,
SystemOneRequestFields,
asend,
prepare_call,
reject_other_format,
send,
)
from litellm.types.decisions import DecisionsJSON, DecisionsResponse
from litellm.utils import client # pyright: ignore[reportUnknownVariableType] # client is an untyped decorator
def _prepare_systemone_call(
*,
model: str,
state: DecisionsJSON | None,
questions: SystemOneQuestions | None,
images: DecisionsImages | None,
safety_identifier: str | None,
audio: DecisionsAudioClips | None,
videos: DecisionsVideoClips | None,
api_key: str | None,
api_base: str | None,
timeout: float | httpx.Timeout | None,
custom_llm_provider: str | None,
extra_headers: Mapping[str, str] | None,
kwargs: Mapping[str, object],
) -> DecisionsCall:
reject_other_format(model=model, kwargs=kwargs, field_name="input", other_function="decisions")
return prepare_call(
model=model,
fields=SystemOneRequestFields(state=state, questions=questions, images=images, audio=audio, videos=videos),
safety_identifier=safety_identifier,
api_key=api_key,
api_base=api_base,
timeout=timeout,
custom_llm_provider=custom_llm_provider,
extra_headers=extra_headers,
kwargs=kwargs,
)
def _systemone_response(response: object) -> DecisionsResponse:
assert isinstance(response, DecisionsResponse)
return response
@client
async def asystemone(
model: str,
state: DecisionsJSON | None = None,
questions: SystemOneQuestions | None = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: float | httpx.Timeout | None = None,
custom_llm_provider: str | None = None,
extra_headers: Mapping[str, str] | None = None,
safety_identifier: str | None = None,
audio: DecisionsAudioClips | None = None,
videos: DecisionsVideoClips | None = None,
images: DecisionsImages | None = None,
**kwargs: object, # kwargs-ok: litellm_params forwarded by @client
) -> DecisionsResponse:
call: Final = _prepare_systemone_call(
model=model,
state=state,
questions=questions,
images=images,
safety_identifier=safety_identifier,
audio=audio,
videos=videos,
api_key=api_key,
api_base=api_base,
timeout=timeout,
custom_llm_provider=custom_llm_provider,
extra_headers=extra_headers,
kwargs=kwargs,
)
return _systemone_response(await asend(call))
@client
def systemone(
model: str,
state: DecisionsJSON | None = None,
questions: SystemOneQuestions | None = None,
api_key: str | None = None,
api_base: str | None = None,
timeout: float | httpx.Timeout | None = None,
custom_llm_provider: str | None = None,
extra_headers: Mapping[str, str] | None = None,
safety_identifier: str | None = None,
audio: DecisionsAudioClips | None = None,
videos: DecisionsVideoClips | None = None,
images: DecisionsImages | None = None,
**kwargs: object, # kwargs-ok: litellm_params forwarded by @client
) -> DecisionsResponse:
call: Final = _prepare_systemone_call(
model=model,
state=state,
questions=questions,
images=images,
safety_identifier=safety_identifier,
audio=audio,
videos=videos,
api_key=api_key,
api_base=api_base,
timeout=timeout,
custom_llm_provider=custom_llm_provider,
extra_headers=extra_headers,
kwargs=kwargs,
)
return _systemone_response(send(call))
__all__ = ["asystemone", "systemone"]