Files
temp/features/steps/langgraph_nodes_uncovered_lines_steps.py

243 lines
7.5 KiB
Python

from copy import deepcopy
from behave import given, then, when
from cleveragents.agents.base import Agent
from cleveragents.langgraph.nodes import Node, NodeConfig, NodeType, ToolAgent
from cleveragents.langgraph.state import GraphState
def _make_state(messages=None, metadata=None):
return GraphState(messages=messages or [], metadata=metadata or {})
class RecordingToolAgent(ToolAgent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
self.received_inputs = []
async def process_message(self, message, context=None):
self.received_inputs.append(message)
return self.response
def get_capabilities(self):
return []
class RecordingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
self.received_inputs = []
self.received_contexts = []
async def process_message(self, message, context=None):
self.received_inputs.append(message)
if context is not None:
self.received_contexts.append(deepcopy(context))
return self.response
def get_capabilities(self):
return []
class ClearingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
async def process_message(self, message, context=None):
if context is not None:
context.clear()
return self.response
def get_capabilities(self):
return []
class MutatingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
async def process_message(self, message, context=None):
if context is not None:
context["mutated"] = "yes"
return self.response
def get_capabilities(self):
return []
@given("a tool node configured")
def step_tool_node_configured(context):
config = NodeConfig(name="tool_node", type=NodeType.TOOL)
context.node = Node(config)
context.state = _make_state()
@when("I execute the tool node")
async def step_execute_tool_node(context):
context.result = await context.node.execute(context.state)
@then("it should mark tool executed")
def step_assert_tool_executed(context):
assert context.result.get("tool_executed") is True
assert context.result.get("node") == "tool_node"
@given('a tool agent node using ToolAgent with current_message "{text}"')
def step_toolagent_branch(context, text):
tool_agent = RecordingToolAgent("tool", response="ok")
config = NodeConfig(name="tool_agent", type=NodeType.AGENT, agent="tool")
context.node = Node(config, agents={"tool": tool_agent})
context.state = _make_state(
messages=[{"role": "assistant", "content": "fallback"}],
metadata={"current_message": text},
)
@when("I execute the tool agent node with ToolAgent branch")
async def step_execute_toolagent_branch(context):
context.result = await context.node.execute(context.state)
@then('the ToolAgent should receive input "{text}"')
def step_assert_toolagent_input(context, text):
agent = context.node.agents["tool"]
assert agent.received_inputs[-1] == text
@given("an agent node with only assistant history")
def step_agent_assistant_only(context):
agent = RecordingAgent("rec", response="ok")
config = NodeConfig(name="assistant_only", type=NodeType.AGENT, agent="rec")
context.node = Node(config, agents={"rec": agent})
context.state = _make_state(messages=[{"role": "assistant", "content": "assist"}])
@when("I execute the agent node for assistant fallback")
async def step_execute_agent_assistant_only(context):
context.result = await context.node.execute(context.state)
@then("the agent should receive the assistant fallback content")
def step_assert_assistant_fallback(context):
agent = context.node.agents["rec"]
assert agent.received_inputs[-1] == "assist"
@given("an agent that clears context during processing")
def step_agent_clears_context(context):
agent = ClearingAgent("clear", response="done")
config = NodeConfig(name="clear_agent", type=NodeType.AGENT, agent="clear")
context.node = Node(config, agents={"clear": agent})
context.state = _make_state(
messages=[{"role": "user", "content": "hi"}], metadata={"foo": "bar"}
)
@when("I execute the clearing agent node")
async def step_execute_clearing_agent(context):
context.result = await context.node.execute(context.state)
@then("only last_agent_node metadata should remain")
def step_assert_cleared_metadata(context):
metadata = context.result.get("metadata", {})
assert metadata == {"last_agent_node": "clear_agent"}
@given("an agent that mutates context to add metadata")
def step_agent_mutates_context(context):
agent = MutatingAgent("mut", response="done")
config = NodeConfig(name="mut_agent", type=NodeType.AGENT, agent="mut")
context.node = Node(config, agents={"mut": agent})
context.state = _make_state(messages=[{"role": "user", "content": "hello"}])
@when("I execute the mutating agent node")
async def step_execute_mutating_agent(context):
context.result = await context.node.execute(context.state)
@then("the metadata should include the mutated key")
def step_assert_mutated_metadata(context):
metadata = context.result.get("metadata", {})
assert metadata.get("mutated") == "yes"
@given('a subgraph node named "{subgraph_name}"')
def step_subgraph_node(context, subgraph_name):
config = NodeConfig(name="subgraph", type=NodeType.SUBGRAPH, subgraph=subgraph_name)
context.node = Node(config)
context.state = _make_state()
@when("I execute the subgraph node")
async def step_execute_subgraph(context):
context.result = await context.node.execute(context.state)
@then('it should report the invoked subgraph "{subgraph_name}"')
def step_assert_subgraph(context, subgraph_name):
assert context.result.get("subgraph_invoked") == subgraph_name
@given("a start node")
def step_start_node(context):
config = NodeConfig(name="start", type=NodeType.START)
context.node = Node(config)
context.state = _make_state()
@when("I execute the start node")
async def step_execute_start(context):
context.result = await context.node.execute(context.state)
@then("it should indicate graph start")
def step_assert_started(context):
assert context.result.get("started") is True
@given("an end node")
def step_end_node(context):
config = NodeConfig(name="end", type=NodeType.END)
context.node = Node(config)
context.state = _make_state()
@when("I execute the end node")
async def step_execute_end(context):
context.result = await context.node.execute(context.state)
@then("it should indicate graph completion")
def step_assert_completed(context):
assert context.result.get("completed") is True
@given("a tool node patched to return non dict")
def step_tool_node_patched(context):
config = NodeConfig(name="patched_tool", type=NodeType.TOOL)
context.node = Node(config)
async def _patched_execute_tool():
return "raw-value"
context.node._execute_tool = _patched_execute_tool # type: ignore[attr-defined]
context.state = _make_state()
@when("I execute the patched tool node")
async def step_execute_patched_tool(context):
context.result = await context.node.execute(context.state)
@then("the execute result should only include current_node")
def step_assert_current_node_only(context):
assert context.result == {"current_node": "patched_tool"}