mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-07-09 16:09:13 +00:00
fix(llm): fall back to non-Gemini model on Gemini content_filter (SKY-11766) (#7155)
Some checks are pending
Run tests and pre-commit / Run tests and pre-commit hooks (push) Waiting to run
Run tests and pre-commit / Frontend Lint and Build (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.11) (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.13) (push) Waiting to run
Publish Fern Docs / run (push) Waiting to run
Some checks are pending
Run tests and pre-commit / Run tests and pre-commit hooks (push) Waiting to run
Run tests and pre-commit / Frontend Lint and Build (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.11) (push) Waiting to run
Run tests and pre-commit / pip Package Smoke Tests (3.13) (push) Waiting to run
Publish Fern Docs / run (push) Waiting to run
This commit is contained in:
parent
c14b6cb95f
commit
90e29f9fc9
5 changed files with 185 additions and 5 deletions
|
|
@ -34,6 +34,7 @@ from skyvern.forge.sdk.api.llm.exceptions import (
|
|||
from skyvern.forge.sdk.api.llm.litellm_transport import configure_litellm_transport
|
||||
from skyvern.forge.sdk.api.llm.ui_tars_response import UITarsResponse
|
||||
from skyvern.forge.sdk.api.llm.utils import (
|
||||
is_content_filtered_response,
|
||||
is_image_message,
|
||||
is_truncated_response,
|
||||
llm_messages_builder,
|
||||
|
|
@ -1378,6 +1379,43 @@ class LLMAPIHandlerFactory:
|
|||
),
|
||||
)
|
||||
|
||||
# A content_filter response with empty content is a valid ModelResponse,
|
||||
# so litellm's router fallback (which only fires on exceptions) never
|
||||
# recovers it. Gemini's non-configurable safety filters block PII-heavy
|
||||
# prompts that safety_settings=BLOCK_NONE cannot recover, so retrying on
|
||||
# another Gemini tier hits the same block — jump straight to the first
|
||||
# non-Gemini fallback group. Fires for any Gemini model that produced the
|
||||
# block, including a standard tier litellm already fell back to (SKY-11766).
|
||||
non_gemini_fallback = next(
|
||||
(group for group in fallback_groups if "gemini" not in group.lower()), None
|
||||
)
|
||||
if (
|
||||
is_content_filtered_response(response)
|
||||
and non_gemini_fallback is not None
|
||||
and "gemini" in (model_used or "").lower()
|
||||
):
|
||||
fallback_model = non_gemini_fallback
|
||||
LOG.warning(
|
||||
"LLM response blocked by content filter on Gemini, retrying with non-Gemini fallback",
|
||||
llm_key=llm_key,
|
||||
prompt_name=prompt_name,
|
||||
filtered_model=model_used,
|
||||
fallback_model=fallback_model,
|
||||
)
|
||||
_llm_call_start = time.perf_counter()
|
||||
try:
|
||||
# No timeout= kwarg here either; see SKY-10200 note on
|
||||
# the primary call site above.
|
||||
response = await router.acompletion(
|
||||
model=fallback_model,
|
||||
messages=messages,
|
||||
drop_params=True,
|
||||
**parameters,
|
||||
)
|
||||
finally:
|
||||
llm_duration_seconds += time.perf_counter() - _llm_call_start
|
||||
model_used = response.model or fallback_model
|
||||
|
||||
# Error paths only set status=error, not token/cost attrs via
|
||||
# _enrich_llm_span — no response object exists so there's nothing to report.
|
||||
except litellm.exceptions.APIError as e:
|
||||
|
|
|
|||
|
|
@ -190,6 +190,20 @@ def is_truncated_response(response: litellm.ModelResponse) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def is_content_filtered_response(response: litellm.ModelResponse) -> bool:
|
||||
# Gemini's non-configurable safety filters (prohibited-content / SPII) block PII-heavy
|
||||
# prompts at the input stage, returning finish_reason=content_filter with empty content.
|
||||
# This is a valid ModelResponse, so litellm's router fallback never fires — the caller
|
||||
# must detect it and retry on a non-Gemini fallback explicitly (SKY-11766).
|
||||
if not response.choices:
|
||||
return False
|
||||
choice = response.choices[0]
|
||||
return (
|
||||
getattr(choice, "finish_reason", None) == "content_filter"
|
||||
and getattr(getattr(choice, "message", None), "content", None) is None
|
||||
)
|
||||
|
||||
|
||||
def parse_api_response(
|
||||
response: litellm.ModelResponse, add_assistant_prefix: bool = False, force_dict: bool = True
|
||||
) -> dict[str, Any] | Any:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue