mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge origin/litellm_internal_staging into litellm_databricks_claude_cache_pricing
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
commit
effe427394
14 changed files with 12688 additions and 12390 deletions
|
|
@ -440,6 +440,16 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
|||
"""
|
||||
return AnthropicModelInfo._supports_model_capability(model, "thinking_always_on", custom_llm_provider)
|
||||
|
||||
@staticmethod
|
||||
def _supports_legacy_thinking(model: str, custom_llm_provider: str) -> bool:
|
||||
"""Whether ``model`` is an adaptive-thinking model that still accepts legacy
|
||||
``thinking.type=enabled`` with ``budget_tokens`` (the Claude 4.6 family).
|
||||
The model cost map is authoritative: an explicit ``supports_legacy_thinking``
|
||||
entry resolved under ``custom_llm_provider``, or a ``fallback_generalizations``
|
||||
rule for unmapped 4.6 ids. Absent flag means the model rejects the legacy shape.
|
||||
"""
|
||||
return AnthropicModelInfo._supports_model_capability(model, "supports_legacy_thinking", custom_llm_provider)
|
||||
|
||||
@staticmethod
|
||||
def maybe_drop_disabled_thinking(
|
||||
model: str,
|
||||
|
|
|
|||
|
|
@ -379,13 +379,19 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
def _translate_legacy_thinking_for_adaptive_model(
|
||||
model: str, optional_params: dict, custom_llm_provider: str
|
||||
) -> None:
|
||||
"""Translate legacy ``thinking.type=enabled`` to adaptive for 4.6/4.7.
|
||||
Caller-provided ``output_config.effort`` is never overridden.
|
||||
"""Translate legacy ``thinking.type=enabled`` to adaptive for the
|
||||
adaptive-thinking models that reject it (4.7+ and the 5 families).
|
||||
Models flagged ``supports_legacy_thinking`` (the 4.6 family) accept the
|
||||
legacy shape natively, so it is forwarded verbatim and the caller's
|
||||
``budget_tokens`` cap keeps applying. Caller-provided
|
||||
``output_config.effort`` is never overridden.
|
||||
"""
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
|
||||
if not AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider):
|
||||
return
|
||||
if AnthropicModelInfo._supports_legacy_thinking(model, custom_llm_provider):
|
||||
return
|
||||
thinking: Final = optional_params.get("thinking")
|
||||
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
|
||||
return
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from typing import Any, Final
|
||||
|
||||
import orjson
|
||||
from fastapi import APIRouter, Depends, File, Form, Request, Response, UploadFile
|
||||
from fastapi.responses import ORJSONResponse
|
||||
|
||||
|
|
@ -20,6 +19,7 @@ from litellm.proxy.video_endpoints.utils import (
|
|||
encode_character_id_in_response,
|
||||
extract_model_from_target_model_names,
|
||||
get_custom_provider_from_data,
|
||||
video_reference_to_id,
|
||||
)
|
||||
from litellm.types.videos.utils import (
|
||||
decode_character_id_with_provider,
|
||||
|
|
@ -451,9 +451,7 @@ async def video_remix(
|
|||
version,
|
||||
)
|
||||
|
||||
# Read request body
|
||||
body: Final = await request.body()
|
||||
data: Final = orjson.loads(body)
|
||||
data: Final = await _read_request_body(request=request)
|
||||
data["video_id"] = video_id
|
||||
|
||||
decoded: Final = decode_video_id_with_provider(video_id)
|
||||
|
|
@ -760,15 +758,10 @@ async def video_edit(
|
|||
version,
|
||||
)
|
||||
|
||||
body: Final = await request.body()
|
||||
data: Final = orjson.loads(body)
|
||||
data: Final = await _read_request_body(request=request)
|
||||
data["video_id"] = video_reference_to_id(data.pop("video", None))
|
||||
|
||||
# Extract video_id from nested video object
|
||||
video_ref: Final = data.pop("video", {})
|
||||
video_id: Final = video_ref.get("id", "") if isinstance(video_ref, dict) else ""
|
||||
data["video_id"] = video_id
|
||||
|
||||
decoded: Final = decode_video_id_with_provider(video_id)
|
||||
decoded: Final = decode_video_id_with_provider(data["video_id"])
|
||||
provider_from_id: Final = decoded.get("custom_llm_provider")
|
||||
model_id_from_decoded: Final = decoded.get("model_id")
|
||||
|
||||
|
|
@ -860,15 +853,10 @@ async def video_extension(
|
|||
version,
|
||||
)
|
||||
|
||||
body: Final = await request.body()
|
||||
data: Final = orjson.loads(body)
|
||||
data: Final = await _read_request_body(request=request)
|
||||
data["video_id"] = video_reference_to_id(data.pop("video", None))
|
||||
|
||||
# Extract video_id from nested video object
|
||||
video_ref: Final = data.pop("video", {})
|
||||
video_id: Final = video_ref.get("id", "") if isinstance(video_ref, dict) else ""
|
||||
data["video_id"] = video_id
|
||||
|
||||
decoded: Final = decode_video_id_with_provider(video_id)
|
||||
decoded: Final = decode_video_id_with_provider(data["video_id"])
|
||||
provider_from_id: Final = decoded.get("custom_llm_provider")
|
||||
model_id_from_decoded: Final = decoded.get("model_id")
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,18 @@ def extract_model_from_target_model_names(target_model_names: Any) -> str | None
|
|||
return target_model_names[0] if target_model_names else None
|
||||
|
||||
|
||||
def video_reference_to_id(video_ref: object) -> str:
|
||||
if isinstance(video_ref, dict):
|
||||
return video_ref.get("id", "")
|
||||
if not isinstance(video_ref, str):
|
||||
return ""
|
||||
try:
|
||||
parsed_ref: Final = orjson.loads(video_ref)
|
||||
except orjson.JSONDecodeError:
|
||||
return video_ref
|
||||
return parsed_ref.get("id", "") if isinstance(parsed_ref, dict) else video_ref
|
||||
|
||||
|
||||
def get_custom_provider_from_data(data: dict[str, Any]) -> str | None:
|
||||
custom_llm_provider: Final = data.get("custom_llm_provider")
|
||||
if custom_llm_provider:
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
|
|||
supports_web_search: bool | None
|
||||
supports_reasoning: bool | None
|
||||
supports_adaptive_thinking: bool | None
|
||||
supports_legacy_thinking: ReadOnly[bool | None]
|
||||
thinking_always_on: ReadOnly[bool | None]
|
||||
supports_tool_search: bool | None
|
||||
supports_mid_conversation_system: bool | None
|
||||
|
|
|
|||
|
|
@ -5753,6 +5753,7 @@ def _get_model_info_helper(
|
|||
supports_url_context=_model_info.get("supports_url_context", None),
|
||||
supports_reasoning=_model_info.get("supports_reasoning", None),
|
||||
supports_adaptive_thinking=_model_info.get("supports_adaptive_thinking", None),
|
||||
supports_legacy_thinking=_model_info.get("supports_legacy_thinking", None),
|
||||
thinking_always_on=_model_info.get("thinking_always_on", None),
|
||||
supports_tool_search=_model_info.get("supports_tool_search", None),
|
||||
supports_mid_conversation_system=_model_info.get("supports_mid_conversation_system", None),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -659,6 +659,9 @@
|
|||
"supports_image_size": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"supports_legacy_thinking": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"supports_low_reasoning_effort": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.constants import (
|
||||
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
|
|
@ -17,7 +16,6 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
|
|||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning_effort,expected_effort",
|
||||
[
|
||||
|
|
@ -258,19 +256,22 @@ def test_reasoning_effort_in_supported_params():
|
|||
"model",
|
||||
[
|
||||
"claude-sonnet-4-6",
|
||||
"bedrock/invoke/us.anthropic.claude-sonnet-4-6",
|
||||
"vertex_ai/claude-sonnet-4-6",
|
||||
"claude-opus-4-6",
|
||||
"claude-sonnet-4-6-20260219",
|
||||
"bedrock/invoke/us.anthropic.claude-sonnet-4-6",
|
||||
"bedrock/invoke/us.anthropic.claude-opus-4-6-v1:0",
|
||||
"vertex_ai/claude-sonnet-4-6",
|
||||
"vertex_ai/claude-opus-4-6",
|
||||
"azure_ai/claude-sonnet-4-6",
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported(
|
||||
local_model_cost_map, model
|
||||
):
|
||||
"""Claude Code sends ``thinking.budget_tokens=31999``; Sonnet 4.6 and Opus 4.6
|
||||
have no ``xhigh`` tier, so the translator must emit ``high`` rather than the
|
||||
provider-invalid ``xhigh`` (regression for issue #29282)."""
|
||||
def test_legacy_thinking_budget_preserved_verbatim_on_46(local_model_cost_map, model):
|
||||
"""Regression for the passthrough silently dropping a caller's hard thinking
|
||||
budget: the 4.6 family accepts ``thinking.type=enabled`` with ``budget_tokens``
|
||||
natively, so rewriting it to ``thinking.type=adaptive`` + ``output_config.effort``
|
||||
(which carries no ceiling) let reasoning run past the requested cap. The legacy
|
||||
shape must be forwarded verbatim, in every 4.6 id shape including unmapped dated
|
||||
releases resolved by the ``claude-legacy-thinking`` fallback rule."""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
|
|
@ -285,8 +286,8 @@ def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported(
|
|||
headers={},
|
||||
)
|
||||
|
||||
assert result.get("thinking") == {"type": "adaptive"}
|
||||
assert result.get("output_config") == {"effort": "high"}
|
||||
assert result.get("thinking") == {"type": "enabled", "budget_tokens": 31999}
|
||||
assert "output_config" not in result
|
||||
|
||||
|
||||
def test_legacy_thinking_high_budget_keeps_xhigh_when_supported():
|
||||
|
|
@ -343,11 +344,44 @@ def test_legacy_thinking_translates_to_adaptive_for_opus_48(
|
|||
assert result.get("output_config") == {"effort": "xhigh"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,expected_effort",
|
||||
[
|
||||
("claude-sonnet-5", "xhigh"),
|
||||
("claude-opus-5", "xhigh"),
|
||||
("claude-newfamily-6", "high"),
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_translates_to_adaptive_for_5_and_future_models(
|
||||
local_model_cost_map, model, expected_effort
|
||||
):
|
||||
"""The 5 families reject ``thinking.type=enabled``, so the adaptive translation
|
||||
stays the safe default for every adaptive model not flagged
|
||||
``supports_legacy_thinking``, unmapped future ids included. An unmapped id
|
||||
cannot prove ``xhigh`` support, so its high-budget bucket clamps to ``high``."""
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
"thinking": {"type": "enabled", "budget_tokens": 31999},
|
||||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result.get("thinking") == {"type": "adaptive"}
|
||||
assert result.get("output_config") == {"effort": expected_effort}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"budget_tokens,expected_effort",
|
||||
[
|
||||
(DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET * 2, "high"),
|
||||
(DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, "high"),
|
||||
(DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET * 2, "xhigh"),
|
||||
(DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, "xhigh"),
|
||||
(DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, "high"),
|
||||
(DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET - 1, "medium"),
|
||||
(DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, "medium"),
|
||||
|
|
@ -355,7 +389,9 @@ def test_legacy_thinking_translates_to_adaptive_for_opus_48(
|
|||
(1, "low"),
|
||||
],
|
||||
)
|
||||
def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_effort):
|
||||
def test_legacy_thinking_budget_buckets_on_opus_48(
|
||||
local_model_cost_map, budget_tokens, expected_effort
|
||||
):
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
|
|
@ -363,7 +399,7 @@ def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_eff
|
|||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-sonnet-4-6",
|
||||
model="claude-opus-4-8",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
|
|
@ -373,7 +409,29 @@ def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_eff
|
|||
assert result.get("output_config") == {"effort": expected_effort}
|
||||
|
||||
|
||||
def test_legacy_thinking_does_not_override_explicit_output_config():
|
||||
def test_legacy_thinking_does_not_override_explicit_output_config(local_model_cost_map):
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
"thinking": {"type": "enabled", "budget_tokens": 31999},
|
||||
"output_config": {"effort": "low"},
|
||||
}
|
||||
|
||||
result = config.transform_anthropic_messages_request(
|
||||
model="claude-opus-4-8",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
anthropic_messages_optional_request_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result.get("thinking") == {"type": "adaptive"}
|
||||
assert result.get("output_config") == {"effort": "low"}
|
||||
|
||||
|
||||
def test_legacy_thinking_with_explicit_output_config_untouched_on_46(
|
||||
local_model_cost_map,
|
||||
):
|
||||
config = AnthropicMessagesConfig()
|
||||
optional_params = {
|
||||
"max_tokens": 1024,
|
||||
|
|
@ -389,6 +447,7 @@ def test_legacy_thinking_does_not_override_explicit_output_config():
|
|||
headers={},
|
||||
)
|
||||
|
||||
assert result.get("thinking") == {"type": "enabled", "budget_tokens": 31999}
|
||||
assert result.get("output_config") == {"effort": "low"}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -372,6 +372,7 @@ async def test_content__model_encoded_id(harness):
|
|||
async def call_edit(
|
||||
harness: Harness, *, body: Dict[str, Any], headers=None, query=None
|
||||
):
|
||||
harness.read_body.return_value = dict(body)
|
||||
return await endpoints.video_edit(
|
||||
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
|
||||
fastapi_response=Response(),
|
||||
|
|
@ -428,6 +429,27 @@ async def test_edit__missing_video_object_defaults_to_openai(harness):
|
|||
assert "video" not in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit__bare_string_video_id_from_form_field(harness):
|
||||
await call_edit(harness, body={"prompt": "brighter", "video": "video_plain"})
|
||||
|
||||
assert harness.processor_data() == {
|
||||
"prompt": "brighter",
|
||||
"video_id": "video_plain",
|
||||
"custom_llm_provider": "openai",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit__json_string_video_reference_from_form_field(harness):
|
||||
await call_edit(
|
||||
harness,
|
||||
body={"prompt": "brighter", "video": orjson.dumps({"id": "video_plain"}).decode()},
|
||||
)
|
||||
|
||||
assert harness.processor_data()["video_id"] == "video_plain"
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# GET /v1/videos - video_list #
|
||||
# =========================================================================== #
|
||||
|
|
@ -471,6 +493,7 @@ async def test_list__provider_from_header(harness):
|
|||
async def call_remix(
|
||||
harness: Harness, video_id: str, *, body, headers=None, query=None
|
||||
):
|
||||
harness.read_body.return_value = dict(body)
|
||||
return await endpoints.video_remix(
|
||||
video_id=video_id,
|
||||
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
|
||||
|
|
@ -629,6 +652,7 @@ async def test_get_character__plain_id_defaults_openai_no_encode(harness):
|
|||
|
||||
|
||||
async def call_extension(harness: Harness, *, body, headers=None, query=None):
|
||||
harness.read_body.return_value = dict(body)
|
||||
return await endpoints.video_extension(
|
||||
request=FakeRequest(headers=headers, query=query, raw_body=orjson.dumps(body)),
|
||||
fastapi_response=Response(),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""
|
||||
Pure-logic contract tests for litellm/proxy/video_endpoints/utils.py
|
||||
|
||||
Three helpers the video proxy endpoints lean on:
|
||||
Four helpers the video proxy endpoints lean on:
|
||||
- extract_model_from_target_model_names: first model from a comma string / list
|
||||
- video_reference_to_id: normalize a video reference (dict / bare id / JSON string) to an id
|
||||
- get_custom_provider_from_data: provider precedence (top-level > extra_body)
|
||||
- encode_character_id_in_response: re-encode a response id in place
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ from litellm.proxy.video_endpoints.utils import (
|
|||
encode_character_id_in_response,
|
||||
extract_model_from_target_model_names,
|
||||
get_custom_provider_from_data,
|
||||
video_reference_to_id,
|
||||
)
|
||||
from litellm.types.videos.utils import (
|
||||
decode_character_id_with_provider,
|
||||
|
|
@ -53,6 +55,31 @@ def test_extract_model__non_str_non_list_is_none(value):
|
|||
assert extract_model_from_target_model_names(value) is None
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# video_reference_to_id
|
||||
# =========================================================================== #
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"video_ref,expected",
|
||||
[
|
||||
({"id": "video_123"}, "video_123"), # dict reference -> its id
|
||||
({"id": ""}, ""), # dict with empty id
|
||||
({}, ""), # dict missing id -> default empty
|
||||
({"other": "x"}, ""), # dict without id key
|
||||
("video_123", "video_123"), # bare id string (not valid JSON) -> itself
|
||||
('{"id": "video_9"}', "video_9"), # JSON-encoded dict -> its id
|
||||
('{"other": 1}', ""), # JSON-encoded dict without id -> empty
|
||||
("[1, 2]", "[1, 2]"), # JSON parses to non-dict -> original string
|
||||
(None, ""), # non-str, non-dict
|
||||
(123, ""), # non-str, non-dict
|
||||
(["video_123"], ""), # list is neither dict nor str
|
||||
],
|
||||
)
|
||||
def test_video_reference_to_id(video_ref, expected):
|
||||
assert video_reference_to_id(video_ref) == expected
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# get_custom_provider_from_data
|
||||
# =========================================================================== #
|
||||
|
|
|
|||
|
|
@ -1001,6 +1001,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
|
|||
"supports_xhigh_reasoning_effort": {"type": "boolean"},
|
||||
"supports_max_reasoning_effort": {"type": "boolean"},
|
||||
"supports_adaptive_thinking": {"type": "boolean"},
|
||||
"supports_legacy_thinking": {"type": "boolean"},
|
||||
"thinking_always_on": {"type": "boolean"},
|
||||
"supports_mid_conversation_system": {"type": "boolean"},
|
||||
"supports_sampling_params": {"type": "boolean"},
|
||||
|
|
|
|||
|
|
@ -2316,6 +2316,72 @@ def test_edit_and_extension_support_custom_provider_from_extra_body(
|
|||
assert captured_data["custom_llm_provider"] == "vertex_ai"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"handler_name, path, form",
|
||||
[
|
||||
(
|
||||
"video_edit",
|
||||
"/v1/videos/edits",
|
||||
{"model": "my-video-model", "prompt": "brighter", "video": "video_123"},
|
||||
),
|
||||
(
|
||||
"video_extension",
|
||||
"/v1/videos/extensions",
|
||||
{"model": "my-video-model", "prompt": "continue", "seconds": "4", "video": "video_123"},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_and_extension_read_cached_body_after_auth_consumes_stream(
|
||||
handler_name, path, form
|
||||
):
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from fastapi import Response
|
||||
from starlette.requests import Request
|
||||
|
||||
import litellm.proxy.video_endpoints.endpoints as endpoints
|
||||
from litellm.proxy._types import ProxyException, UserAPIKeyAuth
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
|
||||
|
||||
body = urlencode(form).encode()
|
||||
stream = {"sent": False}
|
||||
|
||||
async def receive():
|
||||
if stream["sent"]:
|
||||
return {"type": "http.request", "body": b"", "more_body": False}
|
||||
stream["sent"] = True
|
||||
return {"type": "http.request", "body": body, "more_body": False}
|
||||
|
||||
request = Request(
|
||||
{
|
||||
"type": "http",
|
||||
"method": "POST",
|
||||
"path": path,
|
||||
"headers": [
|
||||
(b"content-type", b"application/x-www-form-urlencoded"),
|
||||
(b"content-length", str(len(body)).encode()),
|
||||
],
|
||||
"query_string": b"",
|
||||
},
|
||||
receive,
|
||||
)
|
||||
|
||||
await _read_request_body(request=request)
|
||||
|
||||
handler = getattr(endpoints, handler_name)
|
||||
with pytest.raises(ProxyException) as exc_info:
|
||||
await handler(
|
||||
request=request,
|
||||
fastapi_response=Response(),
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-1234"),
|
||||
)
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "Stream consumed" not in message
|
||||
assert "my-video-model" in message
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ["/v1/videos/edits", "/v1/videos/extensions"])
|
||||
def test_edit_and_extension_route_with_encoded_video_ids(
|
||||
video_proxy_test_client, endpoint
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue