forked from HAL9000/cleveragents-core
439 lines
16 KiB
Python
439 lines
16 KiB
Python
"""Step definitions for comprehensive chain agent testing."""
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
from typing import Dict
|
|
from unittest.mock import MagicMock
|
|
from unittest.mock import Mock
|
|
|
|
from behave import given
|
|
from behave import then
|
|
from behave import when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.agents.base import Agent
|
|
from cleveragents.agents.chain import ChainAgent
|
|
from cleveragents.core.exceptions import ConfigurationError
|
|
from cleveragents.templates.renderer import TemplateRenderer
|
|
|
|
|
|
@given("the chain agent test environment is initialized")
|
|
def step_init_chain_test_environment(context: Context):
|
|
"""Initialize the chain agent test environment."""
|
|
context.template_renderer = Mock(spec=TemplateRenderer)
|
|
context.configs = {}
|
|
context.agents = {}
|
|
context.errors = []
|
|
context.results = {}
|
|
context.template_store = {}
|
|
|
|
|
|
@given('I have a minimal chain agent configuration with name "{name}"')
|
|
def step_minimal_chain_config(context: Context, name: str):
|
|
"""Create a minimal chain agent configuration."""
|
|
context.configs["current"] = {"name": name, "config": {}}
|
|
|
|
|
|
@given("I have a chain agent configuration with steps:")
|
|
def step_chain_config_with_steps(context: Context):
|
|
"""Create a chain agent configuration with steps from table."""
|
|
steps = [row["step_name"] for row in context.table]
|
|
context.configs["current"] = {
|
|
"name": "test_chain_with_steps",
|
|
"config": {"steps": steps},
|
|
}
|
|
|
|
|
|
@given('I have a chain agent configuration with prompt "{prompt}"')
|
|
def step_chain_config_with_prompt(context: Context, prompt: str):
|
|
"""Create a chain agent configuration with inline prompt."""
|
|
context.configs["current"] = {
|
|
"name": "test_chain_with_prompt",
|
|
"config": {"prompt": prompt},
|
|
}
|
|
|
|
|
|
@given('I have a template store with a template "{template_name}"')
|
|
def step_create_template_in_store(context: Context, template_name: str):
|
|
"""Create a template in the template store."""
|
|
context.template_store[template_name] = "Template content for " + template_name
|
|
|
|
# Configure the mock template renderer
|
|
def get_template_side_effect(name):
|
|
if name in context.template_store:
|
|
return context.template_store[name]
|
|
raise ValueError(f"Template '{name}' not found")
|
|
|
|
context.template_renderer.get_template.side_effect = get_template_side_effect
|
|
|
|
|
|
@given('I have a template store with template "{name}" containing "{content}"')
|
|
def step_create_template_with_content(context: Context, name: str, content: str):
|
|
"""Create a template with specific content."""
|
|
context.template_store[name] = content
|
|
|
|
def get_template_side_effect(template_name):
|
|
if template_name in context.template_store:
|
|
return context.template_store[template_name]
|
|
raise ValueError(f"Template '{template_name}' not found")
|
|
|
|
context.template_renderer.get_template.side_effect = get_template_side_effect
|
|
|
|
|
|
@given('I have a chain agent configuration with prompt_reference "{ref}"')
|
|
def step_chain_config_with_prompt_ref(context: Context, ref: str):
|
|
"""Create a chain agent configuration with prompt reference."""
|
|
context.configs["current"] = {
|
|
"name": "test_chain_with_ref",
|
|
"config": {"prompt_reference": ref},
|
|
}
|
|
|
|
|
|
@given("I have a chain agent configuration with both prompt and prompt_reference")
|
|
def step_chain_config_with_both_prompts(context: Context):
|
|
"""Create a chain agent configuration with both prompt types."""
|
|
context.configs["current"] = {
|
|
"name": "test_chain_invalid",
|
|
"config": {"prompt": "Inline prompt", "prompt_reference": "some_ref"},
|
|
}
|
|
|
|
|
|
@given("I have a chain agent with no prompt and no steps")
|
|
def step_create_basic_chain_agent(context: Context):
|
|
"""Create a basic chain agent with no configuration."""
|
|
config = {}
|
|
context.agents["current"] = ChainAgent(
|
|
name="basic_chain", config=config, template_renderer=context.template_renderer
|
|
)
|
|
|
|
|
|
@given("I have a chain agent with steps {steps}")
|
|
def step_create_chain_agent_with_steps(context: Context, steps: str):
|
|
"""Create a chain agent with specified steps."""
|
|
import json
|
|
|
|
steps_list = json.loads(steps)
|
|
config = {"steps": steps_list}
|
|
context.agents["current"] = ChainAgent(
|
|
name="chain_with_steps",
|
|
config=config,
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
|
|
|
|
@given('I have a chain agent with prompt "{prompt}"')
|
|
def step_create_chain_agent_with_prompt(context: Context, prompt: str):
|
|
"""Create a chain agent with inline prompt."""
|
|
config = {"prompt": prompt}
|
|
|
|
# Mock the render_string method
|
|
def render_string_side_effect(template, ctx, source_description=""):
|
|
# Simple template rendering simulation
|
|
result = template
|
|
for key, value in ctx.items():
|
|
result = result.replace(f"{{{key}}}", str(value))
|
|
return result
|
|
|
|
context.template_renderer.render_string.side_effect = render_string_side_effect
|
|
|
|
context.agents["current"] = ChainAgent(
|
|
name="chain_with_prompt",
|
|
config=config,
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
|
|
|
|
@given('I have a chain agent with prompt "{prompt}" and steps {steps}')
|
|
def step_create_chain_agent_with_prompt_and_steps(
|
|
context: Context, prompt: str, steps: str
|
|
):
|
|
"""Create a chain agent with prompt and steps."""
|
|
import json
|
|
|
|
steps_list = json.loads(steps)
|
|
config = {"prompt": prompt, "steps": steps_list}
|
|
|
|
# Mock the render_string method
|
|
def render_string_side_effect(template, ctx, source_description=""):
|
|
result = template
|
|
for key, value in ctx.items():
|
|
result = result.replace(f"{{{key}}}", str(value))
|
|
return result
|
|
|
|
context.template_renderer.render_string.side_effect = render_string_side_effect
|
|
|
|
context.agents["current"] = ChainAgent(
|
|
name="chain_with_both",
|
|
config=config,
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
|
|
|
|
@given('I have a chain agent using prompt_reference "{ref}"')
|
|
def step_create_chain_agent_with_ref(context: Context, ref: str):
|
|
"""Create a chain agent using prompt reference."""
|
|
config = {"prompt_reference": ref}
|
|
context.agents["current"] = ChainAgent(
|
|
name="chain_with_ref",
|
|
config=config,
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
|
|
# Mock the render_string to handle the referenced template
|
|
def render_string_side_effect(template, ctx, source_description=""):
|
|
result = template
|
|
for key, value in ctx.items():
|
|
result = result.replace(f"{{{key}}}", str(value))
|
|
return result
|
|
|
|
context.template_renderer.render_string.side_effect = render_string_side_effect
|
|
|
|
|
|
@given("I have any chain agent")
|
|
def step_create_any_chain_agent(context: Context):
|
|
"""Create any chain agent for testing."""
|
|
context.agents["current"] = ChainAgent(
|
|
name="any_chain", config={}, template_renderer=context.template_renderer
|
|
)
|
|
|
|
|
|
@given("I have a chain agent instance")
|
|
def step_create_chain_instance(context: Context):
|
|
"""Create a chain agent instance for inheritance testing."""
|
|
context.agents["current"] = ChainAgent(
|
|
name="inheritance_test",
|
|
config={"steps": ["test"]},
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
|
|
|
|
@when("I create a chain agent from the configuration")
|
|
def step_create_chain_from_config(context: Context):
|
|
"""Create a chain agent from the current configuration."""
|
|
try:
|
|
config_data = context.configs["current"]
|
|
context.agents["created"] = ChainAgent(
|
|
name=config_data["name"],
|
|
config=config_data["config"],
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
except Exception as e:
|
|
context.errors.append(e)
|
|
|
|
|
|
@when("I try to create a chain agent from the configuration")
|
|
def step_try_create_chain_from_config(context: Context):
|
|
"""Try to create a chain agent, expecting possible errors."""
|
|
try:
|
|
config_data = context.configs["current"]
|
|
context.agents["created"] = ChainAgent(
|
|
name=config_data["name"],
|
|
config=config_data["config"],
|
|
template_renderer=context.template_renderer,
|
|
)
|
|
except ConfigurationError as e:
|
|
context.errors.append(e)
|
|
except Exception as e:
|
|
context.errors.append(e)
|
|
|
|
|
|
@when('I process the message "{message}"')
|
|
def step_process_message(context: Context, message: str):
|
|
"""Process a message through the current agent."""
|
|
agent = context.agents["current"]
|
|
# Run async method in sync context
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
context.results["process"] = loop.run_until_complete(
|
|
agent.process_message(message)
|
|
)
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
@when('I process the message "{message}" with context {context_json}')
|
|
def step_process_message_with_context(
|
|
context: Context, message: str, context_json: str
|
|
):
|
|
"""Process a message with additional context."""
|
|
import json
|
|
|
|
additional_context = json.loads(context_json)
|
|
context.processing_context = additional_context.copy()
|
|
|
|
agent = context.agents["current"]
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
context.results["process"] = loop.run_until_complete(
|
|
agent.process_message(message, additional_context)
|
|
)
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
@when("I get the agent capabilities")
|
|
def step_get_capabilities(context: Context):
|
|
"""Get the capabilities of the current agent."""
|
|
agent = context.agents["current"]
|
|
context.results["capabilities"] = agent.get_capabilities()
|
|
|
|
|
|
@when('I call the process method directly with "{message}"')
|
|
def step_call_process_method(context: Context, message: str):
|
|
"""Call the process method directly."""
|
|
agent = context.agents["current"]
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
context.results["process"] = loop.run_until_complete(agent.process(message))
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
@when("I check the agent inheritance")
|
|
def step_check_inheritance(context: Context):
|
|
"""Check the inheritance of the current agent."""
|
|
agent = context.agents["current"]
|
|
context.results["inheritance"] = {
|
|
"is_agent": isinstance(agent, Agent),
|
|
"has_name": hasattr(agent, "name"),
|
|
"has_config": hasattr(agent, "config"),
|
|
"has_template_renderer": hasattr(agent, "template_renderer"),
|
|
"name_value": getattr(agent, "name", None),
|
|
"config_value": getattr(agent, "config", None),
|
|
}
|
|
|
|
|
|
@then("the chain agent should be created successfully")
|
|
def step_verify_agent_created(context: Context):
|
|
"""Verify the agent was created successfully."""
|
|
assert len(context.errors) == 0, f"Unexpected errors: {context.errors}"
|
|
assert "created" in context.agents
|
|
assert isinstance(context.agents["created"], ChainAgent)
|
|
|
|
|
|
@then('the chain agent name should be "{expected_name}"')
|
|
def step_verify_agent_name(context: Context, expected_name: str):
|
|
"""Verify the agent name."""
|
|
agent = context.agents["created"]
|
|
assert agent.name == expected_name
|
|
|
|
|
|
@then("the chain agent steps should be empty")
|
|
def step_verify_empty_steps(context: Context):
|
|
"""Verify the agent has no steps."""
|
|
agent = context.agents["created"]
|
|
assert agent.steps == []
|
|
|
|
|
|
@then("the chain agent should have {count:d} steps")
|
|
def step_verify_step_count(context: Context, count: int):
|
|
"""Verify the agent step count."""
|
|
agent = context.agents["created"]
|
|
assert len(agent.steps) == count
|
|
|
|
|
|
@then('the chain agent prompt template should be "{expected}"')
|
|
def step_verify_prompt_template(context: Context, expected: str):
|
|
"""Verify the agent prompt template."""
|
|
agent = context.agents["created"]
|
|
assert agent.prompt_template == expected
|
|
|
|
|
|
@then("the chain agent should use the referenced template")
|
|
def step_verify_referenced_template(context: Context):
|
|
"""Verify the agent uses a referenced template."""
|
|
agent = context.agents["created"]
|
|
# The prompt_template should be set to the content from get_template
|
|
assert agent.prompt_template is not None
|
|
assert context.template_renderer.get_template.called
|
|
|
|
|
|
@then('a ConfigurationError should be raised with message containing "{text}"')
|
|
def step_verify_config_error(context: Context, text: str):
|
|
"""Verify a ConfigurationError was raised with specific message."""
|
|
assert len(context.errors) > 0, "Expected an error but none was raised"
|
|
error = context.errors[-1]
|
|
assert isinstance(error, ConfigurationError)
|
|
assert text in str(error)
|
|
|
|
|
|
@then('the chain result should be "{expected}"')
|
|
def step_verify_chain_result(context: Context, expected: str):
|
|
"""Verify the chain processing result."""
|
|
assert context.results["process"] == expected
|
|
|
|
|
|
@then('the chain result should contain "{expected}"')
|
|
def step_verify_chain_result_contains(context: Context, expected: str):
|
|
"""Verify the chain result contains expected text."""
|
|
assert expected in context.results["process"]
|
|
|
|
|
|
@then('the prompt should be rendered with message "{message}"')
|
|
def step_verify_prompt_rendered(context: Context, message: str):
|
|
"""Verify the prompt was rendered with the message."""
|
|
# Check that render_string was called with the message in context
|
|
assert context.template_renderer.render_string.called
|
|
call_args = context.template_renderer.render_string.call_args
|
|
assert call_args[0][1].get("message") == message
|
|
|
|
|
|
@then("the prompt should be rendered with context")
|
|
def step_verify_prompt_rendered_with_context(context: Context):
|
|
"""Verify the prompt was rendered with context."""
|
|
assert context.template_renderer.render_string.called
|
|
|
|
|
|
@then('the context message should be set to "{expected}"')
|
|
def step_verify_context_message(context: Context, expected: str):
|
|
"""Verify the context message value."""
|
|
# The message parameter should override context
|
|
if context.template_renderer.render_string.called:
|
|
call_args = context.template_renderer.render_string.call_args
|
|
if call_args and len(call_args[0]) > 1:
|
|
# The render_context should have the message set to the expected value
|
|
render_context = call_args[0][1]
|
|
assert (
|
|
render_context.get("message") == expected
|
|
), f"Expected message '{expected}', got '{render_context.get('message')}'"
|
|
else:
|
|
assert False, "render_string was called but without expected arguments"
|
|
else:
|
|
assert False, "render_string was not called"
|
|
|
|
|
|
@then("the capabilities should be {expected}")
|
|
def step_verify_capabilities(context: Context, expected: str):
|
|
"""Verify the agent capabilities."""
|
|
import json
|
|
|
|
expected_list = json.loads(expected)
|
|
assert context.results["capabilities"] == expected_list
|
|
|
|
|
|
@then("the agent should be an instance of Agent base class")
|
|
def step_verify_agent_inheritance(context: Context):
|
|
"""Verify the agent inherits from Agent."""
|
|
assert context.results["inheritance"]["is_agent"] is True
|
|
|
|
|
|
@then("the agent should have name attribute")
|
|
def step_verify_has_name_attr(context: Context):
|
|
"""Verify the agent has name attribute."""
|
|
assert context.results["inheritance"]["has_name"] is True
|
|
|
|
|
|
@then("the agent should have config attribute")
|
|
def step_verify_has_config_attr(context: Context):
|
|
"""Verify the agent has config attribute."""
|
|
assert context.results["inheritance"]["has_config"] is True
|
|
|
|
|
|
@then("the agent should have template_renderer attribute")
|
|
def step_verify_has_renderer_attr(context: Context):
|
|
"""Verify the agent has template_renderer attribute."""
|
|
assert context.results["inheritance"]["has_template_renderer"] is True
|