Files
crewAI/lib/crewai/tests/new_agent/test_cli_provider.py
Joao Moura fe7f730546 feat: add interactive agent creation and TUI for multi-agent interaction
- Introduced a new `create_agent` command for interactive agent definition.
- Added `agent_tui.py` for a conversational TUI supporting multi-agent interactions.
- Updated CLI to support agent creation and training workflows.
- Enhanced `.gitignore` to exclude demo files and configuration artifacts.
- Implemented a benchmark runner for testing agent performance against defined cases.

This commit lays the groundwork for a more interactive and user-friendly experience in managing agents within the CrewAI framework.
2026-05-13 12:28:25 -07:00

258 lines
8.4 KiB
Python

"""Tests for the CLIProvider and formatting helpers."""
from __future__ import annotations
import asyncio
import json
from pathlib import Path
import pytest
from crewai.new_agent.cli_provider import (
CLIProvider,
format_elapsed,
format_status_line,
format_tokens,
)
from crewai.new_agent.models import AgentStatus, Message
from crewai.new_agent.provider import ConversationalProvider
# ── format_tokens ────────────────────────────────────────────
class TestFormatTokens:
def test_zero(self):
assert format_tokens(0) == "0"
def test_small(self):
assert format_tokens(999) == "999"
def test_one_thousand(self):
assert format_tokens(1000) == "1.0k"
def test_thousands(self):
assert format_tokens(1234) == "1.2k"
def test_tens_of_thousands(self):
assert format_tokens(12345) == "12.3k"
def test_hundreds_of_thousands(self):
assert format_tokens(123456) == "123.5k"
def test_millions(self):
assert format_tokens(1234567) == "1.2M"
def test_large_millions(self):
assert format_tokens(12345678) == "12.3M"
def test_one(self):
assert format_tokens(1) == "1"
def test_boundary_999(self):
assert format_tokens(999) == "999"
def test_boundary_999999(self):
assert format_tokens(999999) == "1000.0k"
def test_boundary_1000000(self):
assert format_tokens(1000000) == "1.0M"
# ── format_elapsed ───────────────────────────────────────────
class TestFormatElapsed:
def test_seconds(self):
assert format_elapsed(12000) == "12s"
def test_zero(self):
assert format_elapsed(0) == "0s"
def test_one_minute(self):
assert format_elapsed(60000) == "1m 0s"
def test_minutes_and_seconds(self):
assert format_elapsed(72000) == "1m 12s"
def test_one_hour(self):
assert format_elapsed(3600000) == "1h 0m"
def test_hours_and_minutes(self):
assert format_elapsed(3723000) == "1h 2m"
def test_under_one_second(self):
assert format_elapsed(500) == "0s"
def test_59_seconds(self):
assert format_elapsed(59000) == "59s"
# ── format_status_line ───────────────────────────────────────
class TestFormatStatusLine:
def test_basic_status(self):
status = AgentStatus(state="thinking")
line = format_status_line(status)
assert line == "⠋ thinking…"
def test_with_detail(self):
status = AgentStatus(state="using_tool", detail="Searching the web")
line = format_status_line(status)
assert line == "⠋ Searching the web…"
def test_with_elapsed(self):
status = AgentStatus(state="thinking", detail="Analyzing", elapsed_ms=12000)
line = format_status_line(status)
assert line == "⠋ Analyzing… (12s)"
def test_with_tokens(self):
status = AgentStatus(
state="using_tool",
detail="Searching the web",
elapsed_ms=12000,
input_tokens=3400,
output_tokens=1200,
)
line = format_status_line(status)
assert line == "⠋ Searching the web… (12s · ↓ 3.4k tokens · ↑ 1.2k tokens)"
def test_custom_spinner_frame(self):
status = AgentStatus(state="thinking", detail="Working")
line = format_status_line(status, spinner_frame="")
assert line.startswith("⠸ Working…")
def test_only_input_tokens(self):
status = AgentStatus(
state="thinking",
detail="Reading",
elapsed_ms=5000,
input_tokens=500,
output_tokens=0,
)
line = format_status_line(status)
assert line == "⠋ Reading… (5s · ↓ 500 tokens)"
def test_only_output_tokens(self):
status = AgentStatus(
state="thinking",
detail="Writing",
elapsed_ms=0,
input_tokens=0,
output_tokens=2500,
)
line = format_status_line(status)
assert line == "⠋ Writing… (↑ 2.5k tokens)"
# ── CLIProvider protocol conformance ─────────────────────────
class TestCLIProviderProtocol:
def test_implements_protocol(self):
provider = CLIProvider(agent_name="test-agent")
assert isinstance(provider, ConversationalProvider)
def test_has_required_methods(self):
provider = CLIProvider()
assert hasattr(provider, "send_message")
assert hasattr(provider, "receive_message")
assert hasattr(provider, "send_status")
assert hasattr(provider, "get_history")
assert hasattr(provider, "save_history")
assert hasattr(provider, "reset_history")
# ── CLIProvider history persistence ──────────────────────────
class TestCLIProviderHistory:
@pytest.fixture()
def provider(self, tmp_path, monkeypatch):
"""Create a CLIProvider that stores history in a temp dir."""
monkeypatch.chdir(tmp_path)
return CLIProvider(agent_name="test-agent")
def test_get_history_empty(self, provider):
assert provider.get_history() == []
def test_save_and_load(self, provider):
messages = [
Message(role="user", content="Hello"),
Message(role="agent", content="Hi there", sender="TestAgent"),
]
provider.save_history(messages)
loaded = provider.get_history()
assert len(loaded) == 2
assert loaded[0].role == "user"
assert loaded[0].content == "Hello"
assert loaded[1].role == "agent"
assert loaded[1].content == "Hi there"
assert loaded[1].sender == "TestAgent"
def test_reset_history(self, provider, tmp_path):
messages = [Message(role="user", content="Hello")]
provider.save_history(messages)
assert len(provider.get_history()) == 1
provider.reset_history()
assert provider.get_history() == []
def test_reset_nonexistent_history(self, provider):
# Should not raise
provider.reset_history()
def test_history_creates_directories(self, provider, tmp_path):
messages = [Message(role="user", content="Hello")]
provider.save_history(messages)
db_path = tmp_path / ".crewai" / "conversations" / "test-agent.db"
assert db_path.exists()
def test_history_roundtrip_preserves_fields(self, provider):
msg = Message(
role="agent",
content="Result",
sender="Researcher",
model="gpt-4o",
input_tokens=100,
output_tokens=50,
tools_used=["search"],
)
provider.save_history([msg])
loaded = provider.get_history()
assert loaded[0].sender == "Researcher"
assert loaded[0].model == "gpt-4o"
assert loaded[0].input_tokens == 100
assert loaded[0].output_tokens == 50
assert loaded[0].tools_used == ["search"]
# ── CLIProvider send_message ─────────────────────────────────
class TestCLIProviderSendMessage:
def test_send_agent_message(self, capsys, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
provider = CLIProvider(agent_name="test")
msg = Message(role="agent", content="Hello!", sender="Researcher")
asyncio.run(provider.send_message(msg))
captured = capsys.readouterr()
assert "Researcher: Hello!" in captured.out
def test_send_system_message(self, capsys, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
provider = CLIProvider(agent_name="test")
msg = Message(role="system", content="Agent initialized")
asyncio.run(provider.send_message(msg))
captured = capsys.readouterr()
assert "[system] Agent initialized" in captured.out
def test_send_agent_message_no_sender(self, capsys, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
provider = CLIProvider(agent_name="test")
msg = Message(role="agent", content="Hi")
asyncio.run(provider.send_message(msg))
captured = capsys.readouterr()
assert "Agent: Hi" in captured.out