jarviscore-framework 0.1.0__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.
- examples/calculator_agent_example.py +77 -0
- examples/multi_agent_workflow.py +132 -0
- examples/research_agent_example.py +76 -0
- jarviscore/__init__.py +54 -0
- jarviscore/cli/__init__.py +7 -0
- jarviscore/cli/__main__.py +33 -0
- jarviscore/cli/check.py +404 -0
- jarviscore/cli/smoketest.py +371 -0
- jarviscore/config/__init__.py +7 -0
- jarviscore/config/settings.py +128 -0
- jarviscore/core/__init__.py +7 -0
- jarviscore/core/agent.py +163 -0
- jarviscore/core/mesh.py +463 -0
- jarviscore/core/profile.py +64 -0
- jarviscore/docs/API_REFERENCE.md +932 -0
- jarviscore/docs/CONFIGURATION.md +753 -0
- jarviscore/docs/GETTING_STARTED.md +600 -0
- jarviscore/docs/TROUBLESHOOTING.md +424 -0
- jarviscore/docs/USER_GUIDE.md +983 -0
- jarviscore/execution/__init__.py +94 -0
- jarviscore/execution/code_registry.py +298 -0
- jarviscore/execution/generator.py +268 -0
- jarviscore/execution/llm.py +430 -0
- jarviscore/execution/repair.py +283 -0
- jarviscore/execution/result_handler.py +332 -0
- jarviscore/execution/sandbox.py +555 -0
- jarviscore/execution/search.py +281 -0
- jarviscore/orchestration/__init__.py +18 -0
- jarviscore/orchestration/claimer.py +101 -0
- jarviscore/orchestration/dependency.py +143 -0
- jarviscore/orchestration/engine.py +292 -0
- jarviscore/orchestration/status.py +96 -0
- jarviscore/p2p/__init__.py +23 -0
- jarviscore/p2p/broadcaster.py +353 -0
- jarviscore/p2p/coordinator.py +364 -0
- jarviscore/p2p/keepalive.py +361 -0
- jarviscore/p2p/swim_manager.py +290 -0
- jarviscore/profiles/__init__.py +6 -0
- jarviscore/profiles/autoagent.py +264 -0
- jarviscore/profiles/customagent.py +137 -0
- jarviscore_framework-0.1.0.dist-info/METADATA +136 -0
- jarviscore_framework-0.1.0.dist-info/RECORD +55 -0
- jarviscore_framework-0.1.0.dist-info/WHEEL +5 -0
- jarviscore_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- jarviscore_framework-0.1.0.dist-info/top_level.txt +3 -0
- tests/conftest.py +44 -0
- tests/test_agent.py +165 -0
- tests/test_autoagent.py +140 -0
- tests/test_autoagent_day4.py +186 -0
- tests/test_customagent.py +248 -0
- tests/test_integration.py +293 -0
- tests/test_llm_fallback.py +185 -0
- tests/test_mesh.py +356 -0
- tests/test_p2p_integration.py +375 -0
- tests/test_remote_sandbox.py +116 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for CustomAgent profile.
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
from jarviscore.profiles.customagent import CustomAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ValidCustomAgent(CustomAgent):
|
|
9
|
+
"""Valid CustomAgent with execute_task implementation."""
|
|
10
|
+
role = "test_custom"
|
|
11
|
+
capabilities = ["custom_testing"]
|
|
12
|
+
|
|
13
|
+
async def execute_task(self, task):
|
|
14
|
+
return {
|
|
15
|
+
"status": "success",
|
|
16
|
+
"output": f"Custom result for: {task.get('task', '')}",
|
|
17
|
+
"custom_field": "user_data"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoExecuteCustomAgent(CustomAgent):
|
|
22
|
+
"""CustomAgent without execute_task (should raise NotImplementedError)."""
|
|
23
|
+
role = "no_execute"
|
|
24
|
+
capabilities = ["testing"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestCustomAgentInitialization:
|
|
28
|
+
"""Test CustomAgent initialization."""
|
|
29
|
+
|
|
30
|
+
def test_valid_customagent_creation(self):
|
|
31
|
+
"""Test creating a valid CustomAgent."""
|
|
32
|
+
agent = ValidCustomAgent()
|
|
33
|
+
|
|
34
|
+
assert agent.role == "test_custom"
|
|
35
|
+
assert agent.capabilities == ["custom_testing"]
|
|
36
|
+
|
|
37
|
+
def test_customagent_with_custom_attributes(self):
|
|
38
|
+
"""Test CustomAgent with user-defined attributes."""
|
|
39
|
+
class APICustomAgent(CustomAgent):
|
|
40
|
+
role = "api_agent"
|
|
41
|
+
capabilities = ["api_calls"]
|
|
42
|
+
api_endpoint = "https://api.example.com"
|
|
43
|
+
api_key = "test-key-123"
|
|
44
|
+
|
|
45
|
+
async def execute_task(self, task):
|
|
46
|
+
return {"status": "success"}
|
|
47
|
+
|
|
48
|
+
agent = APICustomAgent()
|
|
49
|
+
|
|
50
|
+
assert agent.api_endpoint == "https://api.example.com"
|
|
51
|
+
assert agent.api_key == "test-key-123"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestCustomAgentSetup:
|
|
55
|
+
"""Test CustomAgent setup."""
|
|
56
|
+
|
|
57
|
+
@pytest.mark.asyncio
|
|
58
|
+
async def test_customagent_setup(self):
|
|
59
|
+
"""Test CustomAgent setup hook."""
|
|
60
|
+
agent = ValidCustomAgent()
|
|
61
|
+
await agent.setup()
|
|
62
|
+
|
|
63
|
+
# Should run without error
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_customagent_setup_with_framework_initialization(self):
|
|
67
|
+
"""Test CustomAgent setup with framework initialization."""
|
|
68
|
+
setup_called = []
|
|
69
|
+
|
|
70
|
+
class FrameworkCustomAgent(CustomAgent):
|
|
71
|
+
role = "framework_agent"
|
|
72
|
+
capabilities = ["framework"]
|
|
73
|
+
|
|
74
|
+
async def setup(self):
|
|
75
|
+
await super().setup()
|
|
76
|
+
# Simulate initializing a framework (e.g., LangChain, MCP)
|
|
77
|
+
setup_called.append("framework_initialized")
|
|
78
|
+
|
|
79
|
+
async def execute_task(self, task):
|
|
80
|
+
return {"status": "success"}
|
|
81
|
+
|
|
82
|
+
agent = FrameworkCustomAgent()
|
|
83
|
+
await agent.setup()
|
|
84
|
+
|
|
85
|
+
assert "framework_initialized" in setup_called
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestCustomAgentExecution:
|
|
89
|
+
"""Test CustomAgent task execution."""
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_execute_task_implementation(self):
|
|
93
|
+
"""Test CustomAgent execute_task with user implementation."""
|
|
94
|
+
agent = ValidCustomAgent()
|
|
95
|
+
|
|
96
|
+
task = {"task": "Process data"}
|
|
97
|
+
result = await agent.execute_task(task)
|
|
98
|
+
|
|
99
|
+
assert result["status"] == "success"
|
|
100
|
+
assert "Process data" in result["output"]
|
|
101
|
+
assert result["custom_field"] == "user_data"
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_execute_task_not_implemented_raises_error(self):
|
|
105
|
+
"""Test that CustomAgent without execute_task raises NotImplementedError."""
|
|
106
|
+
agent = NoExecuteCustomAgent()
|
|
107
|
+
|
|
108
|
+
task = {"task": "Should fail"}
|
|
109
|
+
|
|
110
|
+
with pytest.raises(NotImplementedError) as exc_info:
|
|
111
|
+
await agent.execute_task(task)
|
|
112
|
+
|
|
113
|
+
assert "must implement execute_task" in str(exc_info.value)
|
|
114
|
+
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_execute_task_with_optional_cost_tracking(self):
|
|
117
|
+
"""Test CustomAgent returning optional cost tracking fields."""
|
|
118
|
+
class CostTrackingAgent(CustomAgent):
|
|
119
|
+
role = "cost_tracker"
|
|
120
|
+
capabilities = ["tracking"]
|
|
121
|
+
|
|
122
|
+
async def execute_task(self, task):
|
|
123
|
+
return {
|
|
124
|
+
"status": "success",
|
|
125
|
+
"output": "result",
|
|
126
|
+
"tokens_used": 1500,
|
|
127
|
+
"cost_usd": 0.003
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
agent = CostTrackingAgent()
|
|
131
|
+
result = await agent.execute_task({"task": "Track costs"})
|
|
132
|
+
|
|
133
|
+
assert result["tokens_used"] == 1500
|
|
134
|
+
assert result["cost_usd"] == 0.003
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TestCustomAgentFrameworkIntegration:
|
|
138
|
+
"""Test CustomAgent with various framework integrations."""
|
|
139
|
+
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_langchain_integration_example(self):
|
|
142
|
+
"""Test CustomAgent simulating LangChain integration."""
|
|
143
|
+
class LangChainAgent(CustomAgent):
|
|
144
|
+
role = "langchain_agent"
|
|
145
|
+
capabilities = ["langchain"]
|
|
146
|
+
|
|
147
|
+
async def setup(self):
|
|
148
|
+
await super().setup()
|
|
149
|
+
# Simulate LangChain initialization
|
|
150
|
+
self.lc_agent = "mock_langchain_agent"
|
|
151
|
+
|
|
152
|
+
async def execute_task(self, task):
|
|
153
|
+
# Simulate LangChain execution
|
|
154
|
+
return {
|
|
155
|
+
"status": "success",
|
|
156
|
+
"output": f"LangChain result: {task['task']}"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
agent = LangChainAgent()
|
|
160
|
+
await agent.setup()
|
|
161
|
+
|
|
162
|
+
result = await agent.execute_task({"task": "Query database"})
|
|
163
|
+
|
|
164
|
+
assert result["status"] == "success"
|
|
165
|
+
assert "LangChain result" in result["output"]
|
|
166
|
+
|
|
167
|
+
@pytest.mark.asyncio
|
|
168
|
+
async def test_mcp_integration_example(self):
|
|
169
|
+
"""Test CustomAgent simulating MCP integration."""
|
|
170
|
+
class MCPAgent(CustomAgent):
|
|
171
|
+
role = "mcp_agent"
|
|
172
|
+
capabilities = ["mcp_tools"]
|
|
173
|
+
mcp_server_url = "stdio://./server.py"
|
|
174
|
+
|
|
175
|
+
async def setup(self):
|
|
176
|
+
await super().setup()
|
|
177
|
+
# Simulate MCP connection
|
|
178
|
+
self.mcp_client = "mock_mcp_client"
|
|
179
|
+
|
|
180
|
+
async def execute_task(self, task):
|
|
181
|
+
# Simulate MCP tool call
|
|
182
|
+
return {
|
|
183
|
+
"status": "success",
|
|
184
|
+
"data": {"tool": "executed", "params": task.get("params")}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
agent = MCPAgent()
|
|
188
|
+
await agent.setup()
|
|
189
|
+
|
|
190
|
+
result = await agent.execute_task({
|
|
191
|
+
"task": "Call MCP tool",
|
|
192
|
+
"params": {"arg1": "value1"}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
assert result["status"] == "success"
|
|
196
|
+
assert result["data"]["tool"] == "executed"
|
|
197
|
+
|
|
198
|
+
@pytest.mark.asyncio
|
|
199
|
+
async def test_raw_python_integration_example(self):
|
|
200
|
+
"""Test CustomAgent with raw Python logic."""
|
|
201
|
+
class DataProcessor(CustomAgent):
|
|
202
|
+
role = "processor"
|
|
203
|
+
capabilities = ["data_processing"]
|
|
204
|
+
|
|
205
|
+
async def execute_task(self, task):
|
|
206
|
+
# Pure Python logic
|
|
207
|
+
data = task.get("params", {}).get("data", [])
|
|
208
|
+
processed = [x * 2 for x in data]
|
|
209
|
+
return {
|
|
210
|
+
"status": "success",
|
|
211
|
+
"output": processed
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
agent = DataProcessor()
|
|
215
|
+
result = await agent.execute_task({
|
|
216
|
+
"task": "Double values",
|
|
217
|
+
"params": {"data": [1, 2, 3, 4, 5]}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
assert result["status"] == "success"
|
|
221
|
+
assert result["output"] == [2, 4, 6, 8, 10]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class TestCustomAgentInheritance:
|
|
225
|
+
"""Test CustomAgent inheritance from Profile and Agent."""
|
|
226
|
+
|
|
227
|
+
def test_customagent_inherits_agent_methods(self):
|
|
228
|
+
"""Test that CustomAgent inherits Agent methods."""
|
|
229
|
+
agent = ValidCustomAgent()
|
|
230
|
+
|
|
231
|
+
# Should have Agent methods
|
|
232
|
+
assert hasattr(agent, "can_handle")
|
|
233
|
+
assert hasattr(agent, "execute_task")
|
|
234
|
+
assert hasattr(agent, "setup")
|
|
235
|
+
assert hasattr(agent, "teardown")
|
|
236
|
+
|
|
237
|
+
def test_customagent_can_handle_tasks(self):
|
|
238
|
+
"""Test that CustomAgent can check task compatibility."""
|
|
239
|
+
agent = ValidCustomAgent()
|
|
240
|
+
|
|
241
|
+
task1 = {"role": "test_custom", "task": "Do something"}
|
|
242
|
+
assert agent.can_handle(task1) is True
|
|
243
|
+
|
|
244
|
+
task2 = {"capability": "custom_testing", "task": "Run tests"}
|
|
245
|
+
assert agent.can_handle(task2) is True
|
|
246
|
+
|
|
247
|
+
task3 = {"role": "different", "task": "Won't handle"}
|
|
248
|
+
assert agent.can_handle(task3) is False
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests demonstrating full framework usage.
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
from jarviscore import Mesh, AutoAgent, CustomAgent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Example agents for integration testing
|
|
9
|
+
class ScraperAgent(CustomAgent):
|
|
10
|
+
"""Web scraper agent using CustomAgent profile (mocked for testing)."""
|
|
11
|
+
role = "scraper"
|
|
12
|
+
capabilities = ["web_scraping", "data_extraction"]
|
|
13
|
+
|
|
14
|
+
async def execute_task(self, task):
|
|
15
|
+
# Mock scraper for integration testing
|
|
16
|
+
return {
|
|
17
|
+
"status": "success",
|
|
18
|
+
"output": {
|
|
19
|
+
"url": "example.com",
|
|
20
|
+
"products": ["Product A", "Product B", "Product C"]
|
|
21
|
+
},
|
|
22
|
+
"tokens_used": 0,
|
|
23
|
+
"cost_usd": 0.0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProcessorAgent(CustomAgent):
|
|
28
|
+
"""Data processor using CustomAgent profile."""
|
|
29
|
+
role = "processor"
|
|
30
|
+
capabilities = ["data_processing", "transformation"]
|
|
31
|
+
|
|
32
|
+
async def execute_task(self, task):
|
|
33
|
+
# Simulate data processing
|
|
34
|
+
data = task.get("params", {}).get("data", [])
|
|
35
|
+
processed = [x.upper() if isinstance(x, str) else x * 2 for x in data]
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
"status": "success",
|
|
39
|
+
"output": processed,
|
|
40
|
+
"tokens_used": 0, # No LLM usage
|
|
41
|
+
"cost_usd": 0.0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class StorageAgent(CustomAgent):
|
|
46
|
+
"""Storage agent for saving results."""
|
|
47
|
+
role = "storage"
|
|
48
|
+
capabilities = ["database", "file_storage"]
|
|
49
|
+
|
|
50
|
+
def __init__(self, *args, **kwargs):
|
|
51
|
+
super().__init__(*args, **kwargs)
|
|
52
|
+
self.stored_data = [] # Mock storage
|
|
53
|
+
|
|
54
|
+
async def execute_task(self, task):
|
|
55
|
+
data = task.get("params", {}).get("data")
|
|
56
|
+
self.stored_data.append(data)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"status": "success",
|
|
60
|
+
"output": f"Stored {len(self.stored_data)} record(s)",
|
|
61
|
+
"records_stored": len(self.stored_data)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestBasicIntegration:
|
|
66
|
+
"""Test basic framework integration."""
|
|
67
|
+
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_single_agent_workflow(self):
|
|
70
|
+
"""Test workflow with single agent."""
|
|
71
|
+
mesh = Mesh(mode="autonomous")
|
|
72
|
+
mesh.add(ScraperAgent)
|
|
73
|
+
|
|
74
|
+
await mesh.start()
|
|
75
|
+
|
|
76
|
+
results = await mesh.workflow("simple-scrape", [
|
|
77
|
+
{
|
|
78
|
+
"agent": "scraper",
|
|
79
|
+
"task": "Scrape example.com for product data"
|
|
80
|
+
}
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
assert len(results) == 1
|
|
84
|
+
assert results[0]["status"] == "success"
|
|
85
|
+
|
|
86
|
+
await mesh.stop()
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_multi_agent_workflow(self):
|
|
90
|
+
"""Test workflow with multiple agents."""
|
|
91
|
+
mesh = Mesh(mode="autonomous")
|
|
92
|
+
mesh.add(ScraperAgent)
|
|
93
|
+
mesh.add(ProcessorAgent)
|
|
94
|
+
mesh.add(StorageAgent)
|
|
95
|
+
|
|
96
|
+
await mesh.start()
|
|
97
|
+
|
|
98
|
+
results = await mesh.workflow("scrape-process-store", [
|
|
99
|
+
{
|
|
100
|
+
"agent": "scraper",
|
|
101
|
+
"task": "Scrape example.com"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"agent": "processor",
|
|
105
|
+
"task": "Process scraped data",
|
|
106
|
+
"params": {"data": ["hello", "world"]}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"agent": "storage",
|
|
110
|
+
"task": "Save processed data",
|
|
111
|
+
"params": {"data": ["HELLO", "WORLD"]}
|
|
112
|
+
}
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
assert len(results) == 3
|
|
116
|
+
assert all(r["status"] == "success" for r in results)
|
|
117
|
+
|
|
118
|
+
await mesh.stop()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestMixedProfileIntegration:
|
|
122
|
+
"""Test integration of AutoAgent and CustomAgent profiles."""
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_auto_and_custom_agents_together(self):
|
|
126
|
+
"""Test that AutoAgent and CustomAgent work together."""
|
|
127
|
+
mesh = Mesh(mode="autonomous")
|
|
128
|
+
|
|
129
|
+
# Add AutoAgent
|
|
130
|
+
scraper = mesh.add(ScraperAgent)
|
|
131
|
+
|
|
132
|
+
# Add CustomAgent
|
|
133
|
+
processor = mesh.add(ProcessorAgent)
|
|
134
|
+
|
|
135
|
+
await mesh.start()
|
|
136
|
+
|
|
137
|
+
# Verify both agents are registered
|
|
138
|
+
assert mesh.get_agent("scraper") == scraper
|
|
139
|
+
assert mesh.get_agent("processor") == processor
|
|
140
|
+
|
|
141
|
+
# Execute workflow using both
|
|
142
|
+
results = await mesh.workflow("mixed-workflow", [
|
|
143
|
+
{"agent": "scraper", "task": "Scrape data"},
|
|
144
|
+
{"agent": "processor", "task": "Process", "params": {"data": [1, 2, 3]}}
|
|
145
|
+
])
|
|
146
|
+
|
|
147
|
+
assert len(results) == 2
|
|
148
|
+
assert results[0]["status"] == "success"
|
|
149
|
+
assert results[1]["status"] == "success"
|
|
150
|
+
|
|
151
|
+
await mesh.stop()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestCapabilityRouting:
|
|
155
|
+
"""Test task routing by capabilities."""
|
|
156
|
+
|
|
157
|
+
@pytest.mark.asyncio
|
|
158
|
+
async def test_route_by_capability(self):
|
|
159
|
+
"""Test that tasks are routed by capability."""
|
|
160
|
+
mesh = Mesh(mode="autonomous")
|
|
161
|
+
mesh.add(ProcessorAgent)
|
|
162
|
+
|
|
163
|
+
await mesh.start()
|
|
164
|
+
|
|
165
|
+
# Route by capability instead of role
|
|
166
|
+
results = await mesh.workflow("capability-routing", [
|
|
167
|
+
{
|
|
168
|
+
"agent": "data_processing", # Capability, not role
|
|
169
|
+
"task": "Process this data",
|
|
170
|
+
"params": {"data": [10, 20, 30]}
|
|
171
|
+
}
|
|
172
|
+
])
|
|
173
|
+
|
|
174
|
+
assert len(results) == 1
|
|
175
|
+
assert results[0]["status"] == "success"
|
|
176
|
+
assert results[0]["output"] == [20, 40, 60] # Doubled
|
|
177
|
+
|
|
178
|
+
await mesh.stop()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestAgentStateAndCustomization:
|
|
182
|
+
"""Test agent state management and customization."""
|
|
183
|
+
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_agent_maintains_state_across_tasks(self):
|
|
186
|
+
"""Test that CustomAgent can maintain state across multiple tasks."""
|
|
187
|
+
mesh = Mesh(mode="autonomous")
|
|
188
|
+
storage = mesh.add(StorageAgent)
|
|
189
|
+
|
|
190
|
+
await mesh.start()
|
|
191
|
+
|
|
192
|
+
# Execute multiple storage tasks
|
|
193
|
+
await mesh.workflow("multi-store", [
|
|
194
|
+
{"agent": "storage", "task": "Store item 1", "params": {"data": "item1"}},
|
|
195
|
+
{"agent": "storage", "task": "Store item 2", "params": {"data": "item2"}},
|
|
196
|
+
{"agent": "storage", "task": "Store item 3", "params": {"data": "item3"}},
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
# Verify storage agent maintained state
|
|
200
|
+
assert len(storage.stored_data) == 3
|
|
201
|
+
assert storage.stored_data == ["item1", "item2", "item3"]
|
|
202
|
+
|
|
203
|
+
await mesh.stop()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TestErrorHandling:
|
|
207
|
+
"""Test error handling in workflows."""
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_missing_agent_error(self):
|
|
211
|
+
"""Test workflow with missing agent."""
|
|
212
|
+
mesh = Mesh(mode="autonomous")
|
|
213
|
+
mesh.add(ProcessorAgent)
|
|
214
|
+
|
|
215
|
+
await mesh.start()
|
|
216
|
+
|
|
217
|
+
results = await mesh.workflow("missing-agent", [
|
|
218
|
+
{
|
|
219
|
+
"agent": "nonexistent_agent",
|
|
220
|
+
"task": "Should fail"
|
|
221
|
+
}
|
|
222
|
+
])
|
|
223
|
+
|
|
224
|
+
assert len(results) == 1
|
|
225
|
+
assert results[0]["status"] == "failure"
|
|
226
|
+
assert "No agent found" in results[0]["error"]
|
|
227
|
+
|
|
228
|
+
await mesh.stop()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestRealWorldScenario:
|
|
232
|
+
"""Test real-world usage scenarios."""
|
|
233
|
+
|
|
234
|
+
@pytest.mark.asyncio
|
|
235
|
+
async def test_data_pipeline_scenario(self):
|
|
236
|
+
"""Test complete data pipeline: scrape → process → store."""
|
|
237
|
+
mesh = Mesh(mode="autonomous")
|
|
238
|
+
mesh.add(ScraperAgent)
|
|
239
|
+
mesh.add(ProcessorAgent)
|
|
240
|
+
storage = mesh.add(StorageAgent)
|
|
241
|
+
|
|
242
|
+
await mesh.start()
|
|
243
|
+
|
|
244
|
+
# Simulate data pipeline
|
|
245
|
+
results = await mesh.workflow("data-pipeline", [
|
|
246
|
+
{
|
|
247
|
+
"agent": "scraper",
|
|
248
|
+
"task": "Scrape example.com for products"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"agent": "processor",
|
|
252
|
+
"task": "Clean and normalize product data",
|
|
253
|
+
"params": {"data": ["product-a", "product-b", "product-c"]},
|
|
254
|
+
"depends_on": [0]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"agent": "storage",
|
|
258
|
+
"task": "Save to database",
|
|
259
|
+
"params": {"data": ["PRODUCT-A", "PRODUCT-B", "PRODUCT-C"]},
|
|
260
|
+
"depends_on": [1]
|
|
261
|
+
}
|
|
262
|
+
])
|
|
263
|
+
|
|
264
|
+
# Verify all steps succeeded
|
|
265
|
+
assert len(results) == 3
|
|
266
|
+
assert all(r["status"] == "success" for r in results)
|
|
267
|
+
|
|
268
|
+
# Verify data was stored
|
|
269
|
+
assert len(storage.stored_data) == 1
|
|
270
|
+
assert storage.stored_data[0] == ["PRODUCT-A", "PRODUCT-B", "PRODUCT-C"]
|
|
271
|
+
|
|
272
|
+
await mesh.stop()
|
|
273
|
+
|
|
274
|
+
@pytest.mark.asyncio
|
|
275
|
+
async def test_agent_discovery_by_capability(self):
|
|
276
|
+
"""Test that mesh can discover agents by capability."""
|
|
277
|
+
mesh = Mesh(mode="autonomous")
|
|
278
|
+
mesh.add(ScraperAgent)
|
|
279
|
+
mesh.add(ProcessorAgent)
|
|
280
|
+
mesh.add(StorageAgent)
|
|
281
|
+
|
|
282
|
+
# Test capability indexing
|
|
283
|
+
scrapers = mesh.get_agents_by_capability("web_scraping")
|
|
284
|
+
assert len(scrapers) == 1
|
|
285
|
+
assert scrapers[0].role == "scraper"
|
|
286
|
+
|
|
287
|
+
processors = mesh.get_agents_by_capability("data_processing")
|
|
288
|
+
assert len(processors) == 1
|
|
289
|
+
assert processors[0].role == "processor"
|
|
290
|
+
|
|
291
|
+
storage_agents = mesh.get_agents_by_capability("database")
|
|
292
|
+
assert len(storage_agents) == 1
|
|
293
|
+
assert storage_agents[0].role == "storage"
|