From 92c2b6dd643d85fd7433fe57a9bd81fe161d20b6 Mon Sep 17 00:00:00 2001 From: "praisonai-triage-agent[bot]" <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:16:49 +0000 Subject: [PATCH] fix: migrate invalid AgentTeam kwargs to output=/execution= (fixes #3654) AgentTeam.__init__ accepts neither verbose= nor **kwargs, so passing verbose= raised TypeError at construction. Verbosity is controlled via the consolidated output= param (silent/minimal/verbose) and iteration limits via execution=. Migrated all internal CLI, template generators, and examples off the removed verbose=/max_iter=/session= kwargs, and added an AST lint guard that scans examples + internal CLI for invalid AgentTeam kwargs to prevent regression. Co-authored-by: Dhivya-Bharathy --- .../python/concepts/csv-processing-agents.py | 2 +- .../concepts/simple-csv-url-processor.py | 2 +- .../python/input/advanced_dynamic_input.py | 2 +- .../comprehensive-session-management.py | 8 +- .../tests/crewai-tools-example.py | 2 +- .../unit/agents/test_agentteam_kwarg_lint.py | 107 ++++++++++++++++++ .../praisonai_code/cli/features/agents.py | 4 +- .../praisonai_code/cli/features/benchmark.py | 4 +- .../praisonai_code/cli/legacy/praison_ai.py | 4 +- .../praisonai/cli/features/templates.py | 6 +- .../cli/legacy/subcommand_handlers.py | 2 +- .../standardise/enhanced_templates.py | 2 +- 12 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/praisonai-agents/tests/unit/agents/test_agentteam_kwarg_lint.py diff --git a/examples/python/concepts/csv-processing-agents.py b/examples/python/concepts/csv-processing-agents.py index 48f61f40a..462b42ee3 100644 --- a/examples/python/concepts/csv-processing-agents.py +++ b/examples/python/concepts/csv-processing-agents.py @@ -86,7 +86,7 @@ def method_1_simple_loop(): agents=[loop_agent], tasks=[loop_task], process="workflow", - max_iter=10 # Maximum iterations to prevent infinite loops + execution="balanced" # max_iter=10 preset to prevent infinite loops ) print(f"🚀 Starting loop processing of {csv_file}") diff --git a/examples/python/concepts/simple-csv-url-processor.py b/examples/python/concepts/simple-csv-url-processor.py index f90bcda1d..135b32a1b 100644 --- a/examples/python/concepts/simple-csv-url-processor.py +++ b/examples/python/concepts/simple-csv-url-processor.py @@ -64,7 +64,7 @@ def main(): agents=[url_agent], tasks=[url_task], process="workflow", - max_iter=10 # Adjust based on how many URLs you have + execution="balanced" # max_iter=10 preset; adjust for more URLs ) print(f"🚀 Processing URLs from {csv_file}") diff --git a/examples/python/input/advanced_dynamic_input.py b/examples/python/input/advanced_dynamic_input.py index 8e2525b51..169791c5c 100644 --- a/examples/python/input/advanced_dynamic_input.py +++ b/examples/python/input/advanced_dynamic_input.py @@ -141,7 +141,7 @@ class DynamicAgentSystem: agents=agents, tasks=tasks, process=process, - verbose=inputs['depth'] == 'detailed' + output="verbose" if inputs['depth'] == 'detailed' else "silent" ) result = praison_agents.start() diff --git a/examples/python/sessions/comprehensive-session-management.py b/examples/python/sessions/comprehensive-session-management.py index ce0ff2571..6721839f1 100644 --- a/examples/python/sessions/comprehensive-session-management.py +++ b/examples/python/sessions/comprehensive-session-management.py @@ -66,7 +66,7 @@ research_task1 = Task( agents_session1 = AgentTeam( agents=[research_agent], tasks=[research_task1], - session=session1, output="verbose" + output="verbose" ) # Execute first session @@ -105,7 +105,7 @@ analysis_task = Task( agents_session2 = AgentTeam( agents=[analysis_agent], tasks=[analysis_task], - session=session2, output="verbose" + output="verbose" ) print("Starting analysis session with previous context...") @@ -155,7 +155,7 @@ synthesis_task = Task( agents_recovery = AgentTeam( agents=[synthesis_agent], tasks=[synthesis_task], - session=recovery_session, output="verbose" + output="verbose" ) print("Starting recovery session with full context from previous sessions...") @@ -205,7 +205,7 @@ review_task = Task( agents_reviewer = AgentTeam( agents=[review_agent], tasks=[review_task], - session=reviewer_session, output="verbose" + output="verbose" ) print("Starting peer review session by different user...") diff --git a/src/praisonai-agents/tests/crewai-tools-example.py b/src/praisonai-agents/tests/crewai-tools-example.py index caaa91f91..6cf24c540 100644 --- a/src/praisonai-agents/tests/crewai-tools-example.py +++ b/src/praisonai-agents/tests/crewai-tools-example.py @@ -23,5 +23,5 @@ agent = Agent( ) # Run the agent -result = AgentTeam(agents=[agent], verbose=10).start() +result = AgentTeam(agents=[agent], output="verbose").start() print(result) diff --git a/src/praisonai-agents/tests/unit/agents/test_agentteam_kwarg_lint.py b/src/praisonai-agents/tests/unit/agents/test_agentteam_kwarg_lint.py new file mode 100644 index 000000000..05833cae9 --- /dev/null +++ b/src/praisonai-agents/tests/unit/agents/test_agentteam_kwarg_lint.py @@ -0,0 +1,107 @@ +""" +AST lint guard preventing AgentTeam API drift (invalid ``verbose=`` kwarg). + +``AgentTeam.__init__`` does not accept ``verbose``; verbosity is controlled via +the consolidated ``output`` parameter (e.g. ``output="silent"`` / +``output="verbose"``). This test AST-parses example and internal-CLI source and +fails if any ``AgentTeam(...)`` call passes a keyword not present in the real +constructor signature, so broken copy-paste snippets can't be reintroduced. + +The valid kwarg set is read from the ``AgentTeam.__init__`` source via AST +(not ``inspect.signature``) because ``AgentTeam`` subclasses ``typing.Protocol``, +which makes ``inspect`` report a generic ``(*args, **kwargs)`` signature. +""" +import ast +from pathlib import Path + +# Repo root: .../src/praisonai-agents/tests/unit/agents/ +_REPO_ROOT = Path(__file__).resolve().parents[5] +_AGENTS_SRC = ( + _REPO_ROOT + / "src" + / "praisonai-agents" + / "praisonaiagents" + / "agents" + / "agents.py" +) + +# Directories whose ``AgentTeam(...)`` calls must use valid kwargs only. +_SCAN_DIRS = [ + _REPO_ROOT / "examples", + _REPO_ROOT / "src" / "praisonai" / "praisonai", + _REPO_ROOT / "src" / "praisonai-code" / "praisonai_code", +] + + +def _valid_agentteam_kwargs(): + """Extract real ``AgentTeam.__init__`` parameter names by parsing source.""" + tree = ast.parse(_AGENTS_SRC.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name == "AgentTeam": + for item in node.body: + if isinstance(item, ast.FunctionDef) and item.name == "__init__": + args = item.args + names = {a.arg for a in args.args} + names.update(a.arg for a in args.kwonlyargs) + has_var_keyword = args.kwarg is not None + names.discard("self") + return names, has_var_keyword + raise AssertionError("AgentTeam.__init__ not found in agents.py") + + +def _is_agentteam_call(node: ast.Call) -> bool: + func = node.func + if isinstance(func, ast.Name): + return func.id == "AgentTeam" + if isinstance(func, ast.Attribute): + return func.attr == "AgentTeam" + return False + + +def _iter_python_files(): + for base in _SCAN_DIRS: + if not base.exists(): + continue + for path in base.rglob("*.py"): + yield path + + +def _find_bad_kwargs(path: Path, valid_kwargs): + try: + tree = ast.parse(path.read_text(encoding="utf-8")) + except (SyntaxError, UnicodeDecodeError): + return [] + bad = [] + for node in ast.walk(tree): + if isinstance(node, ast.Call) and _is_agentteam_call(node): + for kw in node.keywords: + if kw.arg is None: + continue # **kwargs spread — can't statically validate + if kw.arg not in valid_kwargs: + bad.append((kw.arg, node.lineno)) + return bad + + +def test_real_agentteam_init_has_no_verbose_or_var_keyword(): + valid_kwargs, has_var_keyword = _valid_agentteam_kwargs() + assert "verbose" not in valid_kwargs + assert "output" in valid_kwargs + assert not has_var_keyword, ( + "AgentTeam.__init__ gained **kwargs; the lint guard below would be " + "silently bypassed. Update this test if that change is intentional." + ) + + +def test_no_invalid_agentteam_kwargs_in_examples_and_cli(): + valid_kwargs, _ = _valid_agentteam_kwargs() + + failures = [] + for path in _iter_python_files(): + for arg, lineno in _find_bad_kwargs(path, valid_kwargs): + rel = path.relative_to(_REPO_ROOT) + failures.append(f"{rel}:{lineno} — invalid AgentTeam kwarg '{arg}'") + + assert not failures, ( + "AgentTeam called with invalid kwargs (use output='silent'/'verbose' " + "instead of verbose=):\n" + "\n".join(sorted(failures)) + ) diff --git a/src/praisonai-code/praisonai_code/cli/features/agents.py b/src/praisonai-code/praisonai_code/cli/features/agents.py index 394db8ecf..f410411a9 100644 --- a/src/praisonai-code/praisonai_code/cli/features/agents.py +++ b/src/praisonai-code/praisonai_code/cli/features/agents.py @@ -219,7 +219,7 @@ class MultiAgentHandler: instructions=config.get('instructions', ''), tools=tools if tools else None, llm=config.get('llm') or llm or os.environ.get('OPENAI_MODEL_NAME', 'gpt-4o-mini'), - verbose=self.verbose + output="verbose" if self.verbose else "silent" ) agents.append(agent) @@ -243,7 +243,7 @@ class MultiAgentHandler: agents=agents, tasks=tasks, process=process, - verbose=self.verbose + output="verbose" if self.verbose else "silent" ) result = praison_agents.start() diff --git a/src/praisonai-code/praisonai_code/cli/features/benchmark.py b/src/praisonai-code/praisonai_code/cli/features/benchmark.py index 520925e79..e4073432b 100644 --- a/src/praisonai-code/praisonai_code/cli/features/benchmark.py +++ b/src/praisonai-code/praisonai_code/cli/features/benchmark.py @@ -797,7 +797,7 @@ t_import = time.perf_counter() agent = Agent(name="Helper", instructions="You are helpful", llm="{self.DEFAULT_MODEL}", output="minimal") task = Task(name="respond", description="{prompt}", expected_output="response", agent=agent) -agents = AgentTeam(agents=[agent], tasks=[task], verbose=0) +agents = AgentTeam(agents=[agent], tasks=[task], output="silent") t_init = time.perf_counter() result = agents.start() @@ -845,7 +845,7 @@ agent1 = Agent(name="Analyzer", instructions="Analyze the request", llm="{self.D agent2 = Agent(name="Responder", instructions="Provide a response", llm="{self.DEFAULT_MODEL}", output="minimal") task1 = Task(name="analyze", description="Analyze: {prompt}", expected_output="analysis", agent=agent1) task2 = Task(name="respond", description="Respond based on analysis", expected_output="response", agent=agent2) -agents = AgentTeam(agents=[agent1, agent2], tasks=[task1, task2], verbose=0) +agents = AgentTeam(agents=[agent1, agent2], tasks=[task1, task2], output="silent") t_init = time.perf_counter() result = agents.start() diff --git a/src/praisonai-code/praisonai_code/cli/legacy/praison_ai.py b/src/praisonai-code/praisonai_code/cli/legacy/praison_ai.py index 02d64caa9..a0afbb13b 100644 --- a/src/praisonai-code/praisonai_code/cli/legacy/praison_ai.py +++ b/src/praisonai-code/praisonai_code/cli/legacy/praison_ai.py @@ -2312,13 +2312,13 @@ class PraisonAI: agents=agents_list, tasks=tasks_list, process=process_type, - verbose=1 if verbose else 0 + output="minimal" if verbose else "silent" ) else: praison = AgentTeam( agents=agents_list, process=process_type, - verbose=1 if verbose else 0 + output="minimal" if verbose else "silent" ) praison.launch(port=port, host=host) diff --git a/src/praisonai/praisonai/cli/features/templates.py b/src/praisonai/praisonai/cli/features/templates.py index ac5677104..6740fcf1b 100644 --- a/src/praisonai/praisonai/cli/features/templates.py +++ b/src/praisonai/praisonai/cli/features/templates.py @@ -637,11 +637,15 @@ class TemplatesHandler: tasks.append(task) # Run + _verbose_level = workflow_config.get("verbose", 1) + _output_mode = "silent" if not _verbose_level else ( + "minimal" if _verbose_level == 1 else "verbose" + ) praison_agents = AgentTeam( agents=agents, tasks=tasks, process=workflow_config.get("process", "sequential"), - verbose=workflow_config.get("verbose", 1) + output=_output_mode ) praison_agents.start() elif "steps" in workflow_config: diff --git a/src/praisonai/praisonai/cli/legacy/subcommand_handlers.py b/src/praisonai/praisonai/cli/legacy/subcommand_handlers.py index d51dfa6e1..6bf98a245 100644 --- a/src/praisonai/praisonai/cli/legacy/subcommand_handlers.py +++ b/src/praisonai/praisonai/cli/legacy/subcommand_handlers.py @@ -1210,7 +1210,7 @@ def handle_research_command(self, query: str, model: str = None, verbose: bool = ) print("[cyan]Gathering information with tools...[/cyan]") - agents = AgentTeam(agents=[research_assistant], tasks=[gather_task], verbose=0) + agents = AgentTeam(agents=[research_assistant], tasks=[gather_task], output="silent") tool_results = agents.start() # Enhance query with tool results diff --git a/src/praisonai/praisonai/standardise/enhanced_templates.py b/src/praisonai/praisonai/standardise/enhanced_templates.py index 6799db982..e97808465 100644 --- a/src/praisonai/praisonai/standardise/enhanced_templates.py +++ b/src/praisonai/praisonai/standardise/enhanced_templates.py @@ -567,7 +567,7 @@ if __name__ == "__main__": agents = AgentTeam( agents=[primary_agent, secondary_agent], tasks=[primary_task, verification_task], - verbose=True, + output="verbose", ) # Execute