"""Step definitions for agent_configurable_limits.feature. These steps test the configurable ``max_dependencies`` parameter on ``ContextAnalysisAgent`` and the configurable ``max_context_files`` parameter on ``PlanGenerationGraph``, covering: - Default values preserve existing behaviour (10 / 5 respectively) - Custom values are respected - Non-positive values raise ``ValueError`` """ from __future__ import annotations from typing import Any from behave import given, then, when from langchain_community.llms import FakeListLLM from cleveragents.agents.graphs.context_analysis import ContextAnalysisAgent from cleveragents.agents.graphs.plan_generation import PlanGenerationGraph from cleveragents.domain.models.core import Context # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _DEFAULT_RESPONSES = [ "Dependencies: ['os', 'sys', 'pathlib']", "Relevance: 0.8 - core module", "Summary: Generated context overview", ] * 30 def _make_llm() -> FakeListLLM: return FakeListLLM(responses=list(_DEFAULT_RESPONSES)) def _make_dep_string(count: int) -> str: """Return an LLM-style dependency string with *count* items.""" items = ", ".join(f"dep_{i}" for i in range(count)) return f"[{items}]" def _make_contexts(count: int) -> list[Context]: """Return a list of *count* dummy Context objects.""" return [ Context(plan_id=1, path=f"src/file_{i}.py", content=f"content of file {i}") for i in range(count) ] # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("the configurable limits module is importable") def step_module_importable(context: Any) -> None: assert ContextAnalysisAgent is not None assert PlanGenerationGraph is not None # --------------------------------------------------------------------------- # ContextAnalysisAgent — default max_dependencies # --------------------------------------------------------------------------- @given("a ContextAnalysisAgent with default settings") def step_create_agent_default(context: Any) -> None: context.agent = ContextAnalysisAgent(llm=_make_llm()) @given("a ContextAnalysisAgent with max_dependencies set to {limit:d}") def step_create_agent_custom_max_deps(context: Any, limit: int) -> None: context.agent = ContextAnalysisAgent(llm=_make_llm(), max_dependencies=limit) @when("I parse dependencies from a string with {count:d} items") def step_parse_deps_with_count(context: Any, count: int) -> None: dep_string = _make_dep_string(count) context.parsed_deps = context.agent._parse_dependencies(dep_string) @then("the result should contain exactly {expected:d} dependencies") def step_check_dep_count(context: Any, expected: int) -> None: actual = len(context.parsed_deps) assert actual == expected, ( f"Expected {expected} dependencies, got {actual}: {context.parsed_deps!r}" ) # --------------------------------------------------------------------------- # ContextAnalysisAgent — ValueError for invalid max_dependencies # --------------------------------------------------------------------------- @when("I create a ContextAnalysisAgent with max_dependencies set to {limit:d}") def step_create_agent_invalid_max_deps(context: Any, limit: int) -> None: context.raised_error = None try: ContextAnalysisAgent(llm=_make_llm(), max_dependencies=limit) except ValueError as exc: context.raised_error = exc @then("a ValueError should be raised about max_dependencies") def step_check_value_error_max_deps(context: Any) -> None: assert context.raised_error is not None, ( "Expected ValueError but no error was raised" ) assert "max_dependencies" in str(context.raised_error), ( f"Expected 'max_dependencies' in error message, got: {context.raised_error!r}" ) # --------------------------------------------------------------------------- # PlanGenerationGraph — default max_context_files # --------------------------------------------------------------------------- @given("a PlanGenerationGraph with default settings") def step_create_graph_default(context: Any) -> None: context.graph = PlanGenerationGraph(llm=_make_llm()) @given("a PlanGenerationGraph with max_context_files set to {limit:d}") def step_create_graph_custom_max_ctx(context: Any, limit: int) -> None: context.graph = PlanGenerationGraph(llm=_make_llm(), max_context_files=limit) @when("I format a context summary with {count:d} context files") def step_format_context_summary(context: Any, count: int) -> None: contexts = _make_contexts(count) context.summary = context.graph._format_context_summary(contexts) context.context_count = count @then("the summary should contain exactly {expected:d} file entries") def step_check_file_entries(context: Any, expected: int) -> None: # Each file entry starts with "File: " file_count = context.summary.count("File: ") assert file_count == expected, ( f"Expected {expected} 'File:' entries in summary, got {file_count}.\n" f"Summary:\n{context.summary!r}" ) @then("the summary should mention {extra:d} more files") def step_check_more_files_mention(context: Any, extra: int) -> None: assert f"... and {extra} more files" in context.summary, ( f"Expected '... and {extra} more files' in summary.\n" f"Summary:\n{context.summary!r}" ) @then("the summary should not mention more files") def step_check_no_more_files(context: Any) -> None: assert "more files" not in context.summary, ( f"Expected no 'more files' mention in summary.\nSummary:\n{context.summary!r}" ) @then("the summary should be the no-context message") def step_check_no_context_message(context: Any) -> None: assert context.summary == "No context files provided", ( f"Expected 'No context files provided', got: {context.summary!r}" ) # --------------------------------------------------------------------------- # PlanGenerationGraph — ValueError for invalid max_context_files # --------------------------------------------------------------------------- @when("I create a PlanGenerationGraph with max_context_files set to {limit:d}") def step_create_graph_invalid_max_ctx(context: Any, limit: int) -> None: context.raised_error = None try: PlanGenerationGraph(llm=_make_llm(), max_context_files=limit) except ValueError as exc: context.raised_error = exc @then("a ValueError should be raised about max_context_files") def step_check_value_error_max_ctx(context: Any) -> None: assert context.raised_error is not None, ( "Expected ValueError but no error was raised" ) assert "max_context_files" in str(context.raised_error), ( f"Expected 'max_context_files' in error message, got: {context.raised_error!r}" )