feat: add a subagent frame

This commit is contained in:
tcmofashi
2026-04-03 22:15:53 +08:00
parent ce580d1f8b
commit 185361f2c3
72 changed files with 13062 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
"""Integration tests for Agent class.
This module tests the Agent class with mocked providers to verify
core functionality without making real API calls.
"""
from __future__ import annotations
import pytest
from agentlite import Agent, TextPart
@pytest.mark.integration
class TestAgentInitialization:
"""Tests for Agent initialization."""
def test_agent_initialization(self, mock_provider):
"""Test basic agent creation."""
agent = Agent(provider=mock_provider)
assert agent.provider is mock_provider
assert agent.system_prompt == "You are a helpful assistant."
assert agent.max_iterations == 80
assert agent.history == []
def test_agent_with_custom_system_prompt(self, mock_provider):
"""Test agent creation with custom system prompt."""
agent = Agent(provider=mock_provider, system_prompt="You are a specialized assistant.")
assert agent.system_prompt == "You are a specialized assistant."
def test_agent_with_tools(self, mock_provider, add_tool):
"""Test agent creation with tools."""
agent = Agent(provider=mock_provider, tools=[add_tool])
assert len(agent.tools.tools) == 1
assert agent.tools.tools[0].name == "add"
def test_agent_with_custom_max_iterations(self, mock_provider):
"""Test agent with custom max_iterations."""
agent = Agent(provider=mock_provider, max_iterations=5)
assert agent.max_iterations == 5
@pytest.mark.integration
class TestAgentRun:
"""Tests for Agent.run() method."""
@pytest.mark.asyncio
async def test_agent_run_simple(self, mock_provider):
"""Test simple non-streaming run."""
mock_provider.add_text_response("Hello there!")
agent = Agent(provider=mock_provider)
response = await agent.run("Hi")
assert response == "Hello there!"
@pytest.mark.asyncio
async def test_agent_run_adds_to_history(self, mock_provider):
"""Test that run adds messages to history."""
mock_provider.add_text_response("Response!")
agent = Agent(provider=mock_provider)
await agent.run("Hello")
# History should have user message and assistant response
assert len(agent.history) == 2
assert agent.history[0].role == "user"
assert agent.history[0].extract_text() == "Hello"
assert agent.history[1].role == "assistant"
@pytest.mark.asyncio
async def test_agent_run_multiple_messages(self, mock_provider):
"""Test multiple runs accumulate history."""
mock_provider.add_text_responses("Response 1", "Response 2")
agent = Agent(provider=mock_provider)
await agent.run("Message 1")
await agent.run("Message 2")
# Should have 4 messages total
assert len(agent.history) == 4
assert agent.history[0].role == "user"
assert agent.history[1].role == "assistant"
assert agent.history[2].role == "user"
assert agent.history[3].role == "assistant"
@pytest.mark.asyncio
async def test_agent_run_tracks_calls(self, mock_provider):
"""Test that provider.generate is called during run."""
mock_provider.add_text_response("Response!")
agent = Agent(provider=mock_provider)
await agent.run("Hello")
assert len(mock_provider.calls) == 1
call = mock_provider.calls[0]
assert call["system_prompt"] == "You are a helpful assistant."
assert len(call["history"]) == 1 # User message
@pytest.mark.integration
class TestAgentGenerate:
"""Tests for Agent.generate() method."""
@pytest.mark.asyncio
async def test_agent_generate_returns_message(self, mock_provider):
"""Test that generate returns a Message."""
mock_provider.add_text_response("Generated response")
agent = Agent(provider=mock_provider)
message = await agent.generate("Hello")
assert message.role == "assistant"
assert message.extract_text() == "Generated response"
@pytest.mark.asyncio
async def test_agent_generate_without_tool_loop(self, mock_provider):
"""Test that generate doesn't do tool calling loop."""
# Add tool call response
mock_provider.add_tool_call("add", {"a": 1, "b": 2}, "3")
agent = Agent(provider=mock_provider, tools=[])
message = await agent.generate("Calculate 1+2")
# Should return the tool call without executing it
assert message.has_tool_calls()
assert len(message.tool_calls) == 1
assert message.tool_calls[0].function.name == "add"
@pytest.mark.asyncio
async def test_agent_generate_adds_to_history(self, mock_provider):
"""Test that generate adds response to history."""
mock_provider.add_text_response("Response!")
agent = Agent(provider=mock_provider)
await agent.generate("Hello")
assert len(agent.history) == 2
assert agent.history[1].role == "assistant"
@pytest.mark.integration
class TestAgentHistory:
"""Tests for Agent history management."""
@pytest.mark.asyncio
async def test_agent_history_property_returns_copy(self, mock_provider):
"""Test that history property returns a copy."""
mock_provider.add_text_response("Response!")
agent = Agent(provider=mock_provider)
await agent.run("Hello")
history = agent.history
history.clear() # Modify the copy
# Original should still have messages
assert len(agent.history) == 2
@pytest.mark.asyncio
async def test_agent_clear_history(self, mock_provider):
"""Test clearing history."""
mock_provider.add_text_response("Response!")
agent = Agent(provider=mock_provider)
await agent.run("Hello")
agent.clear_history()
assert agent.history == []
@pytest.mark.asyncio
async def test_agent_add_message(self, mock_provider):
"""Test manually adding a message."""
agent = Agent(provider=mock_provider)
from agentlite import Message
agent.add_message(Message(role="user", content="Manual message"))
assert len(agent.history) == 1
assert agent.history[0].extract_text() == "Manual message"
@pytest.mark.integration
class TestAgentWithTools:
"""Tests for Agent with tools."""
@pytest.mark.asyncio
async def test_agent_with_tools_initialization(self, mock_provider, add_tool):
"""Test agent initialization with tools."""
agent = Agent(
provider=mock_provider, tools=[add_tool], system_prompt="You have access to tools."
)
assert len(agent.tools.tools) == 1
# Run to verify tools are passed to provider
mock_provider.add_text_response("I have tools available")
await agent.run("Hello")
# Check that tools were passed to provider
assert len(mock_provider.calls) == 1
assert len(mock_provider.calls[0]["tools"]) == 1
@pytest.mark.asyncio
async def test_agent_tool_call_execution(self, mock_provider, add_tool):
"""Test that agent executes tool calls."""
# First response: tool call
mock_provider.add_tool_call("add", {"a": 1, "b": 2}, "3")
# Second response: text after tool result
mock_provider.add_text_response("The sum is 3")
agent = Agent(provider=mock_provider, tools=[add_tool])
response = await agent.run("What is 1+2?")
assert "3" in response
# Should have made 2 calls to provider
assert len(mock_provider.calls) == 2
@pytest.mark.integration
class TestAgentMaxIterations:
"""Tests for max_iterations behavior."""
@pytest.mark.asyncio
async def test_agent_respects_max_iterations(self, mock_provider, add_tool):
"""Test that agent stops after max_iterations."""
# Always return tool calls to trigger iteration limit
for _ in range(10):
mock_provider.add_tool_call("add", {"a": 1, "b": 2}, "3")
agent = Agent(provider=mock_provider, tools=[add_tool], max_iterations=3)
response = await agent.run("Calculate")
# Should stop after max_iterations
assert len(mock_provider.calls) <= 3
assert "Maximum tool call iterations reached" in response
@pytest.mark.asyncio
async def test_agent_no_iterations_for_simple_response(self, mock_provider):
"""Test that simple responses don't count as iterations."""
mock_provider.add_text_response("Simple response")
agent = Agent(provider=mock_provider, max_iterations=1)
response = await agent.run("Hello")
assert response == "Simple response"
@pytest.mark.integration
class TestAgentStreaming:
"""Tests for streaming mode."""
@pytest.mark.asyncio
async def test_agent_run_streaming(self, mock_provider):
"""Test streaming run."""
mock_provider.add_text_response("Streamed response")
agent = Agent(provider=mock_provider)
stream = await agent.run("Hello", stream=True)
# Collect stream
chunks = []
async for chunk in stream:
chunks.append(chunk)
assert len(chunks) > 0
assert "".join(chunks) == "Streamed response"
@pytest.mark.asyncio
async def test_agent_streaming_adds_to_history(self, mock_provider):
"""Test that streaming adds messages to history."""
mock_provider.add_text_response("Response")
agent = Agent(provider=mock_provider)
stream = await agent.run("Hello", stream=True)
async for _ in stream:
pass
assert len(agent.history) == 2