Improve LLM error message when LLM is down (#3874)

This commit is contained in:
pedrohsdb
2025-10-31 11:41:07 -07:00
committed by GitHub
parent 393bae88fe
commit 0e0ae81693
2 changed files with 93 additions and 1 deletions

View File

@@ -71,6 +71,7 @@ from skyvern.forge.sdk.api.files import (
wait_for_download_finished,
)
from skyvern.forge.sdk.api.llm.api_handler_factory import LLMAPIHandlerFactory, LLMCaller, LLMCallerManager
from skyvern.forge.sdk.api.llm.exceptions import LLM_PROVIDER_ERROR_RETRYABLE_TASK_TYPE, LLM_PROVIDER_ERROR_TYPE
from skyvern.forge.sdk.api.llm.ui_tars_llm_caller import UITarsLLMCaller
from skyvern.forge.sdk.artifact.models import ArtifactType
from skyvern.forge.sdk.core import skyvern_context
@@ -2862,6 +2863,8 @@ class ForgeAgent:
page: Page | None,
) -> MaxStepsReasonResponse:
steps_results = []
llm_errors: list[str] = []
try:
steps = await app.DATABASE.get_task_steps(
task_id=task.task_id, organization_id=organization.organization_id
@@ -2888,12 +2891,37 @@ class ForgeAgent:
for action, action_results in step.output.actions_and_results:
if len(action_results) == 0:
continue
last_result = action_results[-1]
# Check if this is an LLM provider error
if not last_result.success:
exception_type = last_result.exception_type or ""
exception_message = last_result.exception_message or ""
if (
exception_type in (LLM_PROVIDER_ERROR_TYPE, LLM_PROVIDER_ERROR_RETRYABLE_TASK_TYPE)
or "LLMProvider" in exception_message
):
llm_errors.append(f"Step {step_cnt}: {exception_message}")
action_result_summary.append(
f"{action.reasoning}(action_type={action.action_type}, result={'success' if action_results[-1].success else 'failed'})"
f"{action.reasoning}(action_type={action.action_type}, result={'success' if last_result.success else 'failed'})"
)
step_result["actions_result"] = action_result_summary
steps_results.append(step_result)
# If we detected LLM errors, return a clear message without calling the LLM
if llm_errors:
llm_error_details = "; ".join(llm_errors)
return MaxStepsReasonResponse(
page_info="",
reasoning=(
f"The task failed due to LLM service errors. The LLM provider encountered errors and was unable to process the requests. "
f"This is typically caused by rate limiting, service outages, or resource exhaustion from the LLM provider. "
f"Error details: {llm_error_details}"
),
errors=[],
)
scroll = True
if await service_utils.is_cua_task(task=task):
scroll = False
@@ -2917,6 +2945,17 @@ class ForgeAgent:
return MaxStepsReasonResponse.model_validate(json_response)
except Exception:
LOG.warning("Failed to summary the failure reason")
# Check if we have LLM errors even if the summarization failed
if llm_errors:
llm_error_details = "; ".join(llm_errors)
return MaxStepsReasonResponse(
page_info="",
reasoning=(
f"The task failed due to LLM service errors. The LLM provider encountered errors and was unable to process the requests. "
f"Error details: {llm_error_details}"
),
errors=[],
)
if steps_results:
last_step_result = steps_results[-1]
return MaxStepsReasonResponse(
@@ -2941,11 +2980,21 @@ class ForgeAgent:
html = ""
screenshots: list[bytes] = []
steps_results = []
llm_errors: list[str] = []
steps_without_actions = 0
try:
steps = await app.DATABASE.get_task_steps(
task_id=task.task_id, organization_id=organization.organization_id
)
# Check for LLM provider errors in the failed steps
for step_cnt, cur_step in enumerate(steps[-max_retries:]):
if cur_step.status == StepStatus.failed:
# If step failed with no actions, it might be an LLM error during action extraction
if not cur_step.output or not cur_step.output.actions_and_results:
steps_without_actions += 1
if cur_step.output and cur_step.output.actions_and_results:
action_result_summary: list[str] = []
step_result: dict[str, Any] = {
@@ -2958,12 +3007,38 @@ class ForgeAgent:
if last_result.success:
continue
reason = last_result.exception_message or ""
# Check if this is an LLM provider error
exception_type = last_result.exception_type or ""
if (
exception_type in (LLM_PROVIDER_ERROR_TYPE, LLM_PROVIDER_ERROR_RETRYABLE_TASK_TYPE)
or "LLMProvider" in reason
):
llm_errors.append(f"Step {step_cnt}: {reason}")
action_result_summary.append(
f"{action.reasoning}(action_type={action.action_type}, result=failed, reason={reason})"
)
step_result["actions_result"] = action_result_summary
steps_results.append(step_result)
# If we detected LLM errors, return a clear message without calling the LLM
if llm_errors:
llm_error_details = "; ".join(llm_errors)
return (
f"The task failed due to LLM service errors. The LLM provider encountered errors and was unable to process the requests. "
f"This is typically caused by rate limiting, service outages, or resource exhaustion from the LLM provider. "
f"Error details: {llm_error_details}"
)
# If multiple steps failed without producing any actions, it's likely an LLM error during action extraction
if steps_without_actions >= max_retries:
return (
f"The task failed because all {max_retries} retry attempts failed to generate actions. "
f"This is typically caused by LLM service errors during action extraction, such as rate limiting, "
f"service outages, or resource exhaustion from the LLM provider. Please check the LLM service status and try again."
)
if page is not None:
skyvern_frame = await SkyvernFrame.create_instance(frame=page)
html = await skyvern_frame.get_content()
@@ -2987,6 +3062,19 @@ class ForgeAgent:
return json_response.get("reasoning", "")
except Exception:
LOG.warning("Failed to summarize the failure reason for max retries")
# Check if we have LLM errors even if the summarization failed
if llm_errors:
llm_error_details = "; ".join(llm_errors)
return (
f"The task failed due to LLM service errors. The LLM provider encountered errors and was unable to process the requests. "
f"Error details: {llm_error_details}"
)
# If multiple steps failed without actions during summarization failure, still report it
if steps_without_actions >= max_retries:
return (
f"The task failed because all {max_retries} retry attempts failed to generate actions. "
f"This is typically caused by LLM service errors during action extraction."
)
if steps_results:
last_step_result = steps_results[-1]
return f"Retry Step {last_step_result['order']}: {last_step_result['actions_result']}"

View File

@@ -1,5 +1,9 @@
from skyvern.exceptions import SkyvernException
# Exception type name constants
LLM_PROVIDER_ERROR_TYPE = "LLMProviderError"
LLM_PROVIDER_ERROR_RETRYABLE_TASK_TYPE = "LLMProviderErrorRetryableTask"
class BaseLLMError(SkyvernException):
pass