jarviscore-framework 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jarviscore/__init__.py +37 -5
- jarviscore/adapter/__init__.py +34 -0
- jarviscore/adapter/decorator.py +332 -0
- jarviscore/cli/check.py +18 -13
- jarviscore/cli/scaffold.py +178 -0
- jarviscore/context/__init__.py +40 -0
- jarviscore/context/dependency.py +160 -0
- jarviscore/context/jarvis_context.py +207 -0
- jarviscore/context/memory.py +155 -0
- jarviscore/data/.env.example +146 -0
- jarviscore/data/__init__.py +7 -0
- jarviscore/data/examples/calculator_agent_example.py +77 -0
- jarviscore/data/examples/multi_agent_workflow.py +132 -0
- jarviscore/data/examples/research_agent_example.py +76 -0
- jarviscore/docs/CONFIGURATION.md +6 -2
- jarviscore/docs/GETTING_STARTED.md +7 -4
- jarviscore/docs/TROUBLESHOOTING.md +11 -7
- jarviscore/docs/USER_GUIDE.md +6 -2
- jarviscore/execution/llm.py +23 -16
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/METADATA +10 -9
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/RECORD +26 -12
- tests/test_context.py +467 -0
- tests/test_decorator.py +622 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/WHEEL +0 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/top_level.txt +0 -0
tests/test_decorator.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for @jarvis_agent decorator.
|
|
3
|
+
|
|
4
|
+
Tests the decorator that converts any class into a JarvisCore agent,
|
|
5
|
+
enabling Custom Profile usage with existing agents (LangChain, CrewAI, raw Python).
|
|
6
|
+
"""
|
|
7
|
+
import pytest
|
|
8
|
+
from jarviscore import jarvis_agent, JarvisContext, Mesh
|
|
9
|
+
from jarviscore.adapter.decorator import detect_execute_method, EXECUTE_METHODS
|
|
10
|
+
from jarviscore.profiles.customagent import CustomAgent
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDetectExecuteMethod:
|
|
14
|
+
"""Tests for execute method detection."""
|
|
15
|
+
|
|
16
|
+
def test_detects_run_method(self):
|
|
17
|
+
"""Test detection of 'run' method."""
|
|
18
|
+
class Agent:
|
|
19
|
+
def run(self, data):
|
|
20
|
+
return data
|
|
21
|
+
|
|
22
|
+
assert detect_execute_method(Agent) == "run"
|
|
23
|
+
|
|
24
|
+
def test_detects_invoke_method(self):
|
|
25
|
+
"""Test detection of 'invoke' method (LangChain style)."""
|
|
26
|
+
class Agent:
|
|
27
|
+
def invoke(self, input):
|
|
28
|
+
return input
|
|
29
|
+
|
|
30
|
+
assert detect_execute_method(Agent) == "invoke"
|
|
31
|
+
|
|
32
|
+
def test_detects_execute_method(self):
|
|
33
|
+
"""Test detection of 'execute' method."""
|
|
34
|
+
class Agent:
|
|
35
|
+
def execute(self, task):
|
|
36
|
+
return task
|
|
37
|
+
|
|
38
|
+
assert detect_execute_method(Agent) == "execute"
|
|
39
|
+
|
|
40
|
+
def test_detects_call_method(self):
|
|
41
|
+
"""Test detection of 'call' method."""
|
|
42
|
+
class Agent:
|
|
43
|
+
def call(self, args):
|
|
44
|
+
return args
|
|
45
|
+
|
|
46
|
+
assert detect_execute_method(Agent) == "call"
|
|
47
|
+
|
|
48
|
+
def test_detects_dunder_call(self):
|
|
49
|
+
"""Test detection of '__call__' method."""
|
|
50
|
+
class Agent:
|
|
51
|
+
def __call__(self, x):
|
|
52
|
+
return x
|
|
53
|
+
|
|
54
|
+
assert detect_execute_method(Agent) == "__call__"
|
|
55
|
+
|
|
56
|
+
def test_detects_process_method(self):
|
|
57
|
+
"""Test detection of 'process' method."""
|
|
58
|
+
class Agent:
|
|
59
|
+
def process(self, data):
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
assert detect_execute_method(Agent) == "process"
|
|
63
|
+
|
|
64
|
+
def test_detects_handle_method(self):
|
|
65
|
+
"""Test detection of 'handle' method."""
|
|
66
|
+
class Agent:
|
|
67
|
+
def handle(self, request):
|
|
68
|
+
return request
|
|
69
|
+
|
|
70
|
+
assert detect_execute_method(Agent) == "handle"
|
|
71
|
+
|
|
72
|
+
def test_prefers_run_over_others(self):
|
|
73
|
+
"""Test that 'run' is preferred when multiple methods exist."""
|
|
74
|
+
class Agent:
|
|
75
|
+
def run(self, data):
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
def invoke(self, data):
|
|
79
|
+
return data
|
|
80
|
+
|
|
81
|
+
assert detect_execute_method(Agent) == "run"
|
|
82
|
+
|
|
83
|
+
def test_returns_none_for_no_method(self):
|
|
84
|
+
"""Test returns None when no execute method found."""
|
|
85
|
+
class Agent:
|
|
86
|
+
def custom_method(self, data):
|
|
87
|
+
return data
|
|
88
|
+
|
|
89
|
+
assert detect_execute_method(Agent) is None
|
|
90
|
+
|
|
91
|
+
def test_execute_methods_list(self):
|
|
92
|
+
"""Test EXECUTE_METHODS contains expected methods."""
|
|
93
|
+
assert "run" in EXECUTE_METHODS
|
|
94
|
+
assert "invoke" in EXECUTE_METHODS
|
|
95
|
+
assert "execute" in EXECUTE_METHODS
|
|
96
|
+
assert "__call__" in EXECUTE_METHODS
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestJarvisAgentDecorator:
|
|
100
|
+
"""Tests for @jarvis_agent decorator."""
|
|
101
|
+
|
|
102
|
+
def test_decorator_sets_role(self):
|
|
103
|
+
"""Test decorator sets role attribute."""
|
|
104
|
+
@jarvis_agent(role="test_role", capabilities=["cap1"])
|
|
105
|
+
class TestAgent:
|
|
106
|
+
def run(self, data):
|
|
107
|
+
return data
|
|
108
|
+
|
|
109
|
+
assert TestAgent.role == "test_role"
|
|
110
|
+
|
|
111
|
+
def test_decorator_sets_capabilities(self):
|
|
112
|
+
"""Test decorator sets capabilities attribute."""
|
|
113
|
+
@jarvis_agent(role="test", capabilities=["cap1", "cap2", "cap3"])
|
|
114
|
+
class TestAgent:
|
|
115
|
+
def run(self, data):
|
|
116
|
+
return data
|
|
117
|
+
|
|
118
|
+
assert TestAgent.capabilities == ["cap1", "cap2", "cap3"]
|
|
119
|
+
|
|
120
|
+
def test_decorator_creates_customagent_subclass(self):
|
|
121
|
+
"""Test decorator creates CustomAgent subclass."""
|
|
122
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
123
|
+
class TestAgent:
|
|
124
|
+
def run(self, data):
|
|
125
|
+
return data
|
|
126
|
+
|
|
127
|
+
assert issubclass(TestAgent, CustomAgent)
|
|
128
|
+
|
|
129
|
+
def test_decorator_preserves_class_name(self):
|
|
130
|
+
"""Test decorator preserves original class name."""
|
|
131
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
132
|
+
class MySpecialAgent:
|
|
133
|
+
def run(self, data):
|
|
134
|
+
return data
|
|
135
|
+
|
|
136
|
+
assert MySpecialAgent.__name__ == "MySpecialAgent"
|
|
137
|
+
|
|
138
|
+
def test_decorator_preserves_docstring(self):
|
|
139
|
+
"""Test decorator preserves class docstring."""
|
|
140
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
141
|
+
class DocAgent:
|
|
142
|
+
"""This is a documented agent."""
|
|
143
|
+
def run(self, data):
|
|
144
|
+
return data
|
|
145
|
+
|
|
146
|
+
assert "documented agent" in DocAgent.__doc__
|
|
147
|
+
|
|
148
|
+
def test_decorator_stores_wrapped_class(self):
|
|
149
|
+
"""Test decorator stores reference to wrapped class."""
|
|
150
|
+
class OriginalClass:
|
|
151
|
+
def run(self, data):
|
|
152
|
+
return data
|
|
153
|
+
|
|
154
|
+
Wrapped = jarvis_agent(role="test", capabilities=["cap"])(OriginalClass)
|
|
155
|
+
|
|
156
|
+
assert Wrapped._wrapped_class is OriginalClass
|
|
157
|
+
|
|
158
|
+
def test_decorator_stores_execute_method_name(self):
|
|
159
|
+
"""Test decorator stores execute method name."""
|
|
160
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
161
|
+
class TestAgent:
|
|
162
|
+
def run(self, data):
|
|
163
|
+
return data
|
|
164
|
+
|
|
165
|
+
assert TestAgent._execute_method == "run"
|
|
166
|
+
|
|
167
|
+
def test_decorator_with_custom_execute_method(self):
|
|
168
|
+
"""Test decorator with explicit execute_method."""
|
|
169
|
+
@jarvis_agent(role="test", capabilities=["cap"], execute_method="custom_run")
|
|
170
|
+
class TestAgent:
|
|
171
|
+
def custom_run(self, data):
|
|
172
|
+
return {"result": data}
|
|
173
|
+
|
|
174
|
+
assert TestAgent._execute_method == "custom_run"
|
|
175
|
+
|
|
176
|
+
def test_decorator_detects_context_parameter(self):
|
|
177
|
+
"""Test decorator detects ctx parameter in method."""
|
|
178
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
179
|
+
class TestAgent:
|
|
180
|
+
def run(self, task, ctx: JarvisContext):
|
|
181
|
+
return task
|
|
182
|
+
|
|
183
|
+
assert TestAgent._expects_context is True
|
|
184
|
+
|
|
185
|
+
def test_decorator_detects_no_context_parameter(self):
|
|
186
|
+
"""Test decorator detects absence of ctx parameter."""
|
|
187
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
188
|
+
class TestAgent:
|
|
189
|
+
def run(self, data):
|
|
190
|
+
return data
|
|
191
|
+
|
|
192
|
+
assert TestAgent._expects_context is False
|
|
193
|
+
|
|
194
|
+
def test_decorator_detects_context_named_context(self):
|
|
195
|
+
"""Test decorator detects 'context' parameter name."""
|
|
196
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
197
|
+
class TestAgent:
|
|
198
|
+
def run(self, task, context):
|
|
199
|
+
return task
|
|
200
|
+
|
|
201
|
+
assert TestAgent._expects_context is True
|
|
202
|
+
|
|
203
|
+
def test_decorator_raises_for_missing_method(self):
|
|
204
|
+
"""Test decorator raises error if method not found."""
|
|
205
|
+
with pytest.raises(ValueError) as exc_info:
|
|
206
|
+
@jarvis_agent(role="test", capabilities=["cap"], execute_method="missing")
|
|
207
|
+
class TestAgent:
|
|
208
|
+
def run(self, data):
|
|
209
|
+
return data
|
|
210
|
+
|
|
211
|
+
assert "has no method 'missing'" in str(exc_info.value)
|
|
212
|
+
|
|
213
|
+
def test_decorator_raises_for_no_detectable_method(self):
|
|
214
|
+
"""Test decorator raises error if no method detected."""
|
|
215
|
+
with pytest.raises(ValueError) as exc_info:
|
|
216
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
217
|
+
class TestAgent:
|
|
218
|
+
def custom_only(self, data):
|
|
219
|
+
return data
|
|
220
|
+
|
|
221
|
+
assert "Could not detect execute method" in str(exc_info.value)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class TestDecoratedAgentInstantiation:
|
|
225
|
+
"""Tests for instantiating decorated agents."""
|
|
226
|
+
|
|
227
|
+
def test_instantiation_creates_agent(self):
|
|
228
|
+
"""Test instantiation creates agent instance."""
|
|
229
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
230
|
+
class TestAgent:
|
|
231
|
+
def run(self, data):
|
|
232
|
+
return data
|
|
233
|
+
|
|
234
|
+
agent = TestAgent()
|
|
235
|
+
|
|
236
|
+
assert agent is not None
|
|
237
|
+
assert agent.role == "test"
|
|
238
|
+
|
|
239
|
+
def test_instantiation_generates_agent_id(self):
|
|
240
|
+
"""Test instantiation generates agent_id."""
|
|
241
|
+
@jarvis_agent(role="calculator", capabilities=["math"])
|
|
242
|
+
class Calculator:
|
|
243
|
+
def run(self, data):
|
|
244
|
+
return data
|
|
245
|
+
|
|
246
|
+
agent = Calculator()
|
|
247
|
+
|
|
248
|
+
assert agent.agent_id is not None
|
|
249
|
+
assert agent.agent_id.startswith("calculator-")
|
|
250
|
+
|
|
251
|
+
def test_instantiation_with_custom_agent_id(self):
|
|
252
|
+
"""Test instantiation with custom agent_id."""
|
|
253
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
254
|
+
class TestAgent:
|
|
255
|
+
def run(self, data):
|
|
256
|
+
return data
|
|
257
|
+
|
|
258
|
+
agent = TestAgent(agent_id="custom-id-123")
|
|
259
|
+
|
|
260
|
+
assert agent.agent_id == "custom-id-123"
|
|
261
|
+
|
|
262
|
+
def test_instantiation_creates_wrapped_instance(self):
|
|
263
|
+
"""Test instantiation creates instance of wrapped class."""
|
|
264
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
265
|
+
class TestAgent:
|
|
266
|
+
def __init__(self):
|
|
267
|
+
self.initialized = True
|
|
268
|
+
|
|
269
|
+
def run(self, data):
|
|
270
|
+
return data
|
|
271
|
+
|
|
272
|
+
agent = TestAgent()
|
|
273
|
+
|
|
274
|
+
assert hasattr(agent, "_instance")
|
|
275
|
+
assert agent._instance.initialized is True
|
|
276
|
+
|
|
277
|
+
def test_instantiation_with_kwargs(self):
|
|
278
|
+
"""Test instantiation passes kwargs to wrapped class."""
|
|
279
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
280
|
+
class ConfigurableAgent:
|
|
281
|
+
def __init__(self, config_value=None):
|
|
282
|
+
self.config = config_value
|
|
283
|
+
|
|
284
|
+
def run(self, data):
|
|
285
|
+
return data
|
|
286
|
+
|
|
287
|
+
agent = ConfigurableAgent(config_value="custom_config")
|
|
288
|
+
|
|
289
|
+
assert agent._instance.config == "custom_config"
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TestDecoratedAgentSetup:
|
|
293
|
+
"""Tests for decorated agent setup."""
|
|
294
|
+
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_setup_calls_wrapped_setup(self):
|
|
297
|
+
"""Test setup calls wrapped class setup if exists."""
|
|
298
|
+
setup_called = []
|
|
299
|
+
|
|
300
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
301
|
+
class TestAgent:
|
|
302
|
+
def setup(self):
|
|
303
|
+
setup_called.append("wrapped_setup")
|
|
304
|
+
|
|
305
|
+
def run(self, data):
|
|
306
|
+
return data
|
|
307
|
+
|
|
308
|
+
agent = TestAgent()
|
|
309
|
+
await agent.setup()
|
|
310
|
+
|
|
311
|
+
assert "wrapped_setup" in setup_called
|
|
312
|
+
|
|
313
|
+
@pytest.mark.asyncio
|
|
314
|
+
async def test_setup_handles_async_wrapped_setup(self):
|
|
315
|
+
"""Test setup handles async wrapped setup."""
|
|
316
|
+
setup_called = []
|
|
317
|
+
|
|
318
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
319
|
+
class TestAgent:
|
|
320
|
+
async def setup(self):
|
|
321
|
+
setup_called.append("async_setup")
|
|
322
|
+
|
|
323
|
+
def run(self, data):
|
|
324
|
+
return data
|
|
325
|
+
|
|
326
|
+
agent = TestAgent()
|
|
327
|
+
await agent.setup()
|
|
328
|
+
|
|
329
|
+
assert "async_setup" in setup_called
|
|
330
|
+
|
|
331
|
+
@pytest.mark.asyncio
|
|
332
|
+
async def test_setup_works_without_wrapped_setup(self):
|
|
333
|
+
"""Test setup works when wrapped class has no setup."""
|
|
334
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
335
|
+
class TestAgent:
|
|
336
|
+
def run(self, data):
|
|
337
|
+
return data
|
|
338
|
+
|
|
339
|
+
agent = TestAgent()
|
|
340
|
+
await agent.setup() # Should not raise
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class TestDecoratedAgentExecution:
|
|
344
|
+
"""Tests for decorated agent task execution."""
|
|
345
|
+
|
|
346
|
+
@pytest.mark.asyncio
|
|
347
|
+
async def test_execute_task_calls_wrapped_method(self):
|
|
348
|
+
"""Test execute_task calls wrapped class method."""
|
|
349
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
350
|
+
class TestAgent:
|
|
351
|
+
def run(self, data):
|
|
352
|
+
return {"doubled": data * 2}
|
|
353
|
+
|
|
354
|
+
agent = TestAgent()
|
|
355
|
+
result = await agent.execute_task({"params": 5})
|
|
356
|
+
|
|
357
|
+
assert result["status"] == "success"
|
|
358
|
+
assert result["output"]["doubled"] == 10
|
|
359
|
+
|
|
360
|
+
@pytest.mark.asyncio
|
|
361
|
+
async def test_execute_task_handles_async_method(self):
|
|
362
|
+
"""Test execute_task handles async wrapped method."""
|
|
363
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
364
|
+
class TestAgent:
|
|
365
|
+
async def run(self, data):
|
|
366
|
+
return {"async_result": data}
|
|
367
|
+
|
|
368
|
+
agent = TestAgent()
|
|
369
|
+
result = await agent.execute_task({"params": "test"})
|
|
370
|
+
|
|
371
|
+
assert result["status"] == "success"
|
|
372
|
+
assert result["output"]["async_result"] == "test"
|
|
373
|
+
|
|
374
|
+
@pytest.mark.asyncio
|
|
375
|
+
async def test_execute_task_normalizes_dict_result(self):
|
|
376
|
+
"""Test execute_task normalizes dict result."""
|
|
377
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
378
|
+
class TestAgent:
|
|
379
|
+
def run(self, data):
|
|
380
|
+
return {"key": "value"} # No status field
|
|
381
|
+
|
|
382
|
+
agent = TestAgent()
|
|
383
|
+
result = await agent.execute_task({"params": {}})
|
|
384
|
+
|
|
385
|
+
assert result["status"] == "success"
|
|
386
|
+
assert result["output"] == {"key": "value"}
|
|
387
|
+
assert "agent" in result
|
|
388
|
+
|
|
389
|
+
@pytest.mark.asyncio
|
|
390
|
+
async def test_execute_task_normalizes_non_dict_result(self):
|
|
391
|
+
"""Test execute_task normalizes non-dict result."""
|
|
392
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
393
|
+
class TestAgent:
|
|
394
|
+
def run(self, data):
|
|
395
|
+
return [1, 2, 3, 4, 5]
|
|
396
|
+
|
|
397
|
+
agent = TestAgent()
|
|
398
|
+
result = await agent.execute_task({"params": {}})
|
|
399
|
+
|
|
400
|
+
assert result["status"] == "success"
|
|
401
|
+
assert result["output"] == [1, 2, 3, 4, 5]
|
|
402
|
+
|
|
403
|
+
@pytest.mark.asyncio
|
|
404
|
+
async def test_execute_task_preserves_existing_status(self):
|
|
405
|
+
"""Test execute_task preserves status if already present."""
|
|
406
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
407
|
+
class TestAgent:
|
|
408
|
+
def run(self, data):
|
|
409
|
+
return {"status": "custom_status", "data": "value"}
|
|
410
|
+
|
|
411
|
+
agent = TestAgent()
|
|
412
|
+
result = await agent.execute_task({"params": {}})
|
|
413
|
+
|
|
414
|
+
assert result["status"] == "custom_status"
|
|
415
|
+
|
|
416
|
+
@pytest.mark.asyncio
|
|
417
|
+
async def test_execute_task_handles_exception(self):
|
|
418
|
+
"""Test execute_task handles exceptions gracefully."""
|
|
419
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
420
|
+
class TestAgent:
|
|
421
|
+
def run(self, data):
|
|
422
|
+
raise ValueError("Test error")
|
|
423
|
+
|
|
424
|
+
agent = TestAgent()
|
|
425
|
+
result = await agent.execute_task({"params": {}})
|
|
426
|
+
|
|
427
|
+
assert result["status"] == "failure"
|
|
428
|
+
assert "Test error" in result["error"]
|
|
429
|
+
|
|
430
|
+
@pytest.mark.asyncio
|
|
431
|
+
async def test_execute_task_with_task_string(self):
|
|
432
|
+
"""Test execute_task passes task string correctly."""
|
|
433
|
+
received_task = []
|
|
434
|
+
|
|
435
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
436
|
+
class TestAgent:
|
|
437
|
+
def run(self, task):
|
|
438
|
+
received_task.append(task)
|
|
439
|
+
return {"received": task}
|
|
440
|
+
|
|
441
|
+
agent = TestAgent()
|
|
442
|
+
await agent.execute_task({"task": "Do something"})
|
|
443
|
+
|
|
444
|
+
assert "Do something" in str(received_task)
|
|
445
|
+
|
|
446
|
+
@pytest.mark.asyncio
|
|
447
|
+
async def test_execute_task_with_params_dict(self):
|
|
448
|
+
"""Test execute_task passes params dict correctly."""
|
|
449
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
450
|
+
class TestAgent:
|
|
451
|
+
def run(self, params):
|
|
452
|
+
return {"a": params.get("a"), "b": params.get("b")}
|
|
453
|
+
|
|
454
|
+
agent = TestAgent()
|
|
455
|
+
result = await agent.execute_task({
|
|
456
|
+
"task": "Calculate",
|
|
457
|
+
"params": {"a": 5, "b": 3}
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
assert result["output"]["a"] == 5
|
|
461
|
+
assert result["output"]["b"] == 3
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class TestDecoratedAgentTeardown:
|
|
465
|
+
"""Tests for decorated agent teardown."""
|
|
466
|
+
|
|
467
|
+
@pytest.mark.asyncio
|
|
468
|
+
async def test_teardown_calls_wrapped_teardown(self):
|
|
469
|
+
"""Test teardown calls wrapped class teardown if exists."""
|
|
470
|
+
teardown_called = []
|
|
471
|
+
|
|
472
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
473
|
+
class TestAgent:
|
|
474
|
+
def teardown(self):
|
|
475
|
+
teardown_called.append("wrapped_teardown")
|
|
476
|
+
|
|
477
|
+
def run(self, data):
|
|
478
|
+
return data
|
|
479
|
+
|
|
480
|
+
agent = TestAgent()
|
|
481
|
+
await agent.teardown()
|
|
482
|
+
|
|
483
|
+
assert "wrapped_teardown" in teardown_called
|
|
484
|
+
|
|
485
|
+
@pytest.mark.asyncio
|
|
486
|
+
async def test_teardown_handles_async_wrapped_teardown(self):
|
|
487
|
+
"""Test teardown handles async wrapped teardown."""
|
|
488
|
+
teardown_called = []
|
|
489
|
+
|
|
490
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
491
|
+
class TestAgent:
|
|
492
|
+
async def teardown(self):
|
|
493
|
+
teardown_called.append("async_teardown")
|
|
494
|
+
|
|
495
|
+
def run(self, data):
|
|
496
|
+
return data
|
|
497
|
+
|
|
498
|
+
agent = TestAgent()
|
|
499
|
+
await agent.teardown()
|
|
500
|
+
|
|
501
|
+
assert "async_teardown" in teardown_called
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class TestDecoratedAgentWithContext:
|
|
505
|
+
"""Tests for decorated agents using JarvisContext."""
|
|
506
|
+
|
|
507
|
+
@pytest.mark.asyncio
|
|
508
|
+
async def test_context_not_passed_when_not_expected(self):
|
|
509
|
+
"""Test context is not passed when method doesn't expect it."""
|
|
510
|
+
received_args = []
|
|
511
|
+
|
|
512
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
513
|
+
class TestAgent:
|
|
514
|
+
def run(self, data):
|
|
515
|
+
received_args.append(data)
|
|
516
|
+
return {"received": data}
|
|
517
|
+
|
|
518
|
+
agent = TestAgent()
|
|
519
|
+
await agent.execute_task({"params": {"key": "value"}})
|
|
520
|
+
|
|
521
|
+
# Should receive params, not context
|
|
522
|
+
assert len(received_args) == 1
|
|
523
|
+
assert not isinstance(received_args[0], JarvisContext)
|
|
524
|
+
|
|
525
|
+
@pytest.mark.asyncio
|
|
526
|
+
async def test_context_passed_when_expected(self):
|
|
527
|
+
"""Test context is passed when method expects it."""
|
|
528
|
+
received_ctx = []
|
|
529
|
+
|
|
530
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
531
|
+
class TestAgent:
|
|
532
|
+
def run(self, task, ctx: JarvisContext):
|
|
533
|
+
received_ctx.append(ctx)
|
|
534
|
+
return {"has_ctx": ctx is not None}
|
|
535
|
+
|
|
536
|
+
agent = TestAgent()
|
|
537
|
+
agent._mesh = None # Simulate no mesh
|
|
538
|
+
|
|
539
|
+
result = await agent.execute_task({
|
|
540
|
+
"task": "test",
|
|
541
|
+
"context": {"workflow_id": "w1", "step_id": "s1"}
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
assert len(received_ctx) == 1
|
|
545
|
+
assert isinstance(received_ctx[0], JarvisContext)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class TestDecoratedAgentInheritance:
|
|
549
|
+
"""Tests for decorated agent inheritance."""
|
|
550
|
+
|
|
551
|
+
def test_inherits_can_handle(self):
|
|
552
|
+
"""Test decorated agent inherits can_handle method."""
|
|
553
|
+
@jarvis_agent(role="processor", capabilities=["processing", "transform"])
|
|
554
|
+
class TestAgent:
|
|
555
|
+
def run(self, data):
|
|
556
|
+
return data
|
|
557
|
+
|
|
558
|
+
agent = TestAgent()
|
|
559
|
+
|
|
560
|
+
assert agent.can_handle({"role": "processor"}) is True
|
|
561
|
+
assert agent.can_handle({"capability": "processing"}) is True
|
|
562
|
+
assert agent.can_handle({"capability": "transform"}) is True
|
|
563
|
+
assert agent.can_handle({"role": "other"}) is False
|
|
564
|
+
|
|
565
|
+
def test_inherits_agent_methods(self):
|
|
566
|
+
"""Test decorated agent has all Agent methods."""
|
|
567
|
+
@jarvis_agent(role="test", capabilities=["cap"])
|
|
568
|
+
class TestAgent:
|
|
569
|
+
def run(self, data):
|
|
570
|
+
return data
|
|
571
|
+
|
|
572
|
+
agent = TestAgent()
|
|
573
|
+
|
|
574
|
+
assert hasattr(agent, "setup")
|
|
575
|
+
assert hasattr(agent, "teardown")
|
|
576
|
+
assert hasattr(agent, "execute_task")
|
|
577
|
+
assert hasattr(agent, "can_handle")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
class TestMultipleDecoratedAgents:
|
|
581
|
+
"""Tests for multiple decorated agents."""
|
|
582
|
+
|
|
583
|
+
def test_multiple_agents_independent(self):
|
|
584
|
+
"""Test multiple decorated agents are independent."""
|
|
585
|
+
@jarvis_agent(role="agent1", capabilities=["cap1"])
|
|
586
|
+
class Agent1:
|
|
587
|
+
def run(self, data):
|
|
588
|
+
return {"from": "agent1"}
|
|
589
|
+
|
|
590
|
+
@jarvis_agent(role="agent2", capabilities=["cap2"])
|
|
591
|
+
class Agent2:
|
|
592
|
+
def run(self, data):
|
|
593
|
+
return {"from": "agent2"}
|
|
594
|
+
|
|
595
|
+
assert Agent1.role == "agent1"
|
|
596
|
+
assert Agent2.role == "agent2"
|
|
597
|
+
assert Agent1.capabilities == ["cap1"]
|
|
598
|
+
assert Agent2.capabilities == ["cap2"]
|
|
599
|
+
|
|
600
|
+
@pytest.mark.asyncio
|
|
601
|
+
async def test_multiple_instances_independent(self):
|
|
602
|
+
"""Test multiple instances of same agent are independent."""
|
|
603
|
+
@jarvis_agent(role="counter", capabilities=["count"])
|
|
604
|
+
class Counter:
|
|
605
|
+
def __init__(self):
|
|
606
|
+
self.count = 0
|
|
607
|
+
|
|
608
|
+
def run(self, data):
|
|
609
|
+
self.count += 1
|
|
610
|
+
return {"count": self.count}
|
|
611
|
+
|
|
612
|
+
agent1 = Counter()
|
|
613
|
+
agent2 = Counter()
|
|
614
|
+
|
|
615
|
+
await agent1.execute_task({"params": {}})
|
|
616
|
+
await agent1.execute_task({"params": {}})
|
|
617
|
+
|
|
618
|
+
result1 = await agent1.execute_task({"params": {}})
|
|
619
|
+
result2 = await agent2.execute_task({"params": {}})
|
|
620
|
+
|
|
621
|
+
assert result1["output"]["count"] == 3
|
|
622
|
+
assert result2["output"]["count"] == 1
|
|
File without changes
|
{jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.1.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|