You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deploy an app with a dial-deployment toolset whose tool targets an image-generation deployment (gemini-2.5-flash-image), with the default fallback_configuration.strategies: [{"type": "continue"}]. Full app config at the bottom of this issue.
Exhaust the deployment's token limit for that user — e.g. the model's Daily limit is 3 000 tokens and the user is already at 4 467 (visible in DIAL Chat's model info popup).
Ask the agent to generate an image: generate cat image.
The orchestrator calls image_generation_tool; DIAL Core rejects the downstream completion with HTTP 429.
What is the expected behavior?
Two things should carry the real cause:
1. The stage should show the rate-limit reason, matching the wording the top-level orchestrator path already produces for the very same 429 through resolve_exception() in src/quickapp/core/application/_exception_message_resolver.py:
> #### Error:
The request was rate-limited by the AI model service. Please try again later.
> #### Status Code:
429
When the upstream supplies a user-safe display_message (DIAL Core does for quota errors), that text should win — it is the only place the actual limit numbers live.
2. The model-facing fallback message should include the cause, so a continue strategy lets the LLM tell the user "the image model is over its daily quota" instead of silently trying something else or claiming an unspecified failure.
What do you see instead?
The stage renders a constant string that names no cause and no status code:
Image generation: A beautiful, highly detailed close-up portrait of a fluffy tabby cat with bright…
> Request:
attachment_urls: []
query: A beautiful, highly detailed close-up portrait of a fluffy tabby cat with bright green eyes, sitting near a sunlit window. …
> Exception:
General exception occurred while calling other DIAL deployment
And the LLM receives only the generic ContinueStrategyHandler._DEFAULT_INSTRUCTIONS ("An error occurs, try to call another applicable tool with the same functionality…"), with no mention of a rate limit — so it cannot explain to the user what actually happened or that retrying later would work.
Net effect: a perfectly diagnosable, user-actionable quota failure is presented to the user as an unknown error.
Additional information
Root cause.DeploymentStageWrapper._build_debug_info_from_exception only special-cases aidial_client.DialException:
def_build_debug_info_from_exception(self, exception: Exception) ->str:
ifisinstance(exception, DialException):
return (
f"> #### Error:\n{exception.message}\n"f"> #### Status Code:\n{exception.status_code}\n"
)
return"> #### Exception:\nGeneral exception occurred while calling other DIAL deployment\n"
But the downstream completion is issued through the openai client, not aidial_client:
DialCompletionService is injected with DEPLOYMENT_AZURE_CLIENT, which is Annotated[AsyncAzureOpenAI, "DEPLOYMENT_AZURE_CLIENT"] (src/quickapp/common/_di_types.py:17);
await self.__azure_client.chat.completions.create(**chat_params) therefore raises openai.RateLimitError / openai.APIStatusError on a 429, never aidial_client.DialException.
aidial_client.DialException is only ever raised on the dial_files_tooling path (which uses AsyncDial). So the isinstance branch above is dead code for every DIAL-deployment tool call — every upstream failure (429, 403, 404, 5xx, content filter, context length) collapses into the same "General exception" sentence.
Flow.openai.RateLimitError propagates out of DialCompletionService.complete_request_async → translate_timeout passes it through (not a timeout) → StagedBaseTool catches it at src/quickapp/common/staged_base_tool.py:224-231, calls _report_error_to_stage (→ the generic text above) and then FallbackProcessor.process_fallback.
Why the model is left in the dark too.compose_tool_error_fallback_message (src/quickapp/common/tool_fallback/utils.py) forwards the error text only when the exception is a ToolErrorExceptionandforward_tool_error_message is enabled. The deployment path never wraps upstream failures in ToolErrorException, so even setting forward_tool_error_message: true on the continue strategy would not surface the 429 to the LLM.
Suggested fix.
Reuse the existing resolver instead of the ad-hoc isinstance check — resolve_exception() in src/quickapp/core/application/_exception_message_resolver.py already handles openai.APIError, unwraps DIAL Core's nested {"error": {...}} body, honours display_message, maps code (content_filter, context_length_exceeded) and the full status ladder including 429, and classifies retryability. It was built for the top-level path in Resolve error causes and deliver failures through the DIAL error protocol #411; the tool stages should not be re-deriving a worse version of it.
Render status code alongside the resolved message in DeploymentStageWrapper, gated as today by fallback_configuration.display_error_in_stage.
Make the resolved cause reachable by the model — e.g. by wrapping the upstream failure in ToolErrorException (or an equivalent) so the existing forward_tool_error_message switch from Expose Tool Errors to the LLM #407 applies to DIAL-deployment tools too.
Same bug elsewhere._PyInterpreterStageWrapper._build_debug_info_from_exception (src/quickapp/internal_tooling/py_interpreter_tooling/_py_interpreter_stage_wrapper.py:14) returns the identical "General exception occurred while calling other DIAL deployment" string unconditionally — and the py-interpreter is not even a DIAL deployment call, so the message is also factually wrong there. Worth fixing in the same change.
Related issues.#411 (resolver introduced for the top-level path, closed), #407 (Expose Tool Errors to the LLM), #440 (Stage content disclosure policy), #455 (standardize file-tool error flow onto ToolErrorException).
App config used to reproduce
{
"name": "pg-chat-hub",
"display_name": "PG ChatHub",
"display_version": "1.0.0",
"description": "Agent with vision, image generation, documents RAG and web search capabilities.",
"application_properties": {
"orchestrator": {
"deployment": { "name": "gemini-3.5-flash-google-search" },
"system_prompt": { "type": "predefined", "template": "gemini_prompt" },
"attachment_strategy": { "type": "lazy_on_demand" }
},
"contexts": [],
"tool_sets": [
{
"name": "chat-hub",
"description": "Chat Hub tool set",
"type": "dial-deployment",
"tools": [
{
"type": "deployment-tool",
"display": { "stage": { "name": "Image generation: " } },
"deployment": { "name": "gemini-2.5-flash-image" },
"content_propagation": { "propagate_history": true, "propagate_headers": [] },
"open_ai_tool": {
"type": "function",
"function": {
"name": "image_generation_tool",
"description": "Generates an image from a text description. Use this tool when the user asks to create, draw, generate, or produce an image, illustration, picture, or visual based on a description.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "A clear, detailed description of the image to generate.",
"display": { "stage": { "show_value_in_stage_title": true } }
}
},
"required": ["query"]
}
}
},
"fallback_configuration": { "strategies": [{ "type": "continue" }] }
}
]
},
{ "type": "dial-mcp", "deployment_id": "markitdown-mcp" }
]
},
"application_type_schema_id": "https://mydial.epam.com/custom_application_schemas/quickapps2"
}
QuickApps version
0.11.0 (latest)
What steps will reproduce the bug?
dial-deploymenttoolset whose tool targets an image-generation deployment (gemini-2.5-flash-image), with the defaultfallback_configuration.strategies: [{"type": "continue"}]. Full app config at the bottom of this issue.3 000tokens and the user is already at4 467(visible in DIAL Chat's model info popup).generate cat image.image_generation_tool; DIAL Core rejects the downstream completion with HTTP 429.What is the expected behavior?
Two things should carry the real cause:
1. The stage should show the rate-limit reason, matching the wording the top-level orchestrator path already produces for the very same 429 through
resolve_exception()insrc/quickapp/core/application/_exception_message_resolver.py:When the upstream supplies a user-safe
display_message(DIAL Core does for quota errors), that text should win — it is the only place the actual limit numbers live.2. The model-facing fallback message should include the cause, so a
continuestrategy lets the LLM tell the user "the image model is over its daily quota" instead of silently trying something else or claiming an unspecified failure.What do you see instead?
The stage renders a constant string that names no cause and no status code:
And the LLM receives only the generic
ContinueStrategyHandler._DEFAULT_INSTRUCTIONS("An error occurs, try to call another applicable tool with the same functionality…"), with no mention of a rate limit — so it cannot explain to the user what actually happened or that retrying later would work.Net effect: a perfectly diagnosable, user-actionable quota failure is presented to the user as an unknown error.
Additional information
Root cause.
DeploymentStageWrapper._build_debug_info_from_exceptiononly special-casesaidial_client.DialException:https://github.com/epam/ai-dial-quickapps-backend/blob/development/src/quickapp/dial_deployment_tooling/deployment_stage_wrapper.py#L14-L20
But the downstream completion is issued through the openai client, not
aidial_client:DialCompletionServiceis injected withDEPLOYMENT_AZURE_CLIENT, which isAnnotated[AsyncAzureOpenAI, "DEPLOYMENT_AZURE_CLIENT"](src/quickapp/common/_di_types.py:17);await self.__azure_client.chat.completions.create(**chat_params)therefore raisesopenai.RateLimitError/openai.APIStatusErroron a 429, neveraidial_client.DialException.aidial_client.DialExceptionis only ever raised on thedial_files_toolingpath (which usesAsyncDial). So theisinstancebranch above is dead code for every DIAL-deployment tool call — every upstream failure (429, 403, 404, 5xx, content filter, context length) collapses into the same "General exception" sentence.Flow.
openai.RateLimitErrorpropagates out ofDialCompletionService.complete_request_async→translate_timeoutpasses it through (not a timeout) →StagedBaseToolcatches it atsrc/quickapp/common/staged_base_tool.py:224-231, calls_report_error_to_stage(→ the generic text above) and thenFallbackProcessor.process_fallback.Why the model is left in the dark too.
compose_tool_error_fallback_message(src/quickapp/common/tool_fallback/utils.py) forwards the error text only when the exception is aToolErrorExceptionandforward_tool_error_messageis enabled. The deployment path never wraps upstream failures inToolErrorException, so even settingforward_tool_error_message: trueon thecontinuestrategy would not surface the 429 to the LLM.Suggested fix.
isinstancecheck —resolve_exception()insrc/quickapp/core/application/_exception_message_resolver.pyalready handlesopenai.APIError, unwraps DIAL Core's nested{"error": {...}}body, honoursdisplay_message, mapscode(content_filter,context_length_exceeded) and the full status ladder including 429, and classifies retryability. It was built for the top-level path in Resolve error causes and deliver failures through the DIAL error protocol #411; the tool stages should not be re-deriving a worse version of it.DeploymentStageWrapper, gated as today byfallback_configuration.display_error_in_stage.ToolErrorException(or an equivalent) so the existingforward_tool_error_messageswitch from Expose Tool Errors to the LLM #407 applies to DIAL-deployment tools too.Same bug elsewhere.
_PyInterpreterStageWrapper._build_debug_info_from_exception(src/quickapp/internal_tooling/py_interpreter_tooling/_py_interpreter_stage_wrapper.py:14) returns the identical "General exception occurred while calling other DIAL deployment" string unconditionally — and the py-interpreter is not even a DIAL deployment call, so the message is also factually wrong there. Worth fixing in the same change.Related issues. #411 (resolver introduced for the top-level path, closed), #407 (Expose Tool Errors to the LLM), #440 (Stage content disclosure policy), #455 (standardize file-tool error flow onto
ToolErrorException).App config used to reproduce
{ "name": "pg-chat-hub", "display_name": "PG ChatHub", "display_version": "1.0.0", "description": "Agent with vision, image generation, documents RAG and web search capabilities.", "application_properties": { "orchestrator": { "deployment": { "name": "gemini-3.5-flash-google-search" }, "system_prompt": { "type": "predefined", "template": "gemini_prompt" }, "attachment_strategy": { "type": "lazy_on_demand" } }, "contexts": [], "tool_sets": [ { "name": "chat-hub", "description": "Chat Hub tool set", "type": "dial-deployment", "tools": [ { "type": "deployment-tool", "display": { "stage": { "name": "Image generation: " } }, "deployment": { "name": "gemini-2.5-flash-image" }, "content_propagation": { "propagate_history": true, "propagate_headers": [] }, "open_ai_tool": { "type": "function", "function": { "name": "image_generation_tool", "description": "Generates an image from a text description. Use this tool when the user asks to create, draw, generate, or produce an image, illustration, picture, or visual based on a description.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "A clear, detailed description of the image to generate.", "display": { "stage": { "show_value_in_stage_title": true } } } }, "required": ["query"] } } }, "fallback_configuration": { "strategies": [{ "type": "continue" }] } } ] }, { "type": "dial-mcp", "deployment_id": "markitdown-mcp" } ] }, "application_type_schema_id": "https://mydial.epam.com/custom_application_schemas/quickapps2" }