import asyncio import json import yaml from behave import given from behave import then from behave import when from cleveragents.agents.factory import AgentFactory from cleveragents.core.config import ConfigurationManager from cleveragents.core.exceptions import ConfigurationError from cleveragents.core.exceptions import ExecutionError from cleveragents.templates.renderer import TemplateRenderer def _setup_context_from_text(context, config_text: str): """Helper to load configuration from a string.""" context.config_manager = ConfigurationManager() config = yaml.safe_load(config_text) context.config_manager.config = config if not hasattr(context, "template_renderer"): context.template_renderer = TemplateRenderer() context.agent_factory = AgentFactory( context.config_manager.to_dict(), context.template_renderer ) @given("a TemplateRenderer is initialized") def step_init_renderer(context): context.template_renderer = TemplateRenderer() @given( "a configuration for a ToolAgent with a custom code tool that concatenates input with a fixed string" ) def step_config_concat_tool(context): _setup_context_from_text(context, context.text) @given("a configuration for a ToolAgent with a custom code tool that accesses context") def step_config_context_tool(context): _setup_context_from_text(context, context.text) @given( "a configuration for a ToolAgent with a custom code tool that does not assign to result" ) def step_config_no_result_tool(context): _setup_context_from_text(context, context.text) @given( "a configuration for a ToolAgent with a custom code tool containing invalid Python code" ) def step_config_error_tool(context): _setup_context_from_text(context, context.text) @given( "a configuration for a ToolAgent with an invalid custom code tool config '{config_json}'" ) def step_config_invalid_tool(context, config_json): config_dict = json.loads(config_json) context.config_manager = ConfigurationManager() context.config_manager.config = config_dict if not hasattr(context, "template_renderer"): context.template_renderer = TemplateRenderer() context.agent_factory = AgentFactory( context.config_manager.to_dict(), context.template_renderer ) @given('I create the ToolAgent named "{agent_name}"') @when('I create the ToolAgent named "{agent_name}"') def step_create_tool_agent(context, agent_name): context.error = None try: context.agent = context.agent_factory.create_agent(agent_name) assert context.agent is not None except Exception as e: context.error = e raise AssertionError(f"Agent creation failed unexpectedly: {e}") @when('I try to create the ToolAgent named "{agent_name}"') def step_try_create_tool_agent(context, agent_name): context.error = None try: context.agent = context.agent_factory.create_agent(agent_name) except Exception as e: context.error = e @when('I process this message "{message}" with the agent') def step_process_message(context, message): context.response = None context.error = None async def run(): try: context.response = await context.agent.process(message) except Exception as e: context.error = e asyncio.run(run()) @when('I process this message "{message}" with the agent with context') def step_process_message_with_context(context, message): context.response = None context.error = None message_context = json.loads(context.text) async def run(): try: context.response = await context.agent.process( message, context=message_context ) except Exception as e: context.error = e asyncio.run(run()) @when('I try to process the message "{message}" with the agent') def step_try_process_message(context, message): context.response = None context.error = None async def run(): try: context.response = await context.agent.process(message) except Exception as e: context.error = e asyncio.run(run()) @then('the response should be the following "{expected_response}"') def step_assert_response(context, expected_response): assert context.error is None, f"Expected no error, but got {context.error}" assert ( context.response == expected_response ), f"Expected '{expected_response}', but got '{context.response}'" @then('the response should be the following ""') def step_assert_response_empty(context): """ Step definition for asserting an empty string response. Using an explicit pattern without a parameter avoids issues where behave cannot capture an empty string argument. """ expected_response = "" assert context.error is None, f"Expected no error, but got {context.error}" assert ( context.response == expected_response ), f"Expected '{expected_response}', but got '{context.response}'" @then('an ExecutionError should be raised with the message containing "{text}"') def step_assert_execution_error(context, text): assert ( context.error is not None ), "Expected an ExecutionError but no exception was raised." assert isinstance( context.error, ExecutionError ), f"Expected ExecutionError, but got {type(context.error).__name__}" assert text in str( context.error ), f"Expected error message to contain '{text}', but it was '{str(context.error)}'" @then('a ConfigurationError should be raised with the message containing "{text}"') def step_assert_configuration_error(context, text): assert ( context.error is not None ), "Expected a ConfigurationError but no exception was raised." assert isinstance( context.error, ConfigurationError ), f"Expected ConfigurationError, but got {type(context.error).__name__}" assert text in str( context.error ), f"Expected error message to contain '{text}', but it was '{str(context.error)}'"