From 9e146ad0b4fde8ef341b8722277da9da52829ce9 Mon Sep 17 00:00:00 2001 From: "praisonai-triage-agent[bot]" <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 09:51:59 +0000 Subject: [PATCH 1/4] fix: resolve BudgetExceededError CLI traceback issues (fixes #1627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add clean error handling for BudgetExceededError in CLI * Wrap agent execution calls with graceful error handling * Show user-friendly error messages instead of raw tracebacks * Exit with code 1 and provide clear remediation guidance - Add max_budget parameter to Agent constructor * Accept max_budget directly as convenience parameter * Automatically create/update ExecutionConfig with budget setting * Maintain full backward compatibility * Add proper parameter documentation Both fixes follow architecture guidelines and provide intuitive UX. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: praisonai-triage-agent[bot] --- .../praisonaiagents/agent/agent.py | 31 ++++++ src/praisonai/praisonai/cli/main.py | 98 +++++++++++++------ 2 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/praisonai-agents/praisonaiagents/agent/agent.py b/src/praisonai-agents/praisonaiagents/agent/agent.py index 7fad33816..a06fba40f 100644 --- a/src/praisonai-agents/praisonaiagents/agent/agent.py +++ b/src/praisonai-agents/praisonaiagents/agent/agent.py @@ -543,6 +543,7 @@ def __init__( verification_hooks: Optional[List[Any]] = None, # Deprecated: use autonomy=AutonomyConfig(verification_hooks=[...]) output: Optional[Union[bool, str, Dict[str, Any], 'OutputConfig']] = None, execution: Optional[Union[bool, str, Dict[str, Any], 'ExecutionConfig']] = None, + max_budget: Optional[float] = None, # Budget limit in USD - convenience alias for ExecutionConfig(max_budget=...) templates: Optional[Union[Dict[str, Any], 'TemplateConfig']] = None, caching: Optional[Union[bool, str, Dict[str, Any], 'CachingConfig']] = None, hooks: Optional[Union[List[Any], Dict[str, Any], 'HooksConfig']] = None, @@ -621,6 +622,8 @@ def __init__( - Dict[str, Any]: Config overrides (e.g. {"max_iter": 10, "max_rpm": 60}) - ExecutionConfig: Custom configuration Controls: max_iter, max_rpm, max_execution_time, max_retry_limit + max_budget: Budget limit in USD (convenience alias). Creates ExecutionConfig(max_budget=value). + Use execution=ExecutionConfig(max_budget=...) for more control. templates: Template configuration. Accepts: - Dict[str, Any]: Template fields (e.g. {"system": "...", "prompt": "..."}) - TemplateConfig: Custom configuration @@ -732,6 +735,34 @@ def __init__( web = apply_config_defaults("web", web, WebConfig) if output is None: output = apply_config_defaults("output", output, OutputConfig) + + # Handle max_budget convenience parameter + if max_budget is not None: + from ..config.feature_configs import ExecutionConfig, resolve_execution + if execution is None: + execution = ExecutionConfig(max_budget=max_budget) + else: + # If execution config is already provided, merge max_budget into it + resolved_exec = resolve_execution(execution) + if resolved_exec is None: + execution = ExecutionConfig(max_budget=max_budget) + else: + # Update existing config with max_budget if not already set + if hasattr(resolved_exec, 'max_budget') and resolved_exec.max_budget is None: + resolved_exec.max_budget = max_budget + execution = resolved_exec + elif not hasattr(resolved_exec, 'max_budget'): + # Fallback for older config objects + execution = ExecutionConfig( + max_iter=getattr(resolved_exec, 'max_iter', 20), + max_rpm=getattr(resolved_exec, 'max_rpm', None), + max_execution_time=getattr(resolved_exec, 'max_execution_time', None), + max_retry_limit=getattr(resolved_exec, 'max_retry_limit', 2), + code_execution=getattr(resolved_exec, 'code_execution', False), + code_mode=getattr(resolved_exec, 'code_mode', "safe"), + max_budget=max_budget + ) + if execution is None: execution = apply_config_defaults("execution", execution, ExecutionConfig) if caching is None: diff --git a/src/praisonai/praisonai/cli/main.py b/src/praisonai/praisonai/cli/main.py index b6298e1b4..b647f1275 100644 --- a/src/praisonai/praisonai/cli/main.py +++ b/src/praisonai/praisonai/cli/main.py @@ -4063,6 +4063,45 @@ def _run_inline_workflow(self, initial_prompt): return results[-1].get("output", "") if results else "" + def _execute_agent_with_budget_handling(self, agent, method_name, *args, **kwargs): + """ + Execute an agent method with graceful BudgetExceededError handling. + + Args: + agent: The agent instance + method_name: Name of the method to call ('start' or 'chat') + *args, **kwargs: Arguments to pass to the method + + Returns: + The result of the agent method call + + Raises: + SystemExit: On BudgetExceededError with clean error message + Exception: Re-raises any other exceptions + """ + try: + method = getattr(agent, method_name) + return method(*args, **kwargs) + except Exception as e: + # Handle BudgetExceededError gracefully + try: + from praisonaiagents.errors import BudgetExceededError + is_budget_error = isinstance(e, BudgetExceededError) + except ImportError: + # Fallback to string check if import fails + is_budget_error = 'BudgetExceededError' in str(type(e).__name__) + + if is_budget_error: + from rich import print + print(f"[red]Error: Budget limit exceeded[/red]") + print(f"[red]{str(e)}[/red]") + print("[yellow]To adjust the budget, set max_budget in ExecutionConfig:[/yellow]") + print("[yellow] Agent(execution=ExecutionConfig(max_budget=1.00))[/yellow]") + sys.exit(1) + else: + # Re-raise other exceptions + raise + def _extract_cli_config_for_yaml(self): """ Extract CLI configuration that should be passed to YAML processing. @@ -4172,6 +4211,7 @@ def handle_direct_prompt(self, prompt): if PRAISONAI_AVAILABLE: from praisonaiagents import Agent as PraisonAgent + from praisonaiagents.errors import BudgetExceededError agent_config = { "name": "DirectAgent", @@ -4609,9 +4649,9 @@ def level_based_approve(function_name, arguments, risk_level): from rich.panel import Panel with Live(Panel(Spinner("dots", text="Generating..."), border_style="cyan"), refresh_per_second=10, transient=True): - result = auto_rag.chat(prompt) + result = self._execute_agent_with_budget_handling(auto_rag, 'chat', prompt) else: - result = auto_rag.chat(prompt) + result = self._execute_agent_with_budget_handling(auto_rag, 'chat', prompt) else: # Resolve display mode from CLI flags display_mode = self._resolve_display_mode() @@ -4619,16 +4659,16 @@ def level_based_approve(function_name, arguments, risk_level): if display_mode == 'silent': # -qq: No output at all, exit code only if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) elif display_mode == 'quiet': # -q: Result only, no spinners or status if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) if result is not None: output = getattr(result, 'output', None) or (str(result) if result else None) if output: @@ -4640,15 +4680,15 @@ def level_based_approve(function_name, arguments, risk_level): from praisonaiagents.output.status import enable_status_output, disable_status_output enable_status_output(show_timestamps=True, show_metrics=True) if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) disable_status_output() except ImportError: if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) elif display_mode == 'debug': # -vv: SDK TraceOutput with markdown rendering @@ -4656,15 +4696,15 @@ def level_based_approve(function_name, arguments, risk_level): from praisonaiagents.output.trace import enable_trace_output, disable_trace_output enable_trace_output(use_markdown=True) if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) disable_trace_output() except ImportError: if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) elif display_mode == 'jsonl': # --output jsonl: JSONL structured output for CI/CD @@ -4686,9 +4726,9 @@ def level_based_approve(function_name, arguments, risk_level): start_time = time.time() if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) # Emit final result reason = getattr(result, 'completion_reason', None) if hasattr(result, 'completion_reason') else 'complete' @@ -4707,9 +4747,9 @@ def level_based_approve(function_name, arguments, risk_level): import json as json_mod start_time = time.time() if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) output = result.output if hasattr(result, 'output') else str(result) envelope = { @@ -4730,15 +4770,15 @@ def level_based_approve(function_name, arguments, risk_level): flow = track_workflow() flow.start() if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) flow.stop() except ImportError: if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) elif display_mode == 'editor': # --output editor: User-friendly step-by-step format @@ -4748,9 +4788,9 @@ def level_based_approve(function_name, arguments, risk_level): # Run agent if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) # SDK callbacks (interaction, llm_content) handle display — # no explicit editor.output() needed here. @@ -4772,15 +4812,15 @@ def level_based_approve(function_name, arguments, risk_level): from praisonaiagents.output.status import enable_status_output, disable_status_output enable_status_output(show_timestamps=False, show_metrics=False) if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) disable_status_output() except ImportError: if hasattr(agent, 'start'): - result = agent.start(prompt) + result = self._execute_agent_with_budget_handling(agent, 'start', prompt) else: - result = agent.chat(prompt) + result = self._execute_agent_with_budget_handling(agent, 'chat', prompt) # ===== POST-PROCESSING WITH NEW FEATURES ===== @@ -4858,7 +4898,7 @@ def level_based_approve(function_name, arguments, risk_level): Now, {final_instruction.lower()}:""" final_agent = PraisonAgent(**final_agent_config) - result = final_agent.start(final_prompt) + result = self._execute_agent_with_budget_handling(final_agent, 'start', final_prompt) print(f"\n[bold green]✅ Final agent processing complete[/bold green]\n") # Save output if --save is enabled From f4ff710928b1619684d20862f930106da199ae2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 10:00:34 +0000 Subject: [PATCH 2/4] fix: address review feedback for budget handling and import safety Agent-Logs-Url: https://github.com/MervinPraison/PraisonAI/sessions/16ae1b3d-1c3c-4051-9465-a718a6746a90 Co-authored-by: MervinPraison <454862+MervinPraison@users.noreply.github.com> --- .../praisonaiagents/agent/agent.py | 21 ++++++++++++++++--- src/praisonai/praisonai/cli/main.py | 8 +++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/praisonai-agents/praisonaiagents/agent/agent.py b/src/praisonai-agents/praisonaiagents/agent/agent.py index a06fba40f..48fd5b269 100644 --- a/src/praisonai-agents/praisonaiagents/agent/agent.py +++ b/src/praisonai-agents/praisonaiagents/agent/agent.py @@ -623,7 +623,8 @@ def __init__( - ExecutionConfig: Custom configuration Controls: max_iter, max_rpm, max_execution_time, max_retry_limit max_budget: Budget limit in USD (convenience alias). Creates ExecutionConfig(max_budget=value). - Use execution=ExecutionConfig(max_budget=...) for more control. + If both execution.max_budget and max_budget are provided, max_budget takes precedence. + Use execution=ExecutionConfig(...) for full execution control. templates: Template configuration. Accepts: - Dict[str, Any]: Template fields (e.g. {"system": "...", "prompt": "..."}) - TemplateConfig: Custom configuration @@ -747,8 +748,22 @@ def __init__( if resolved_exec is None: execution = ExecutionConfig(max_budget=max_budget) else: - # Update existing config with max_budget if not already set - if hasattr(resolved_exec, 'max_budget') and resolved_exec.max_budget is None: + # Update existing config with max_budget + if hasattr(resolved_exec, 'max_budget'): + if ( + resolved_exec.max_budget is not None + and resolved_exec.max_budget != max_budget + ): + import warnings + warnings.warn( + ( + f"Both execution.max_budget={resolved_exec.max_budget} and " + f"max_budget={max_budget} were provided; " + "using max_budget." + ), + UserWarning, + stacklevel=3, + ) resolved_exec.max_budget = max_budget execution = resolved_exec elif not hasattr(resolved_exec, 'max_budget'): diff --git a/src/praisonai/praisonai/cli/main.py b/src/praisonai/praisonai/cli/main.py index b647f1275..e449f6d1c 100644 --- a/src/praisonai/praisonai/cli/main.py +++ b/src/praisonai/praisonai/cli/main.py @@ -4094,7 +4094,12 @@ def _execute_agent_with_budget_handling(self, agent, method_name, *args, **kwarg if is_budget_error: from rich import print print(f"[red]Error: Budget limit exceeded[/red]") - print(f"[red]{str(e)}[/red]") + used = getattr(e, "used", None) + limit = getattr(e, "limit", None) + if isinstance(used, (int, float)) and isinstance(limit, (int, float)): + print(f"[red]Spent ${used:.2f} (limit: ${limit:.2f})[/red]") + else: + print(f"[red]{str(e)}[/red]") print("[yellow]To adjust the budget, set max_budget in ExecutionConfig:[/yellow]") print("[yellow] Agent(execution=ExecutionConfig(max_budget=1.00))[/yellow]") sys.exit(1) @@ -4211,7 +4216,6 @@ def handle_direct_prompt(self, prompt): if PRAISONAI_AVAILABLE: from praisonaiagents import Agent as PraisonAgent - from praisonaiagents.errors import BudgetExceededError agent_config = { "name": "DirectAgent", From 5ccc652e7929ae34ec166aae7193b01d7d881052 Mon Sep 17 00:00:00 2001 From: "praisonai-triage-agent[bot]" <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 10:05:05 +0000 Subject: [PATCH 3/4] fix: resolve print shadowing and simplify budget error message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Import rich print as rich_print to avoid shadowing builtin - Consolidate multi-line error output into single actionable message - Update guidance to mention new max_budget convenience parameter 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Mervin Praison --- src/praisonai/praisonai/cli/main.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/praisonai/praisonai/cli/main.py b/src/praisonai/praisonai/cli/main.py index e449f6d1c..f6984bb70 100644 --- a/src/praisonai/praisonai/cli/main.py +++ b/src/praisonai/praisonai/cli/main.py @@ -4092,16 +4092,9 @@ def _execute_agent_with_budget_handling(self, agent, method_name, *args, **kwarg is_budget_error = 'BudgetExceededError' in str(type(e).__name__) if is_budget_error: - from rich import print - print(f"[red]Error: Budget limit exceeded[/red]") - used = getattr(e, "used", None) - limit = getattr(e, "limit", None) - if isinstance(used, (int, float)) and isinstance(limit, (int, float)): - print(f"[red]Spent ${used:.2f} (limit: ${limit:.2f})[/red]") - else: - print(f"[red]{str(e)}[/red]") - print("[yellow]To adjust the budget, set max_budget in ExecutionConfig:[/yellow]") - print("[yellow] Agent(execution=ExecutionConfig(max_budget=1.00))[/yellow]") + from rich import print as rich_print + # Single-line error message with actionable guidance + rich_print(f"[red]Budget limit exceeded: {e!s} - Set max_budget parameter (e.g., Agent(max_budget=1.00))[/red]") sys.exit(1) else: # Re-raise other exceptions From 3bf7ec4a5bcd6d4284bb0fa9fa43bc6a3a8f60f1 Mon Sep 17 00:00:00 2001 From: "praisonai-triage-agent[bot]" <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 08:57:59 +0000 Subject: [PATCH 4/4] fix: resolve print shadowing and simplify budget error message - Remove dangerous ImportError fallback in budget error handling - Use dataclasses.replace() instead of mutating shared ExecutionConfig - Simplify budget error to single actionable message - Address CodeRabbit, Greptile, and Copilot review feedback Co-authored-by: Mervin Praison --- .../praisonaiagents/agent/agent.py | 18 ++++++------------ src/praisonai/praisonai/cli/main.py | 9 ++------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/praisonai-agents/praisonaiagents/agent/agent.py b/src/praisonai-agents/praisonaiagents/agent/agent.py index 48fd5b269..5f93ecfaf 100644 --- a/src/praisonai-agents/praisonaiagents/agent/agent.py +++ b/src/praisonai-agents/praisonaiagents/agent/agent.py @@ -764,19 +764,13 @@ def __init__( UserWarning, stacklevel=3, ) - resolved_exec.max_budget = max_budget - execution = resolved_exec + # Use dataclasses.replace to avoid mutating shared state + import dataclasses + execution = dataclasses.replace(resolved_exec, max_budget=max_budget) elif not hasattr(resolved_exec, 'max_budget'): - # Fallback for older config objects - execution = ExecutionConfig( - max_iter=getattr(resolved_exec, 'max_iter', 20), - max_rpm=getattr(resolved_exec, 'max_rpm', None), - max_execution_time=getattr(resolved_exec, 'max_execution_time', None), - max_retry_limit=getattr(resolved_exec, 'max_retry_limit', 2), - code_execution=getattr(resolved_exec, 'code_execution', False), - code_mode=getattr(resolved_exec, 'code_mode', "safe"), - max_budget=max_budget - ) + # Fallback for older config objects + import dataclasses + execution = dataclasses.replace(resolved_exec, max_budget=max_budget) if execution is None: execution = apply_config_defaults("execution", execution, ExecutionConfig) diff --git a/src/praisonai/praisonai/cli/main.py b/src/praisonai/praisonai/cli/main.py index f6984bb70..839149b1a 100644 --- a/src/praisonai/praisonai/cli/main.py +++ b/src/praisonai/praisonai/cli/main.py @@ -4084,14 +4084,9 @@ def _execute_agent_with_budget_handling(self, agent, method_name, *args, **kwarg return method(*args, **kwargs) except Exception as e: # Handle BudgetExceededError gracefully - try: - from praisonaiagents.errors import BudgetExceededError - is_budget_error = isinstance(e, BudgetExceededError) - except ImportError: - # Fallback to string check if import fails - is_budget_error = 'BudgetExceededError' in str(type(e).__name__) + from praisonaiagents.errors import BudgetExceededError - if is_budget_error: + if isinstance(e, BudgetExceededError): from rich import print as rich_print # Single-line error message with actionable guidance rich_print(f"[red]Budget limit exceeded: {e!s} - Set max_budget parameter (e.g., Agent(max_budget=1.00))[/red]")