File size: 9,813 Bytes
f6686e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
import pytest
import logging
logger = logging.getLogger("tinytroupe")

import sys
sys.path.insert(0, '../../tinytroupe/')
sys.path.insert(0, '../../')
sys.path.insert(0, '..')

from tinytroupe.tools.tiny_tool import TinyTool
from tinytroupe.examples import create_oscar_the_architect, create_lisa_the_data_scientist
from tinytroupe.extraction import ArtifactExporter
from tinytroupe.enrichment import TinyEnricher
from testing_utils import *

class MockTool(TinyTool):
    """A mock tool implementation for testing."""
    
    def __init__(self, name="MockTool", description="A test tool", **kwargs):
        super().__init__(name, description, **kwargs)
        self.actions_processed = []
    
    def _process_action(self, agent, action: dict) -> bool:
        """Mock implementation that records processed actions."""
        self.actions_processed.append(action)
        return True
    
    def actions_definitions_prompt(self) -> str:
        """Mock implementation of action definitions."""
        return "MOCK_ACTION: Use this tool for testing purposes."
    
    def actions_constraints_prompt(self) -> str:
        """Mock implementation of action constraints."""
        return "Only use this tool during testing."

class DangerousMockTool(TinyTool):
    """A mock tool with real-world side effects for testing."""
    
    def __init__(self):
        super().__init__(
            name="DangerousTool",
            description="A tool with real-world side effects",
            real_world_side_effects=True
        )
    
    def _process_action(self, agent, action: dict) -> bool:
        return True
    
    def actions_definitions_prompt(self) -> str:
        return "DANGEROUS_ACTION: This action has real-world effects."
    
    def actions_constraints_prompt(self) -> str:
        return "Use with extreme caution."

def test_tiny_tool_initialization(setup):
    """Test TinyTool initialization with various parameters."""
    
    # Test basic initialization
    tool = MockTool()
    assert tool.name == "MockTool"
    assert tool.description == "A test tool"
    assert tool.owner is None
    assert tool.real_world_side_effects == False
    assert tool.exporter is None
    assert tool.enricher is None
    
    # Test initialization with all parameters
    agent = create_oscar_the_architect()
    exporter = ArtifactExporter(base_output_folder="test")
    enricher = TinyEnricher()
    
    tool = MockTool(
        name="CustomTool",
        description="Custom description",
        owner=agent,
        real_world_side_effects=True,
        exporter=exporter,
        enricher=enricher
    )
    
    assert tool.name == "CustomTool"
    assert tool.description == "Custom description"
    assert tool.owner == agent
    assert tool.real_world_side_effects == True
    assert tool.exporter == exporter
    assert tool.enricher == enricher

def test_tiny_tool_ownership(setup):
    """Test TinyTool ownership mechanisms."""
    
    oscar = create_oscar_the_architect()
    lisa = create_lisa_the_data_scientist()
    
    # Create tool owned by Oscar
    tool = MockTool(owner=oscar)
    
    # Test that Oscar can use the tool
    mock_action = {"type": "MOCK_ACTION", "content": "test"}
    result = tool.process_action(oscar, mock_action)
    assert result == True, "Owner should be able to use tool"
    
    # Test that Lisa cannot use the tool
    with pytest.raises(ValueError, match="does not own tool"):
        tool.process_action(lisa, mock_action)
    
    # Test setting owner
    tool.set_owner(lisa)
    assert tool.owner == lisa
    
    # Now Lisa should be able to use it
    result = tool.process_action(lisa, mock_action)
    assert result == True, "New owner should be able to use tool"

def test_tiny_tool_real_world_side_effects(setup):
    """Test TinyTool real-world side effects warning."""
    
    # Test tool without side effects
    safe_tool = MockTool(real_world_side_effects=False)
    agent = create_oscar_the_architect()
    
    # Should not raise warnings for safe tools
    mock_action = {"type": "MOCK_ACTION", "content": "test"}
    result = safe_tool.process_action(agent, mock_action)
    assert result == True
    
    # Test tool with side effects
    dangerous_tool = DangerousMockTool()
    
    # Should log warning (we can't easily test logging in unit tests,
    # but we can verify the tool still works)
    result = dangerous_tool.process_action(agent, mock_action)
    assert result == True

def test_tiny_tool_action_processing(setup):
    """Test TinyTool action processing functionality."""
    
    tool = MockTool()
    agent = create_oscar_the_architect()
    
    # Test processing different types of actions
    actions = [
        {"type": "MOCK_ACTION", "content": "action 1"},
        {"type": "MOCK_ACTION", "content": "action 2"},
        {"type": "OTHER_ACTION", "content": "action 3"}
    ]
    
    for action in actions:
        result = tool.process_action(agent, action)
        assert result == True, "Should process actions successfully"
    
    # Verify actions were recorded
    assert len(tool.actions_processed) == 3, "Should have processed all actions"
    assert tool.actions_processed[0]["content"] == "action 1"
    assert tool.actions_processed[1]["content"] == "action 2"
    assert tool.actions_processed[2]["content"] == "action 3"

def test_tiny_tool_prompts():
    """Test TinyTool prompt generation methods."""
    
    tool = MockTool()
    
    # Test action definitions prompt
    definitions = tool.actions_definitions_prompt()
    assert isinstance(definitions, str), "Should return string"
    assert len(definitions) > 0, "Should not be empty"
    assert "MOCK_ACTION" in definitions, "Should contain action definition"
    
    # Test action constraints prompt
    constraints = tool.actions_constraints_prompt()
    assert isinstance(constraints, str), "Should return string"
    assert len(constraints) > 0, "Should not be empty"

def test_tiny_tool_serialization():
    """Test TinyTool serialization/deserialization."""
    
    tool = MockTool(
        name="SerializationTest",
        description="Test serialization",
        real_world_side_effects=True
    )
    
    # Test serialization
    serialized = tool.to_json()
    assert isinstance(serialized, dict), "Should serialize to dictionary"
    assert "name" in serialized, "Should include name"
    assert "description" in serialized, "Should include description"
    assert "real_world_side_effects" in serialized, "Should include side effects flag"
    
    # Test deserialization
    new_tool = MockTool.from_json(serialized)
    assert new_tool.name == tool.name
    assert new_tool.description == tool.description
    assert new_tool.real_world_side_effects == tool.real_world_side_effects

def test_tiny_tool_with_exporter_and_enricher(setup):
    """Test TinyTool with exporter and enricher components."""
    
    exporter = ArtifactExporter(base_output_folder="test")
    enricher = TinyEnricher()
    
    tool = MockTool(
        exporter=exporter,
        enricher=enricher
    )
    
    assert tool.exporter == exporter, "Should store exporter"
    assert tool.enricher == enricher, "Should store enricher"
    
    # Test that tool can still process actions with these components
    agent = create_oscar_the_architect()
    mock_action = {"type": "MOCK_ACTION", "content": "test with components"}
    
    result = tool.process_action(agent, mock_action)
    assert result == True, "Should work with exporter and enricher"

def test_tiny_tool_abstract_methods(setup):
    """Test that TinyTool properly enforces abstract methods."""
    
    # Create a tool without implementing required methods
    class IncompleteTool(TinyTool):
        pass
    
    incomplete_tool = IncompleteTool("incomplete", "missing methods")
    agent = create_oscar_the_architect()
    
    # Should raise NotImplementedError for unimplemented methods
    with pytest.raises(NotImplementedError):
        incomplete_tool._process_action(agent, {})
    
    with pytest.raises(NotImplementedError):
        incomplete_tool.actions_definitions_prompt()
    
    with pytest.raises(NotImplementedError):
        incomplete_tool.actions_constraints_prompt()

def test_tiny_tool_edge_cases(setup):
    """Test TinyTool edge cases and error handling."""
    
    tool = MockTool()
    agent = create_oscar_the_architect()
    
    # Test with None action
    result = tool.process_action(agent, None)
    assert result == True, "Should handle None action gracefully"
    
    # Test with empty action
    result = tool.process_action(agent, {})
    assert result == True, "Should handle empty action gracefully"
    
    # Test with malformed action
    malformed_action = {"invalid": "structure"}
    result = tool.process_action(agent, malformed_action)
    assert result == True, "Should handle malformed action gracefully"

def test_tiny_tool_multiple_agents(setup):
    """Test TinyTool with multiple agents."""
    
    tool = MockTool()  # No owner, so any agent can use it
    oscar = create_oscar_the_architect()
    lisa = create_lisa_the_data_scientist()
    
    # Both agents should be able to use the tool
    oscar_action = {"type": "MOCK_ACTION", "content": "Oscar's action"}
    lisa_action = {"type": "MOCK_ACTION", "content": "Lisa's action"}
    
    result1 = tool.process_action(oscar, oscar_action)
    result2 = tool.process_action(lisa, lisa_action)
    
    assert result1 == True, "Oscar should be able to use tool"
    assert result2 == True, "Lisa should be able to use tool"
    
    # Verify both actions were processed
    assert len(tool.actions_processed) == 2
    assert tool.actions_processed[0]["content"] == "Oscar's action"
    assert tool.actions_processed[1]["content"] == "Lisa's action"