feat: add a subagent frame
This commit is contained in:
0
agentlite/tests/scenarios/__init__.py
Normal file
0
agentlite/tests/scenarios/__init__.py
Normal file
141
agentlite/tests/scenarios/test_cli_debug.py
Normal file
141
agentlite/tests/scenarios/test_cli_debug.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""Debug script to find CLI test hang cause."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import signal
|
||||
|
||||
sys.path.insert(0, "/home/tcmofashi/proj/l2d_backend/agentlite/src")
|
||||
|
||||
from agentlite import Agent, OpenAIProvider
|
||||
from agentlite.tools.shell.shell import Shell, Params
|
||||
|
||||
SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
|
||||
SILICONFLOW_MODEL = "Qwen/Qwen3.5-397B-A17B"
|
||||
|
||||
|
||||
async def test_shell_directly():
|
||||
"""Test shell tool without agent."""
|
||||
print("\n=== Test 1: Shell tool directly ===")
|
||||
shell = Shell(timeout=10)
|
||||
|
||||
# Use Params dataclass
|
||||
result = await shell(Params(command="echo 'Hello'", timeout=5))
|
||||
print(f"Result: {result}")
|
||||
print(f"Output: {result.output if hasattr(result, 'output') else result}")
|
||||
return True
|
||||
|
||||
|
||||
async def test_agent_no_tools():
|
||||
"""Test agent without tools."""
|
||||
print("\n=== Test 2: Agent without tools ===")
|
||||
api_key = os.environ.get("SILICONFLOW_API_KEY")
|
||||
if not api_key:
|
||||
print("SILICONFLOW_API_KEY not set")
|
||||
return False
|
||||
|
||||
provider = OpenAIProvider(
|
||||
api_key=api_key,
|
||||
base_url=SILICONFLOW_BASE_URL,
|
||||
model=SILICONFLOW_MODEL,
|
||||
timeout=30.0,
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=provider,
|
||||
system_prompt="Reply briefly in one word.",
|
||||
max_iterations=3,
|
||||
)
|
||||
|
||||
print("Sending message to LLM...")
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
agent.run("Say hello."),
|
||||
timeout=60.0,
|
||||
)
|
||||
print(f"Response: {response[:100]}...")
|
||||
return True
|
||||
except asyncio.TimeoutError:
|
||||
print("TIMEOUT in agent without tools!")
|
||||
return False
|
||||
|
||||
|
||||
async def test_agent_with_shell():
|
||||
"""Test agent with shell tool - the problematic case."""
|
||||
print("\n=== Test 3: Agent WITH shell tool ===")
|
||||
api_key = os.environ.get("SILICONFLOW_API_KEY")
|
||||
if not api_key:
|
||||
print("SILICONFLOW_API_KEY not set")
|
||||
return False
|
||||
|
||||
provider = OpenAIProvider(
|
||||
api_key=api_key,
|
||||
base_url=SILICONFLOW_BASE_URL,
|
||||
model=SILICONFLOW_MODEL,
|
||||
timeout=60.0,
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=provider,
|
||||
system_prompt="You are a shell assistant. Execute commands when asked. Keep responses brief.",
|
||||
tools=[Shell(timeout=10)],
|
||||
max_iterations=5, # Limit iterations
|
||||
)
|
||||
|
||||
print("Sending message with tool request...")
|
||||
print("This is where it might hang...")
|
||||
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
agent.run("Run 'echo test' and tell me the result."),
|
||||
timeout=120.0,
|
||||
)
|
||||
print(f"Response: {response}")
|
||||
return True
|
||||
except asyncio.TimeoutError:
|
||||
print("TIMEOUT! Agent hung for 120 seconds")
|
||||
|
||||
# Check history to see what happened
|
||||
print(f"\nHistory length: {len(agent.history)}")
|
||||
for i, msg in enumerate(agent.history[-5:]):
|
||||
content_preview = str(msg.content)[:100] if msg.content else "None"
|
||||
print(f" [{i}] {msg.role}: {content_preview}...")
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all tests."""
|
||||
print("=" * 60)
|
||||
print("CLI Debug Test - Finding the hang cause")
|
||||
print("=" * 60)
|
||||
|
||||
results = []
|
||||
|
||||
# Test 1: Shell directly
|
||||
r1 = await test_shell_directly()
|
||||
results.append(("Shell directly", r1))
|
||||
print(f"Result: {'PASS' if r1 else 'FAIL'}")
|
||||
|
||||
# Test 2: Agent without tools
|
||||
r2 = await test_agent_no_tools()
|
||||
results.append(("Agent no tools", r2))
|
||||
print(f"Result: {'PASS' if r2 else 'FAIL'}")
|
||||
|
||||
# Test 3: Agent with shell (the problem)
|
||||
r3 = await test_agent_with_shell()
|
||||
results.append(("Agent with shell", r3))
|
||||
print(f"Result: {'PASS' if r3 else 'FAIL'}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
for name, passed in results:
|
||||
status = "✅ PASS" if passed else "❌ FAIL"
|
||||
print(f" {name}: {status}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
221
agentlite/tests/scenarios/test_cli_debug_verbose.py
Normal file
221
agentlite/tests/scenarios/test_cli_debug_verbose.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""Debug script with detailed logging to find CLI test hang cause."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
sys.path.insert(0, "/home/tcmofashi/proj/l2d_backend/agentlite/src")
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
logger = logging.getLogger("debug")
|
||||
|
||||
# SiliconFlow DeepSeek-V3 (known good function calling support)
|
||||
SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
|
||||
SILICONFLOW_MODEL = "Pro/deepseek-ai/DeepSeek-V3.2"
|
||||
SILICONFLOW_API_KEY = "sk-eaxfgkkcuatochftxpevkyvltghigsrclzjzalybmaqycual"
|
||||
|
||||
|
||||
async def main():
|
||||
from agentlite import Agent, OpenAIProvider
|
||||
from agentlite.tools.shell.shell import Shell
|
||||
from agentlite.message import Message
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("CLI Debug Test with DeepSeek-V3 (SiliconFlow)")
|
||||
logger.info("=" * 60)
|
||||
|
||||
api_key = os.environ.get("SILICONFLOW_API_KEY") or SILICONFLOW_API_KEY
|
||||
if not api_key:
|
||||
logger.error("SILICONFLOW_API_KEY not set")
|
||||
return
|
||||
|
||||
logger.info(f"Using model: {SILICONFLOW_MODEL}")
|
||||
|
||||
provider = OpenAIProvider(
|
||||
api_key=api_key,
|
||||
base_url=SILICONFLOW_BASE_URL,
|
||||
model=SILICONFLOW_MODEL,
|
||||
timeout=30.0,
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=provider,
|
||||
system_prompt="You are a shell assistant. Execute commands when asked. Reply briefly.",
|
||||
tools=[Shell(timeout=10)],
|
||||
max_iterations=5,
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
message = "Run 'echo test' and tell me the result."
|
||||
|
||||
logger.info(f"\n=== Starting Agent Run ===")
|
||||
logger.info(f"Message: {message}")
|
||||
logger.info(f"Max iterations: {agent.max_iterations}")
|
||||
logger.info(f"Tools: {[t.name for t in agent.tools.tools]}")
|
||||
|
||||
agent._history.append(Message(role="user", content=message))
|
||||
|
||||
iterations = 0
|
||||
final_response = None
|
||||
|
||||
while iterations < agent.max_iterations:
|
||||
iterations += 1
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
logger.info(f"\n{'=' * 50}")
|
||||
logger.info(f"ITERATION {iterations}/{agent.max_iterations} (elapsed: {elapsed:.1f}s)")
|
||||
logger.info(f"{'=' * 50}")
|
||||
|
||||
# Step 1: Call Provider
|
||||
logger.info(">>> Step 1: Calling provider.generate()...")
|
||||
step_start = time.time()
|
||||
|
||||
try:
|
||||
stream = await asyncio.wait_for(
|
||||
provider.generate(
|
||||
system_prompt=agent.system_prompt,
|
||||
tools=agent.tools.tools,
|
||||
history=agent._history,
|
||||
),
|
||||
timeout=60.0,
|
||||
)
|
||||
logger.info(f"<<< Provider returned stream in {time.time() - step_start:.2f}s")
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("!!! Provider call TIMEOUT after 60s")
|
||||
final_response = "ERROR: Provider timeout"
|
||||
break
|
||||
|
||||
# Step 2: Collect stream parts
|
||||
logger.info(">>> Step 2: Collecting stream parts...")
|
||||
step_start = time.time()
|
||||
|
||||
from agentlite.message import TextPart, ToolCall, ContentPart
|
||||
|
||||
response_parts = []
|
||||
tool_calls = []
|
||||
chunk_count = 0
|
||||
|
||||
try:
|
||||
async for part in stream:
|
||||
chunk_count += 1
|
||||
if chunk_count % 10 == 0:
|
||||
logger.debug(f" Received chunk #{chunk_count}")
|
||||
|
||||
if isinstance(part, ToolCall):
|
||||
tool_calls.append(part)
|
||||
logger.info(
|
||||
f" ToolCall received: {part.function.name if hasattr(part, 'function') else part}"
|
||||
)
|
||||
elif isinstance(part, ContentPart):
|
||||
response_parts.append(part)
|
||||
if isinstance(part, TextPart):
|
||||
logger.debug(f" Text: {part.text[:50]}...")
|
||||
|
||||
logger.info(
|
||||
f"<<< Stream finished in {time.time() - step_start:.2f}s, {chunk_count} chunks"
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("!!! Stream reading TIMEOUT")
|
||||
final_response = "ERROR: Stream timeout"
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"!!! Stream error: {type(e).__name__}: {e}")
|
||||
final_response = f"ERROR: Stream error - {e}"
|
||||
break
|
||||
|
||||
# Extract text
|
||||
response_text = ""
|
||||
for part in response_parts:
|
||||
if isinstance(part, TextPart):
|
||||
response_text += part.text
|
||||
logger.info(f"Response text ({len(response_text)} chars): {response_text[:100]}...")
|
||||
logger.info(f"Tool calls: {len(tool_calls)}")
|
||||
|
||||
# Add to history
|
||||
agent._history.append(
|
||||
Message(
|
||||
role="assistant",
|
||||
content=response_parts,
|
||||
tool_calls=tool_calls if tool_calls else None,
|
||||
)
|
||||
)
|
||||
|
||||
# Step 3: Check if done
|
||||
if not tool_calls:
|
||||
elapsed = time.time() - start_time
|
||||
logger.info(f"\n=== Agent completed in {elapsed:.2f}s, {iterations} iterations ===")
|
||||
final_response = response_text
|
||||
break
|
||||
|
||||
# Step 4: Execute tool calls
|
||||
logger.info(f"\n>>> Step 3: Executing {len(tool_calls)} tool calls...")
|
||||
step_start = time.time()
|
||||
|
||||
for i, tc in enumerate(tool_calls):
|
||||
func_name = tc.function.name if hasattr(tc, "function") else str(tc)
|
||||
func_args = tc.function.arguments if hasattr(tc, "function") else ""
|
||||
logger.info(f" Tool #{i + 1}: {func_name}")
|
||||
logger.info(f" Args: {func_args[:200]}...")
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
agent.tools.handle(tc),
|
||||
timeout=30.0,
|
||||
)
|
||||
output = result.output if hasattr(result, "output") else str(result)
|
||||
is_error = result.is_error if hasattr(result, "is_error") else False
|
||||
logger.info(
|
||||
f" Result: is_error={is_error}, output_len={len(output) if output else 0}"
|
||||
)
|
||||
output_preview = output[:100] if output else "None"
|
||||
logger.info(f" Output preview: {output_preview}...")
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f" !!! Tool execution TIMEOUT")
|
||||
output = "Tool execution timed out"
|
||||
is_error = True
|
||||
except Exception as e:
|
||||
logger.error(f" !!! Tool error: {type(e).__name__}: {e}")
|
||||
output = str(e)
|
||||
is_error = True
|
||||
|
||||
# Add tool result to history
|
||||
agent._history.append(
|
||||
Message(
|
||||
role="tool",
|
||||
content=output,
|
||||
tool_call_id=tc.id if hasattr(tc, "id") else f"tc_{i}",
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(f"<<< Tool execution finished in {time.time() - step_start:.2f}s")
|
||||
|
||||
# Check overall timeout
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed > 90:
|
||||
logger.warning(f"!!! Overall timeout approaching ({elapsed:.1f}s)")
|
||||
final_response = f"Timeout after {iterations} iterations"
|
||||
break
|
||||
|
||||
if iterations >= agent.max_iterations:
|
||||
logger.warning(f"!!! Max iterations reached ({agent.max_iterations})")
|
||||
final_response = f"Max iterations ({agent.max_iterations}) reached"
|
||||
|
||||
logger.info(f"\n{'=' * 60}")
|
||||
logger.info(f"FINAL RESULT:")
|
||||
logger.info(f"{'=' * 60}")
|
||||
logger.info(f"{final_response}")
|
||||
logger.info(f"Total iterations: {iterations}")
|
||||
logger.info(f"Total time: {time.time() - start_time:.2f}s")
|
||||
logger.info(f"History length: {len(agent._history)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
349
agentlite/tests/scenarios/test_cli_operations_real_api.py
Normal file
349
agentlite/tests/scenarios/test_cli_operations_real_api.py
Normal file
@@ -0,0 +1,349 @@
|
||||
"""End-to-end test for complex CLI operations with real API.
|
||||
|
||||
This test simulates a realistic complex CLI task where an agent:
|
||||
1. Explores project structure using shell commands
|
||||
2. Searches for specific patterns using grep/glob
|
||||
3. Reads relevant files
|
||||
4. Creates analysis reports
|
||||
|
||||
Uses real SiliconFlow qwen3.5-397B API (requires SILICONFLOW_API_KEY env var).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from agentlite import Agent, OpenAIProvider
|
||||
from agentlite.tools import (
|
||||
ConfigurableToolset,
|
||||
ToolSuiteConfig,
|
||||
Shell,
|
||||
ReadFile,
|
||||
WriteFile,
|
||||
Glob,
|
||||
Grep,
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# Configuration from model_config.toml
|
||||
# =============================================================================
|
||||
|
||||
SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
|
||||
SILICONFLOW_MODEL = "Qwen/Qwen3.5-397B-A17B"
|
||||
|
||||
|
||||
def get_siliconflow_provider() -> OpenAIProvider | None:
|
||||
"""Create OpenAIProvider for SiliconFlow API."""
|
||||
api_key = os.environ.get("SILICONFLOW_API_KEY")
|
||||
if not api_key:
|
||||
return None
|
||||
return OpenAIProvider(
|
||||
api_key=api_key,
|
||||
base_url=SILICONFLOW_BASE_URL,
|
||||
model=SILICONFLOW_MODEL,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_provider():
|
||||
"""Create real SiliconFlow provider."""
|
||||
provider = get_siliconflow_provider()
|
||||
if provider is None:
|
||||
pytest.skip("SILICONFLOW_API_KEY not set")
|
||||
return provider
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_project():
|
||||
"""Create a mock project structure for testing."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
project_dir = Path(tmpdir) / "test_project"
|
||||
project_dir.mkdir()
|
||||
|
||||
# Create project structure
|
||||
(project_dir / "src").mkdir()
|
||||
(project_dir / "src" / "utils").mkdir()
|
||||
(project_dir / "tests").mkdir()
|
||||
(project_dir / "docs").mkdir()
|
||||
|
||||
# Create source files
|
||||
(project_dir / "src" / "main.py").write_text('''"""Main module."""
|
||||
from src.utils.helper import process_data
|
||||
from src.utils.logger import setup_logger
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
logger = setup_logger()
|
||||
data = [1, 2, 3, 4, 5]
|
||||
result = process_data(data)
|
||||
logger.info(f"Result: {result}")
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
''')
|
||||
|
||||
(project_dir / "src" / "__init__.py").write_text('"""Source package."""')
|
||||
|
||||
(project_dir / "src" / "utils" / "helper.py").write_text('''"""Helper utilities."""
|
||||
def process_data(data: list) -> list:
|
||||
"""Process input data."""
|
||||
return [x * 2 for x in data]
|
||||
|
||||
def validate_data(data: list) -> bool:
|
||||
"""Validate data format."""
|
||||
return all(isinstance(x, (int, float)) for x in data)
|
||||
''')
|
||||
|
||||
(project_dir / "src" / "utils" / "logger.py").write_text('''"""Logging utilities."""
|
||||
import logging
|
||||
|
||||
def setup_logger(name: str = "app") -> logging.Logger:
|
||||
"""Setup application logger."""
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.INFO)
|
||||
return logger
|
||||
''')
|
||||
|
||||
(project_dir / "src" / "utils" / "__init__.py").write_text('"""Utils package."""')
|
||||
|
||||
# Create test files
|
||||
(project_dir / "tests" / "test_helper.py").write_text('''"""Tests for helper module."""
|
||||
from src.utils.helper import process_data, validate_data
|
||||
|
||||
def test_process_data():
|
||||
assert process_data([1, 2, 3]) == [2, 4, 6]
|
||||
|
||||
def test_validate_data():
|
||||
assert validate_data([1, 2, 3]) == True
|
||||
assert validate_data(["a", "b"]) == False
|
||||
''')
|
||||
|
||||
# Create documentation
|
||||
(project_dir / "docs" / "README.md").write_text("""# Test Project
|
||||
|
||||
A sample project for testing CLI operations.
|
||||
|
||||
## Structure
|
||||
|
||||
- `src/` - Source code
|
||||
- `tests/` - Unit tests
|
||||
- `docs/` - Documentation
|
||||
""")
|
||||
|
||||
(project_dir / "README.md").write_text("""# Test Project
|
||||
|
||||
Simple data processing project.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python -m src.main
|
||||
```
|
||||
""")
|
||||
|
||||
yield project_dir
|
||||
|
||||
|
||||
@pytest.mark.scenario
|
||||
@pytest.mark.slow
|
||||
class TestComplexCLITasks:
|
||||
"""End-to-end tests with complex CLI operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_explore_project_structure(self, real_provider, test_project):
|
||||
"""Test exploring project structure using CLI tools.
|
||||
|
||||
Task: Use shell commands to explore the project structure,
|
||||
then summarize what files exist.
|
||||
"""
|
||||
# Create toolset with Shell tool
|
||||
toolset = ConfigurableToolset(
|
||||
config=ToolSuiteConfig(
|
||||
shell_tools=ToolSuiteConfig().shell_tools,
|
||||
),
|
||||
work_dir=str(test_project),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=toolset.tools,
|
||||
system_prompt=(
|
||||
"你是一个项目分析助手。使用 Shell 工具执行命令来探索项目结构。"
|
||||
"请使用 find、ls、tree 等命令来了解项目。"
|
||||
),
|
||||
max_iterations=5, # Limit iterations to prevent hanging
|
||||
)
|
||||
|
||||
# Add overall timeout to prevent infinite hanging
|
||||
try:
|
||||
response = await asyncio.wait_for(
|
||||
agent.run(
|
||||
f"探索项目目录 {test_project} 的结构,列出所有文件和目录,并总结项目的组织方式。"
|
||||
),
|
||||
timeout=120.0, # 2 minute overall timeout
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Agent timed out after 120 seconds - possible infinite loop")
|
||||
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[项目结构探索结果]:\n{response}\n")
|
||||
|
||||
# Verify response mentions key files
|
||||
response_lower = response.lower()
|
||||
assert any(
|
||||
word in response_lower for word in ["src", "tests", "main.py", "helper", "logger"]
|
||||
), "Response should mention project files"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_and_analyze_code(self, real_provider, test_project):
|
||||
"""Test searching for patterns and analyzing code.
|
||||
|
||||
Task: Use grep/glob to find specific patterns,
|
||||
read the files, and create an analysis report.
|
||||
"""
|
||||
# Create toolset with all file tools
|
||||
toolset = ConfigurableToolset(
|
||||
config=ToolSuiteConfig(
|
||||
file_tools=ToolSuiteConfig().file_tools,
|
||||
shell_tools=ToolSuiteConfig().shell_tools,
|
||||
),
|
||||
work_dir=str(test_project),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=toolset.tools,
|
||||
system_prompt=(
|
||||
"你是一个代码分析助手。使用 Glob、Grep、ReadFile 等工具来搜索和分析代码。"
|
||||
"请使用 Shell 工具执行 grep、find 等命令。"
|
||||
),
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
f"在项目 {test_project} 中搜索所有包含 'def ' 的 Python 文件,"
|
||||
f"列出找到的函数定义,并创建一个函数清单文件保存到 {test_project}/functions.txt。"
|
||||
)
|
||||
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[代码搜索分析结果]:\n{response}\n")
|
||||
|
||||
# Check if analysis file was created
|
||||
functions_file = test_project / "functions.txt"
|
||||
if functions_file.exists():
|
||||
content = functions_file.read_text()
|
||||
print(f"\n[函数清单文件]:\n{content}\n")
|
||||
assert len(content) > 0, "Functions file should not be empty"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_complex_multi_step_task(self, real_provider, test_project):
|
||||
"""Test a complex multi-step CLI task.
|
||||
|
||||
Task:
|
||||
1. Find all Python files using shell
|
||||
2. Search for TODO comments using grep
|
||||
3. Read files with TODOs
|
||||
4. Create a summary report
|
||||
"""
|
||||
# Add some TODO comments
|
||||
todo_file = test_project / "src" / "utils" / "todo_items.py"
|
||||
todo_file.write_text('''"""Module with TODO items."""
|
||||
|
||||
# TODO: Implement error handling
|
||||
def risky_operation(data):
|
||||
"""Perform a risky operation."""
|
||||
return data / 0 # This will fail
|
||||
|
||||
# TODO: Add caching mechanism
|
||||
def expensive_computation(n):
|
||||
"""Perform expensive computation."""
|
||||
return sum(range(n))
|
||||
|
||||
# FIXME: Memory leak in this function
|
||||
def process_large_file(path):
|
||||
"""Process a large file."""
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
''')
|
||||
|
||||
# Create comprehensive toolset
|
||||
toolset = ConfigurableToolset(
|
||||
config=ToolSuiteConfig(
|
||||
file_tools=ToolSuiteConfig().file_tools,
|
||||
shell_tools=ToolSuiteConfig().shell_tools,
|
||||
),
|
||||
work_dir=str(test_project),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=toolset.tools,
|
||||
system_prompt=(
|
||||
"你是一个项目维护助手。"
|
||||
"使用 Shell 工具执行命令(如 find、grep、ls 等)。"
|
||||
"使用 ReadFile 读取文件内容。"
|
||||
"使用 WriteFile 创建新文件。"
|
||||
"请一步一步完成任务。"
|
||||
),
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
f"请完成以下任务:\n"
|
||||
f"1. 使用 'find' 命令找出项目 {test_project} 中所有的 .py 文件\n"
|
||||
f"2. 使用 'grep' 命令搜索所有包含 'TODO' 或 'FIXME' 的行\n"
|
||||
f"3. 读取包含 TODO 的文件内容\n"
|
||||
f"4. 创建一个 TODO 报告文件,保存到 {test_project}/todo_report.txt"
|
||||
)
|
||||
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[复杂任务结果]:\n{response}\n")
|
||||
|
||||
# Verify report was created
|
||||
report_file = test_project / "todo_report.txt"
|
||||
if report_file.exists():
|
||||
content = report_file.read_text()
|
||||
print(f"\n[TODO 报告]:\n{content}\n")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_pipes_and_chains(self, real_provider, test_project):
|
||||
"""Test complex shell commands with pipes and chains.
|
||||
|
||||
Task: Use shell pipes to perform complex data processing.
|
||||
"""
|
||||
toolset = ConfigurableToolset(
|
||||
config=ToolSuiteConfig(
|
||||
shell_tools=ToolSuiteConfig().shell_tools,
|
||||
),
|
||||
work_dir=str(test_project),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=toolset.tools,
|
||||
system_prompt=(
|
||||
"你是一个 Shell 命令专家。"
|
||||
"使用复杂的 Shell 命令(管道、重定向、条件执行等)来完成任务。"
|
||||
),
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
f"在项目目录 {test_project} 中执行以下操作:\n"
|
||||
f"1. 使用 'find . -name \"*.py\" | xargs wc -l' 统计所有 Python 文件的总行数\n"
|
||||
f'2. 使用 \'grep -r "def " --include="*.py" | wc -l\' 统计函数定义数量\n'
|
||||
f"3. 使用 'ls -la' 查看目录详情\n"
|
||||
f"报告你的发现。"
|
||||
)
|
||||
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[Shell 管道命令结果]:\n{response}\n")
|
||||
|
||||
# Verify response contains relevant information
|
||||
response_lower = response.lower()
|
||||
assert any(
|
||||
word in response_lower for word in ["行", "line", "函数", "function", "文件", "file"]
|
||||
), "Response should mention analysis results"
|
||||
374
agentlite/tests/scenarios/test_file_operations.py
Normal file
374
agentlite/tests/scenarios/test_file_operations.py
Normal file
@@ -0,0 +1,374 @@
|
||||
"""End-to-end scenario test for file operations.
|
||||
|
||||
This test simulates a realistic scenario where an agent:
|
||||
1. Reads a file
|
||||
2. Explains its content
|
||||
3. Creates a new file with analysis results
|
||||
|
||||
This is a meaningful e2e test that demonstrates the agent's ability to
|
||||
orchestrate multiple tool calls in sequence.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from agentlite import Agent, TextPart, tool
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# File Operation Tools
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@tool()
|
||||
async def read_file(file_path: str) -> str:
|
||||
"""Read the content of a file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to read.
|
||||
|
||||
Returns:
|
||||
The content of the file as a string.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the file does not exist.
|
||||
"""
|
||||
with open(file_path) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@tool()
|
||||
async def write_file(file_path: str, content: str) -> str:
|
||||
"""Write content to a file, creating it if it doesn't exist.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to write.
|
||||
content: Content to write to the file.
|
||||
|
||||
Returns:
|
||||
Success message confirming the file was written.
|
||||
"""
|
||||
# Create parent directories if they don't exist
|
||||
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return f"File successfully written to {file_path}"
|
||||
|
||||
|
||||
@tool()
|
||||
async def list_files(directory: str) -> str:
|
||||
"""List all files in a directory.
|
||||
|
||||
Args:
|
||||
directory: Path to the directory to list.
|
||||
|
||||
Returns:
|
||||
A newline-separated list of file names in the directory.
|
||||
"""
|
||||
files = os.listdir(directory)
|
||||
return "\n".join(files)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# E2E Test
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.scenario
|
||||
class TestFileOperationsScenario:
|
||||
"""End-to-end test for file read/write operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_explain_and_write(self, mock_provider):
|
||||
"""Test a complete workflow: read file -> explain -> write results."""
|
||||
# Setup: Create a temporary file with content
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create a source file to read
|
||||
source_file = os.path.join(tmpdir, "source.txt")
|
||||
source_content = """Project Overview
|
||||
================
|
||||
|
||||
This is a sample project document for testing.
|
||||
|
||||
Features:
|
||||
- Feature A: Does something useful
|
||||
- Feature B: Does something else
|
||||
- Feature C: The most important feature
|
||||
|
||||
Conclusion: This project demonstrates file operations.
|
||||
"""
|
||||
with open(source_file, "w") as f:
|
||||
f.write(source_content)
|
||||
|
||||
# Configure mock provider responses
|
||||
# The agent should:
|
||||
# 1. Read the file
|
||||
# 2. Summarize it
|
||||
# 3. Write the summary to a new file
|
||||
mock_provider.add_text_response(
|
||||
f"I'll read the file at {source_file} and analyze it for you."
|
||||
)
|
||||
|
||||
# Create agent with file tools
|
||||
tools = [read_file, write_file, list_files]
|
||||
agent = Agent(
|
||||
provider=mock_provider,
|
||||
tools=tools,
|
||||
system_prompt="You are a helpful file analysis assistant.",
|
||||
)
|
||||
|
||||
# Step 1: Agent reads and analyzes the file
|
||||
mock_provider.clear_responses()
|
||||
mock_provider.add_tool_call(
|
||||
"read_file",
|
||||
{"file_path": source_file},
|
||||
source_content,
|
||||
)
|
||||
|
||||
# Agent analyzes the content
|
||||
mock_provider.add_text_response(
|
||||
"I've read the file. It's a project overview document with 3 features. "
|
||||
"Let me create a summary file."
|
||||
)
|
||||
|
||||
# Step 2: Agent writes summary to a new file
|
||||
summary_file = os.path.join(tmpdir, "summary.txt")
|
||||
expected_summary = """Project Summary
|
||||
================
|
||||
|
||||
This is a sample project with 3 main features:
|
||||
- Feature A, - Feature B, - Feature C
|
||||
|
||||
The most important feature is Feature C.
|
||||
"""
|
||||
|
||||
mock_provider.clear_responses()
|
||||
mock_provider.add_tool_call(
|
||||
"write_file",
|
||||
{
|
||||
"file_path": summary_file,
|
||||
"content": expected_summary,
|
||||
},
|
||||
f"File successfully written to {summary_file}",
|
||||
)
|
||||
mock_provider.add_text_response(f"I've created a summary at {summary_file}")
|
||||
|
||||
# Execute the agent
|
||||
response = await agent.run(
|
||||
f"Please read {source_file}, analyze it, and create a summary file at {summary_file}"
|
||||
)
|
||||
|
||||
# Verify the interaction
|
||||
assert "summary" in response.lower()
|
||||
|
||||
# Verify the provider was called correctly
|
||||
assert len(mock_provider.calls) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_scenario(self, mock_provider):
|
||||
"""Test listing files in a directory."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create some test files
|
||||
for i in range(3):
|
||||
with open(os.path.join(tmpdir, f"file{i}.txt"), "w") as f:
|
||||
f.write(f"Content {i}")
|
||||
|
||||
# Configure agent to list files
|
||||
mock_provider.add_tool_call(
|
||||
"list_files",
|
||||
{"directory": tmpdir},
|
||||
"file0.txt\nfile1.txt\nfile2.txt",
|
||||
)
|
||||
mock_provider.add_text_response(
|
||||
f"I found 3 files in {tmpdir}: file0.txt, file1.txt, file2.txt"
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=mock_provider,
|
||||
tools=[list_files],
|
||||
system_prompt="You are a file system assistant.",
|
||||
)
|
||||
|
||||
response = await agent.run(f"List all files in {tmpdir}")
|
||||
|
||||
assert "3 files" in response
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multi_step_file_workflow(self, mock_provider):
|
||||
"""Test a complex multi-step file workflow.
|
||||
|
||||
Scenario:
|
||||
1. List files in directory
|
||||
2. Read each file
|
||||
3. Create a combined report
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create test files
|
||||
files_content = {
|
||||
"report1.txt": "Sales increased by 20%",
|
||||
"report2.txt": "Customer satisfaction at 85%",
|
||||
"report3.txt": "Bug fixes: 15 resolved",
|
||||
}
|
||||
|
||||
for name, content in files_content.items():
|
||||
with open(os.path.join(tmpdir, name), "w") as f:
|
||||
f.write(content)
|
||||
|
||||
# Configure agent responses for multi-step workflow
|
||||
tools = [read_file, write_file, list_files]
|
||||
|
||||
# Step 1: List files
|
||||
mock_provider.add_tool_call(
|
||||
"list_files",
|
||||
{"directory": tmpdir},
|
||||
"report1.txt\nreport2.txt\nreport3.txt",
|
||||
)
|
||||
|
||||
# Step 2: Read all files
|
||||
mock_provider.add_tool_call(
|
||||
"read_file",
|
||||
{"file_path": os.path.join(tmpdir, "report1.txt")},
|
||||
"Sales increased by 20%",
|
||||
)
|
||||
mock_provider.add_tool_call(
|
||||
"read_file",
|
||||
{"file_path": os.path.join(tmpdir, "report2.txt")},
|
||||
"Customer satisfaction at 85%",
|
||||
)
|
||||
mock_provider.add_tool_call(
|
||||
"read_file",
|
||||
{"file_path": os.path.join(tmpdir, "report3.txt")},
|
||||
"Bug fixes: 15 resolved",
|
||||
)
|
||||
|
||||
# Step 3: Write combined report
|
||||
combined_report = """Combined Report
|
||||
================
|
||||
|
||||
1. Sales: Increased by 20%
|
||||
2. Customer Satisfaction: 85%
|
||||
3. Development: 15 bugs resolved
|
||||
"""
|
||||
mock_provider.add_tool_call(
|
||||
"write_file",
|
||||
{
|
||||
"file_path": os.path.join(tmpdir, "combined_report.txt"),
|
||||
"content": combined_report,
|
||||
},
|
||||
f"File successfully written to {os.path.join(tmpdir, 'combined_report.txt')}",
|
||||
)
|
||||
|
||||
mock_provider.add_text_response(
|
||||
"I've created a combined report summarizing all three reports."
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=mock_provider,
|
||||
tools=tools,
|
||||
system_prompt="You are a report analyst assistant.",
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
f"List all files in {tmpdir}, read them all, and create a combined report at combined_report.txt"
|
||||
)
|
||||
|
||||
assert "combined report" in response.lower()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Additional Tools for Extended Scenarios
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@tool()
|
||||
async def count_words(file_path: str) -> str:
|
||||
"""Count the number of words in a file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to analyze.
|
||||
|
||||
Returns:
|
||||
The word count as a string.
|
||||
"""
|
||||
with open(file_path) as f:
|
||||
content = f.read()
|
||||
word_count = len(content.split())
|
||||
return f"Word count: {word_count}"
|
||||
|
||||
|
||||
@tool()
|
||||
async def append_to_file(file_path: str, content: str) -> str:
|
||||
"""Append content to an existing file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to append to.
|
||||
content: Content to append.
|
||||
|
||||
Returns:
|
||||
Success message.
|
||||
"""
|
||||
with open(file_path, "a") as f:
|
||||
f.write("\n" + content)
|
||||
return f"Content appended to {file_path}"
|
||||
|
||||
|
||||
@pytest.mark.scenario
|
||||
class TestExtendedFileOperations:
|
||||
"""Extended scenarios with more file operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_count_and_append(self, mock_provider):
|
||||
"""Test reading a file, counting words, and appending a note."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
source_file = os.path.join(tmpdir, "document.txt")
|
||||
with open(source_file, "w") as f:
|
||||
f.write("This is a test document with several words in it.")
|
||||
|
||||
tools = [read_file, write_file, count_words, append_to_file]
|
||||
|
||||
# Step 1: Read file
|
||||
mock_provider.add_tool_call(
|
||||
"read_file",
|
||||
{"file_path": source_file},
|
||||
"This is a test document with several words in it.",
|
||||
)
|
||||
|
||||
# Step 2: Count words
|
||||
mock_provider.add_tool_call(
|
||||
"count_words",
|
||||
{"file_path": source_file},
|
||||
"Word count: 10",
|
||||
)
|
||||
|
||||
# Step 3: Append analysis
|
||||
mock_provider.add_tool_call(
|
||||
"append_to_file",
|
||||
{
|
||||
"file_path": source_file,
|
||||
"content": "\n\n[Analysis] This document contains 10 words.",
|
||||
},
|
||||
f"Content appended to {source_file}",
|
||||
)
|
||||
|
||||
mock_provider.add_text_response(
|
||||
"I've analyzed the document and appended the word count analysis."
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
provider=mock_provider,
|
||||
tools=tools,
|
||||
system_prompt="You are a document analysis assistant.",
|
||||
)
|
||||
|
||||
response = await agent.run(
|
||||
f"Read {source_file}, count its words, and append the word count as an analysis note"
|
||||
)
|
||||
|
||||
assert "analyzed" in response.lower()
|
||||
226
agentlite/tests/scenarios/test_file_operations_real_api.py
Normal file
226
agentlite/tests/scenarios/test_file_operations_real_api.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""End-to-end scenario test for file operations with real API.
|
||||
|
||||
This test simulates a realistic scenario where an agent:
|
||||
1. Reads a file
|
||||
2. Explains its content
|
||||
3. Creates a new file with analysis results
|
||||
|
||||
Uses real SiliconFlow qwen3.5-397B API (requires SILICONFLOW_API_KEY env var).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from agentlite import Agent, OpenAIProvider, tool
|
||||
|
||||
# =============================================================================
|
||||
# Configuration from model_config.toml
|
||||
# =============================================================================
|
||||
|
||||
# SiliconFlow API configuration (matches qwen35_397b in model_config.toml)
|
||||
SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
|
||||
SILICONFLOW_MODEL = "Qwen/Qwen3.5-397B-A17B"
|
||||
|
||||
|
||||
def get_siliconflow_provider() -> OpenAIProvider | None:
|
||||
"""Create OpenAIProvider for SiliconFlow API.
|
||||
|
||||
Returns None if SILICONFLOW_API_KEY is not set.
|
||||
"""
|
||||
api_key = os.environ.get("SILICONFLOW_API_KEY")
|
||||
if not api_key:
|
||||
return None
|
||||
|
||||
return OpenAIProvider(
|
||||
api_key=api_key,
|
||||
base_url=SILICONFLOW_BASE_URL,
|
||||
model=SILICONFLOW_MODEL,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# File Operation Tools
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@tool()
|
||||
async def read_file(file_path: str) -> str:
|
||||
"""Read the content of a file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to read.
|
||||
|
||||
Returns:
|
||||
The content of the file as a string.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the file does not exist.
|
||||
"""
|
||||
with open(file_path) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@tool()
|
||||
async def write_file(file_path: str, content: str) -> str:
|
||||
"""Write content to a file, creating it if it doesn't exist.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to write.
|
||||
content: Content to write to the file.
|
||||
|
||||
Returns:
|
||||
Success message confirming the file was written.
|
||||
"""
|
||||
# Create parent directories if they don't exist
|
||||
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return f"File successfully written to {file_path}"
|
||||
|
||||
|
||||
@tool()
|
||||
async def list_files(directory: str) -> str:
|
||||
"""List all files in a directory.
|
||||
|
||||
Args:
|
||||
directory: Path to the directory to list.
|
||||
|
||||
Returns:
|
||||
A newline-separated list of file names in the directory.
|
||||
"""
|
||||
files = os.listdir(directory)
|
||||
return "\n".join(files)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Real API E2E Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_provider():
|
||||
"""Create a real SiliconFlow provider.
|
||||
|
||||
Skip tests if SILICONFLOW_API_KEY is not set.
|
||||
"""
|
||||
provider = get_siliconflow_provider()
|
||||
if provider is None:
|
||||
pytest.skip("SILICONFLOW_API_KEY not set, skipping real API tests")
|
||||
return provider
|
||||
|
||||
|
||||
@pytest.mark.scenario
|
||||
@pytest.mark.expensive
|
||||
class TestFileOperationsWithRealAPI:
|
||||
"""End-to-end tests with real SiliconFlow API."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_and_summarize(self, real_provider):
|
||||
"""Test reading a file and creating a summary with real API."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create a source file with meaningful content
|
||||
source_file = os.path.join(tmpdir, "source.txt")
|
||||
source_content = """AgentLite 项目概述
|
||||
================
|
||||
|
||||
AgentLite 是一个轻量级的 Agent 组件库,主要特点:
|
||||
- 异步优先设计
|
||||
- OpenAI 兼容 API
|
||||
- 工具系统 (支持 MCP)
|
||||
- 流式响应支持
|
||||
|
||||
使用示例:
|
||||
```python
|
||||
from agentlite import Agent, OpenAIProvider
|
||||
|
||||
provider = OpenAIProvider(api_key="...", model="gpt-4")
|
||||
agent = Agent(provider=provider)
|
||||
response = await agent.run("Hello!")
|
||||
```
|
||||
"""
|
||||
with open(source_file, "w") as f:
|
||||
f.write(source_content)
|
||||
|
||||
# Create agent with file tools
|
||||
tools = [read_file, write_file, list_files]
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=tools,
|
||||
system_prompt="你是一个文件分析助手。请使用工具来完成任务。",
|
||||
)
|
||||
|
||||
# Run the agent to read, analyze, and write summary
|
||||
output_file = os.path.join(tmpdir, "summary.txt")
|
||||
response = await agent.run(
|
||||
f"请读取 {source_file} 文件,分析其内容,并创建一个摘要文件保存到 {output_file}。"
|
||||
)
|
||||
|
||||
# Verify the agent responded
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[Agent 响应]:\n{response}\n")
|
||||
|
||||
# Verify the output file was created
|
||||
if os.path.exists(output_file):
|
||||
with open(output_file) as f:
|
||||
output_content = f.read()
|
||||
print(f"\n[输出文件内容]:\n{output_content}\n")
|
||||
assert len(output_content) > 0, "Output file should not be empty"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_and_combine(self, real_provider):
|
||||
"""Test listing files, reading them, and creating combined report."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create multiple files
|
||||
files = {
|
||||
"sales.txt": "销售额增长了 20%",
|
||||
"users.txt": "用户满意度达到 85%",
|
||||
"bugs.txt": "修复了 15 个问题",
|
||||
}
|
||||
for name, content in files.items():
|
||||
with open(os.path.join(tmpdir, name), "w") as f:
|
||||
f.write(content)
|
||||
|
||||
# Create agent with file tools
|
||||
tools = [read_file, write_file, list_files]
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
tools=tools,
|
||||
system_prompt="你是一个数据分析助手。请使用工具来完成任务。",
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
report_file = os.path.join(tmpdir, "report.txt")
|
||||
response = await agent.run(
|
||||
f"列出 {tmpdir} 目录中的所有文件,读取每个文件的内容,然后创建一份综合报告保存到 {report_file}。"
|
||||
)
|
||||
|
||||
# Verify the agent responded
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[Agent 响应]:\n{response}\n")
|
||||
|
||||
# The agent should have created the report file
|
||||
if os.path.exists(report_file):
|
||||
with open(report_file) as f:
|
||||
report_content = f.read()
|
||||
print(f"\n[报告文件内容]:\n{report_content}\n")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_simple_conversation(self, real_provider):
|
||||
"""Test basic conversation without tools."""
|
||||
agent = Agent(
|
||||
provider=real_provider,
|
||||
system_prompt="你是一个有帮助的助手。请用中文回答。",
|
||||
)
|
||||
|
||||
response = await agent.run("你好!请简单介绍一下你自己。")
|
||||
|
||||
assert response, "Agent should return a response"
|
||||
print(f"\n[Agent 自我介绍]:\n{response}\n")
|
||||
assert len(response) > 10, "Response should be meaningful"
|
||||
Reference in New Issue
Block a user