memra 0.2.13__py3-none-any.whl → 0.2.15__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.
- memra/cli.py +322 -51
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/METADATA +1 -1
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/RECORD +7 -61
- memra-0.2.15.dist-info/top_level.txt +1 -0
- memra-0.2.13.dist-info/top_level.txt +0 -4
- memra-ops/app.py +0 -808
- memra-ops/config/config.py +0 -25
- memra-ops/config.py +0 -34
- memra-ops/logic/__init__.py +0 -1
- memra-ops/logic/file_tools.py +0 -43
- memra-ops/logic/invoice_tools.py +0 -668
- memra-ops/logic/invoice_tools_fix.py +0 -66
- memra-ops/mcp_bridge_server.py +0 -1178
- memra-ops/scripts/check_database.py +0 -37
- memra-ops/scripts/clear_database.py +0 -48
- memra-ops/scripts/monitor_database.py +0 -67
- memra-ops/scripts/release.py +0 -133
- memra-ops/scripts/reset_database.py +0 -65
- memra-ops/scripts/start_memra.py +0 -334
- memra-ops/scripts/stop_memra.py +0 -132
- memra-ops/server_tool_registry.py +0 -190
- memra-ops/tests/test_llm_text_to_sql.py +0 -115
- memra-ops/tests/test_llm_vs_pattern.py +0 -130
- memra-ops/tests/test_mcp_schema_aware.py +0 -124
- memra-ops/tests/test_schema_aware_sql.py +0 -139
- memra-ops/tests/test_schema_aware_sql_simple.py +0 -66
- memra-ops/tests/test_text_to_sql_demo.py +0 -140
- memra-ops/tools/mcp_bridge_server.py +0 -851
- memra-sdk/examples/accounts_payable.py +0 -215
- memra-sdk/examples/accounts_payable_client.py +0 -217
- memra-sdk/examples/accounts_payable_mcp.py +0 -200
- memra-sdk/examples/ask_questions.py +0 -123
- memra-sdk/examples/invoice_processing.py +0 -116
- memra-sdk/examples/propane_delivery.py +0 -87
- memra-sdk/examples/simple_text_to_sql.py +0 -158
- memra-sdk/memra/__init__.py +0 -31
- memra-sdk/memra/discovery.py +0 -15
- memra-sdk/memra/discovery_client.py +0 -49
- memra-sdk/memra/execution.py +0 -481
- memra-sdk/memra/models.py +0 -99
- memra-sdk/memra/tool_registry.py +0 -343
- memra-sdk/memra/tool_registry_client.py +0 -106
- memra-sdk/scripts/release.py +0 -133
- memra-sdk/setup.py +0 -52
- memra-workflows/accounts_payable/accounts_payable.py +0 -215
- memra-workflows/accounts_payable/accounts_payable_client.py +0 -216
- memra-workflows/accounts_payable/accounts_payable_mcp.py +0 -200
- memra-workflows/accounts_payable/accounts_payable_smart.py +0 -221
- memra-workflows/invoice_processing/invoice_processing.py +0 -116
- memra-workflows/invoice_processing/smart_invoice_processor.py +0 -220
- memra-workflows/logic/__init__.py +0 -1
- memra-workflows/logic/file_tools.py +0 -50
- memra-workflows/logic/invoice_tools.py +0 -501
- memra-workflows/logic/propane_agents.py +0 -52
- memra-workflows/mcp_bridge_server.py +0 -230
- memra-workflows/propane_delivery/propane_delivery.py +0 -87
- memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +0 -208
- memra-workflows/text_to_sql/complete_text_to_sql_system.py +0 -266
- memra-workflows/text_to_sql/file_discovery_demo.py +0 -156
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/LICENSE +0 -0
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/WHEEL +0 -0
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/entry_points.txt +0 -0
memra-sdk/memra/execution.py
DELETED
@@ -1,481 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
import logging
|
3
|
-
from typing import Dict, Any, List, Optional
|
4
|
-
from .models import Department, Agent, DepartmentResult, ExecutionTrace, DepartmentAudit
|
5
|
-
from .tool_registry import ToolRegistry
|
6
|
-
from .tool_registry_client import ToolRegistryClient
|
7
|
-
|
8
|
-
logger = logging.getLogger(__name__)
|
9
|
-
|
10
|
-
class ExecutionEngine:
|
11
|
-
"""Engine that executes department workflows by coordinating agents and tools"""
|
12
|
-
|
13
|
-
def __init__(self):
|
14
|
-
self.tool_registry = ToolRegistry()
|
15
|
-
self.api_client = ToolRegistryClient()
|
16
|
-
self.last_execution_audit: Optional[DepartmentAudit] = None
|
17
|
-
|
18
|
-
def execute_department(self, department: Department, input_data: Dict[str, Any]) -> DepartmentResult:
|
19
|
-
"""Execute a department workflow"""
|
20
|
-
start_time = time.time()
|
21
|
-
trace = ExecutionTrace()
|
22
|
-
|
23
|
-
try:
|
24
|
-
print(f"\n🏢 Starting {department.name} Department")
|
25
|
-
print(f"📋 Mission: {department.mission}")
|
26
|
-
print(f"👥 Team: {', '.join([agent.role for agent in department.agents])}")
|
27
|
-
if department.manager_agent:
|
28
|
-
print(f"👔 Manager: {department.manager_agent.role}")
|
29
|
-
print(f"🔄 Workflow: {' → '.join(department.workflow_order)}")
|
30
|
-
print("=" * 60)
|
31
|
-
|
32
|
-
logger.info(f"Starting execution of department: {department.name}")
|
33
|
-
|
34
|
-
# Initialize execution context
|
35
|
-
context = {
|
36
|
-
"input": input_data,
|
37
|
-
"department_context": department.context or {},
|
38
|
-
"results": {}
|
39
|
-
}
|
40
|
-
|
41
|
-
# Execute agents in workflow order
|
42
|
-
for i, agent_role in enumerate(department.workflow_order, 1):
|
43
|
-
print(f"\n🔄 Step {i}/{len(department.workflow_order)}: {agent_role}")
|
44
|
-
|
45
|
-
agent = self._find_agent_by_role(department, agent_role)
|
46
|
-
if not agent:
|
47
|
-
error_msg = f"Agent with role '{agent_role}' not found in department"
|
48
|
-
print(f"❌ Error: {error_msg}")
|
49
|
-
trace.errors.append(error_msg)
|
50
|
-
return DepartmentResult(
|
51
|
-
success=False,
|
52
|
-
error=error_msg,
|
53
|
-
trace=trace
|
54
|
-
)
|
55
|
-
|
56
|
-
# Execute agent
|
57
|
-
agent_start = time.time()
|
58
|
-
result = self._execute_agent(agent, context, trace)
|
59
|
-
agent_duration = time.time() - agent_start
|
60
|
-
|
61
|
-
trace.agents_executed.append(agent.role)
|
62
|
-
trace.execution_times[agent.role] = agent_duration
|
63
|
-
|
64
|
-
if not result.get("success", False):
|
65
|
-
# Try fallback if available
|
66
|
-
if department.manager_agent and agent.role in (department.manager_agent.fallback_agents or {}):
|
67
|
-
fallback_role = department.manager_agent.fallback_agents[agent.role]
|
68
|
-
print(f"🔄 {department.manager_agent.role}: Let me try {fallback_role} as backup for {agent.role}")
|
69
|
-
fallback_agent = self._find_agent_by_role(department, fallback_role)
|
70
|
-
if fallback_agent:
|
71
|
-
logger.info(f"Trying fallback agent: {fallback_role}")
|
72
|
-
result = self._execute_agent(fallback_agent, context, trace)
|
73
|
-
trace.agents_executed.append(fallback_agent.role)
|
74
|
-
|
75
|
-
if not result.get("success", False):
|
76
|
-
error_msg = f"Agent {agent.role} failed: {result.get('error', 'Unknown error')}"
|
77
|
-
print(f"❌ Workflow stopped: {error_msg}")
|
78
|
-
trace.errors.append(error_msg)
|
79
|
-
return DepartmentResult(
|
80
|
-
success=False,
|
81
|
-
error=error_msg,
|
82
|
-
trace=trace
|
83
|
-
)
|
84
|
-
|
85
|
-
# Store result for next agent
|
86
|
-
context["results"][agent.output_key] = result.get("data")
|
87
|
-
print(f"✅ Step {i} completed in {agent_duration:.1f}s")
|
88
|
-
|
89
|
-
# Execute manager agent for final validation if present
|
90
|
-
if department.manager_agent:
|
91
|
-
print(f"\n🔍 Final Review Phase")
|
92
|
-
manager_start = time.time()
|
93
|
-
|
94
|
-
# Prepare manager input with all workflow results
|
95
|
-
manager_input = {
|
96
|
-
"workflow_results": context["results"],
|
97
|
-
"department_context": context["department_context"]
|
98
|
-
}
|
99
|
-
|
100
|
-
# Add connection if available
|
101
|
-
if "connection" in context["input"]:
|
102
|
-
manager_input["connection"] = context["input"]["connection"]
|
103
|
-
|
104
|
-
# Execute manager validation
|
105
|
-
manager_result = self._execute_manager_validation(department.manager_agent, manager_input, trace)
|
106
|
-
manager_duration = time.time() - manager_start
|
107
|
-
|
108
|
-
trace.agents_executed.append(department.manager_agent.role)
|
109
|
-
trace.execution_times[department.manager_agent.role] = manager_duration
|
110
|
-
|
111
|
-
# Store manager validation results
|
112
|
-
context["results"][department.manager_agent.output_key] = manager_result.get("data")
|
113
|
-
|
114
|
-
# Check if manager validation failed
|
115
|
-
if not manager_result.get("success", False):
|
116
|
-
error_msg = f"Manager validation failed: {manager_result.get('error', 'Unknown error')}"
|
117
|
-
print(f"❌ {error_msg}")
|
118
|
-
trace.errors.append(error_msg)
|
119
|
-
return DepartmentResult(
|
120
|
-
success=False,
|
121
|
-
error=error_msg,
|
122
|
-
trace=trace
|
123
|
-
)
|
124
|
-
|
125
|
-
print(f"✅ Manager review completed in {manager_duration:.1f}s")
|
126
|
-
|
127
|
-
# Create audit record
|
128
|
-
total_duration = time.time() - start_time
|
129
|
-
self.last_execution_audit = DepartmentAudit(
|
130
|
-
agents_run=trace.agents_executed,
|
131
|
-
tools_invoked=trace.tools_invoked,
|
132
|
-
duration_seconds=total_duration
|
133
|
-
)
|
134
|
-
|
135
|
-
print(f"\n🎉 {department.name} Department workflow completed!")
|
136
|
-
print(f"⏱️ Total time: {total_duration:.1f}s")
|
137
|
-
print("=" * 60)
|
138
|
-
|
139
|
-
return DepartmentResult(
|
140
|
-
success=True,
|
141
|
-
data=context["results"],
|
142
|
-
trace=trace
|
143
|
-
)
|
144
|
-
|
145
|
-
except Exception as e:
|
146
|
-
print(f"💥 Unexpected error in {department.name} Department: {str(e)}")
|
147
|
-
logger.error(f"Execution failed: {str(e)}")
|
148
|
-
trace.errors.append(str(e))
|
149
|
-
return DepartmentResult(
|
150
|
-
success=False,
|
151
|
-
error=str(e),
|
152
|
-
trace=trace
|
153
|
-
)
|
154
|
-
|
155
|
-
def _find_agent_by_role(self, department: Department, role: str) -> Optional[Agent]:
|
156
|
-
"""Find an agent by role in the department"""
|
157
|
-
for agent in department.agents:
|
158
|
-
if agent.role == role:
|
159
|
-
return agent
|
160
|
-
return None
|
161
|
-
|
162
|
-
def _execute_agent(self, agent: Agent, context: Dict[str, Any], trace: ExecutionTrace) -> Dict[str, Any]:
|
163
|
-
"""Execute a single agent"""
|
164
|
-
print(f"\n👤 {agent.role}: Hi! I'm starting my work now...")
|
165
|
-
logger.info(f"Executing agent: {agent.role}")
|
166
|
-
|
167
|
-
try:
|
168
|
-
# Show what the agent is thinking about
|
169
|
-
print(f"💭 {agent.role}: My job is to {agent.job.lower()}")
|
170
|
-
|
171
|
-
# Prepare input data for agent
|
172
|
-
agent_input = {}
|
173
|
-
for key in agent.input_keys:
|
174
|
-
if key in context["input"]:
|
175
|
-
agent_input[key] = context["input"][key]
|
176
|
-
print(f"📥 {agent.role}: I received '{key}' as input")
|
177
|
-
elif key in context["results"]:
|
178
|
-
agent_input[key] = context["results"][key]
|
179
|
-
print(f"📥 {agent.role}: I got '{key}' from a previous agent")
|
180
|
-
else:
|
181
|
-
print(f"🤔 {agent.role}: Hmm, I'm missing input '{key}' but I'll try to work without it")
|
182
|
-
logger.warning(f"Missing input key '{key}' for agent {agent.role}")
|
183
|
-
|
184
|
-
# Always include connection string if available (for database tools)
|
185
|
-
if "connection" in context["input"]:
|
186
|
-
agent_input["connection"] = context["input"]["connection"]
|
187
|
-
|
188
|
-
# Execute agent's tools
|
189
|
-
result_data = {}
|
190
|
-
tools_with_real_work = []
|
191
|
-
tools_with_mock_work = []
|
192
|
-
|
193
|
-
print(f"🔧 {agent.role}: I need to use {len(agent.tools)} tool(s) to complete my work...")
|
194
|
-
|
195
|
-
for i, tool_spec in enumerate(agent.tools, 1):
|
196
|
-
tool_name = tool_spec["name"] if isinstance(tool_spec, dict) else tool_spec.name
|
197
|
-
hosted_by = tool_spec.get("hosted_by", "memra") if isinstance(tool_spec, dict) else tool_spec.hosted_by
|
198
|
-
|
199
|
-
print(f"⚡ {agent.role}: Using tool {i}/{len(agent.tools)}: {tool_name}")
|
200
|
-
|
201
|
-
trace.tools_invoked.append(tool_name)
|
202
|
-
|
203
|
-
# Get tool from registry and execute
|
204
|
-
print(f"🔍 {agent.role}: Tool {tool_name} is hosted by: {hosted_by}")
|
205
|
-
if hosted_by == "memra":
|
206
|
-
# Use API client for server-hosted tools
|
207
|
-
print(f"🌐 {agent.role}: Using API client for {tool_name}")
|
208
|
-
config_to_pass = tool_spec.get("config") if isinstance(tool_spec, dict) else tool_spec.config
|
209
|
-
tool_result = self.api_client.execute_tool(
|
210
|
-
tool_name,
|
211
|
-
hosted_by,
|
212
|
-
agent_input,
|
213
|
-
config_to_pass
|
214
|
-
)
|
215
|
-
else:
|
216
|
-
# Use local registry for MCP and other local tools
|
217
|
-
print(f"🏠 {agent.role}: Using local registry for {tool_name}")
|
218
|
-
config_to_pass = tool_spec.get("config") if isinstance(tool_spec, dict) else tool_spec.config
|
219
|
-
|
220
|
-
# For MCP tools, merge department context MCP configuration
|
221
|
-
if hosted_by == "mcp":
|
222
|
-
mcp_config = {}
|
223
|
-
dept_context = context.get("department_context", {})
|
224
|
-
if "mcp_bridge_url" in dept_context:
|
225
|
-
mcp_config["bridge_url"] = dept_context["mcp_bridge_url"]
|
226
|
-
if "mcp_bridge_secret" in dept_context:
|
227
|
-
mcp_config["bridge_secret"] = dept_context["mcp_bridge_secret"]
|
228
|
-
|
229
|
-
# Merge with tool-specific config if it exists
|
230
|
-
if config_to_pass:
|
231
|
-
mcp_config.update(config_to_pass)
|
232
|
-
config_to_pass = mcp_config
|
233
|
-
|
234
|
-
print(f"🔧 {agent.role}: Config for {tool_name}: {config_to_pass}")
|
235
|
-
tool_result = self.tool_registry.execute_tool(
|
236
|
-
tool_name,
|
237
|
-
hosted_by,
|
238
|
-
agent_input,
|
239
|
-
config_to_pass
|
240
|
-
)
|
241
|
-
|
242
|
-
if not tool_result.get("success", False):
|
243
|
-
print(f"😟 {agent.role}: Oh no! Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}")
|
244
|
-
return {
|
245
|
-
"success": False,
|
246
|
-
"error": f"Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}"
|
247
|
-
}
|
248
|
-
|
249
|
-
# Check if this tool did real work or mock work
|
250
|
-
tool_data = tool_result.get("data", {})
|
251
|
-
if self._is_real_work(tool_name, tool_data):
|
252
|
-
tools_with_real_work.append(tool_name)
|
253
|
-
print(f"✅ {agent.role}: Great! {tool_name} did real work and gave me useful results")
|
254
|
-
else:
|
255
|
-
tools_with_mock_work.append(tool_name)
|
256
|
-
print(f"🔄 {agent.role}: {tool_name} gave me simulated results (that's okay for testing)")
|
257
|
-
|
258
|
-
result_data.update(tool_data)
|
259
|
-
|
260
|
-
# Add metadata about real vs mock work
|
261
|
-
result_data["_memra_metadata"] = {
|
262
|
-
"agent_role": agent.role,
|
263
|
-
"tools_real_work": tools_with_real_work,
|
264
|
-
"tools_mock_work": tools_with_mock_work,
|
265
|
-
"work_quality": "real" if tools_with_real_work else "mock"
|
266
|
-
}
|
267
|
-
|
268
|
-
# Agent reports completion
|
269
|
-
if tools_with_real_work:
|
270
|
-
print(f"🎉 {agent.role}: Perfect! I completed my work with real data processing")
|
271
|
-
else:
|
272
|
-
print(f"📝 {agent.role}: I finished my work, but used simulated data (still learning!)")
|
273
|
-
|
274
|
-
print(f"📤 {agent.role}: Passing my results to the next agent via '{agent.output_key}'")
|
275
|
-
|
276
|
-
return {
|
277
|
-
"success": True,
|
278
|
-
"data": result_data
|
279
|
-
}
|
280
|
-
|
281
|
-
except Exception as e:
|
282
|
-
print(f"😰 {agent.role}: I encountered an error and couldn't complete my work: {str(e)}")
|
283
|
-
logger.error(f"Agent {agent.role} execution failed: {str(e)}")
|
284
|
-
return {
|
285
|
-
"success": False,
|
286
|
-
"error": str(e)
|
287
|
-
}
|
288
|
-
|
289
|
-
def _is_real_work(self, tool_name: str, tool_data: Dict[str, Any]) -> bool:
|
290
|
-
"""Determine if a tool did real work or returned mock data"""
|
291
|
-
|
292
|
-
# Check for specific indicators of real work
|
293
|
-
if tool_name == "PDFProcessor":
|
294
|
-
# Real work if it has actual image paths and file size
|
295
|
-
return (
|
296
|
-
"metadata" in tool_data and
|
297
|
-
"file_size" in tool_data["metadata"] and
|
298
|
-
tool_data["metadata"]["file_size"] > 1000 and # Real file size
|
299
|
-
"pages" in tool_data and
|
300
|
-
len(tool_data["pages"]) > 0 and
|
301
|
-
"image_path" in tool_data["pages"][0]
|
302
|
-
)
|
303
|
-
|
304
|
-
elif tool_name == "InvoiceExtractionWorkflow":
|
305
|
-
# Real work if it has actual extracted data with specific vendor info
|
306
|
-
return (
|
307
|
-
"headerSection" in tool_data and
|
308
|
-
"vendorName" in tool_data["headerSection"] and
|
309
|
-
tool_data["headerSection"]["vendorName"] not in ["", "UNKNOWN", "Sample Vendor"] and
|
310
|
-
"chargesSummary" in tool_data and
|
311
|
-
"memra_checksum" in tool_data["chargesSummary"]
|
312
|
-
)
|
313
|
-
|
314
|
-
elif tool_name == "DatabaseQueryTool":
|
315
|
-
# Real work if it loaded the actual schema file (more than 3 columns)
|
316
|
-
return (
|
317
|
-
"columns" in tool_data and
|
318
|
-
len(tool_data["columns"]) > 3
|
319
|
-
)
|
320
|
-
|
321
|
-
elif tool_name == "DataValidator":
|
322
|
-
# Real work if it actually validated real data with meaningful validation
|
323
|
-
return (
|
324
|
-
"validation_errors" in tool_data and
|
325
|
-
isinstance(tool_data["validation_errors"], list) and
|
326
|
-
"is_valid" in tool_data and
|
327
|
-
# Check if it's validating real extracted data (not just mock data)
|
328
|
-
len(str(tool_data)) > 100 and # Real validation results are more substantial
|
329
|
-
not tool_data.get("_mock", False) # Not mock data
|
330
|
-
)
|
331
|
-
|
332
|
-
elif tool_name == "PostgresInsert":
|
333
|
-
# Real work if it successfully inserted into a real database
|
334
|
-
return (
|
335
|
-
"success" in tool_data and
|
336
|
-
tool_data["success"] == True and
|
337
|
-
"record_id" in tool_data and
|
338
|
-
isinstance(tool_data["record_id"], int) and # Real DB returns integer IDs
|
339
|
-
"database_table" in tool_data and # Real implementation includes table name
|
340
|
-
not tool_data.get("_mock", False) # Not mock data
|
341
|
-
)
|
342
|
-
|
343
|
-
elif tool_name == "FileDiscovery":
|
344
|
-
# Real work if it actually discovered files in a real directory
|
345
|
-
return (
|
346
|
-
"files" in tool_data and
|
347
|
-
isinstance(tool_data["files"], list) and
|
348
|
-
"directory" in tool_data and
|
349
|
-
tool_data.get("success", False) == True
|
350
|
-
)
|
351
|
-
|
352
|
-
elif tool_name == "FileCopy":
|
353
|
-
# Real work if it actually copied a file
|
354
|
-
return (
|
355
|
-
"destination_path" in tool_data and
|
356
|
-
"source_path" in tool_data and
|
357
|
-
tool_data.get("success", False) == True and
|
358
|
-
tool_data.get("operation") == "copy_completed"
|
359
|
-
)
|
360
|
-
|
361
|
-
elif tool_name == "TextToSQL":
|
362
|
-
# Real work if it actually executed SQL and returned real results
|
363
|
-
return (
|
364
|
-
"generated_sql" in tool_data and
|
365
|
-
"results" in tool_data and
|
366
|
-
isinstance(tool_data["results"], list) and
|
367
|
-
tool_data.get("success", False) == True and
|
368
|
-
not tool_data.get("_mock", False) # Not mock data
|
369
|
-
)
|
370
|
-
|
371
|
-
# Default to mock work
|
372
|
-
return False
|
373
|
-
|
374
|
-
def get_last_audit(self) -> Optional[DepartmentAudit]:
|
375
|
-
"""Get audit information from the last execution"""
|
376
|
-
return self.last_execution_audit
|
377
|
-
|
378
|
-
def _execute_manager_validation(self, manager_agent: Agent, manager_input: Dict[str, Any], trace: ExecutionTrace) -> Dict[str, Any]:
|
379
|
-
"""Execute manager agent to validate workflow results"""
|
380
|
-
print(f"\n👔 {manager_agent.role}: Time for me to review everyone's work...")
|
381
|
-
logger.info(f"Manager {manager_agent.role} validating workflow results")
|
382
|
-
|
383
|
-
try:
|
384
|
-
# Analyze workflow results for real vs mock work
|
385
|
-
workflow_analysis = self._analyze_workflow_quality(manager_input["workflow_results"])
|
386
|
-
|
387
|
-
print(f"🔍 {manager_agent.role}: Let me analyze what each agent accomplished...")
|
388
|
-
|
389
|
-
# Prepare validation report
|
390
|
-
validation_report = {
|
391
|
-
"workflow_analysis": workflow_analysis,
|
392
|
-
"validation_status": "pass" if workflow_analysis["overall_quality"] == "real" else "fail",
|
393
|
-
"recommendations": [],
|
394
|
-
"agent_performance": {}
|
395
|
-
}
|
396
|
-
|
397
|
-
# Analyze each agent's performance
|
398
|
-
for result_key, result_data in manager_input["workflow_results"].items():
|
399
|
-
if isinstance(result_data, dict) and "_memra_metadata" in result_data:
|
400
|
-
metadata = result_data["_memra_metadata"]
|
401
|
-
agent_role = metadata["agent_role"]
|
402
|
-
|
403
|
-
if metadata["work_quality"] == "real":
|
404
|
-
print(f"👍 {manager_agent.role}: {agent_role} did excellent real work!")
|
405
|
-
else:
|
406
|
-
print(f"📋 {manager_agent.role}: {agent_role} completed their tasks but with simulated data")
|
407
|
-
|
408
|
-
validation_report["agent_performance"][agent_role] = {
|
409
|
-
"work_quality": metadata["work_quality"],
|
410
|
-
"tools_real_work": metadata["tools_real_work"],
|
411
|
-
"tools_mock_work": metadata["tools_mock_work"],
|
412
|
-
"status": "completed_real_work" if metadata["work_quality"] == "real" else "completed_mock_work"
|
413
|
-
}
|
414
|
-
|
415
|
-
# Add recommendations for mock work
|
416
|
-
if metadata["work_quality"] == "mock":
|
417
|
-
recommendation = f"Agent {agent_role} performed mock work - implement real {', '.join(metadata['tools_mock_work'])} functionality"
|
418
|
-
validation_report["recommendations"].append(recommendation)
|
419
|
-
print(f"💡 {manager_agent.role}: I recommend upgrading {agent_role}'s tools for production")
|
420
|
-
|
421
|
-
# Overall workflow validation
|
422
|
-
if workflow_analysis["overall_quality"] == "real":
|
423
|
-
validation_report["summary"] = "Workflow completed successfully with real data processing"
|
424
|
-
print(f"🎯 {manager_agent.role}: Excellent! This workflow is production-ready")
|
425
|
-
elif workflow_analysis["overall_quality"].startswith("mixed"):
|
426
|
-
validation_report["summary"] = "Workflow completed with mixed real and simulated data"
|
427
|
-
print(f"⚖️ {manager_agent.role}: Good progress! Some agents are production-ready, others need work")
|
428
|
-
else:
|
429
|
-
validation_report["summary"] = "Workflow completed but with mock/simulated data - production readiness requires real implementations"
|
430
|
-
print(f"🚧 {manager_agent.role}: This workflow needs more development before production use")
|
431
|
-
|
432
|
-
real_percentage = workflow_analysis["real_work_percentage"]
|
433
|
-
print(f"📊 {manager_agent.role}: Overall assessment: {real_percentage:.0f}% of agents did real work")
|
434
|
-
|
435
|
-
return {
|
436
|
-
"success": True,
|
437
|
-
"data": validation_report
|
438
|
-
}
|
439
|
-
|
440
|
-
except Exception as e:
|
441
|
-
print(f"😰 {manager_agent.role}: I had trouble analyzing the workflow: {str(e)}")
|
442
|
-
logger.error(f"Manager validation failed: {str(e)}")
|
443
|
-
return {
|
444
|
-
"success": False,
|
445
|
-
"error": str(e)
|
446
|
-
}
|
447
|
-
|
448
|
-
def _analyze_workflow_quality(self, workflow_results: Dict[str, Any]) -> Dict[str, Any]:
|
449
|
-
"""Analyze the overall quality of workflow execution"""
|
450
|
-
|
451
|
-
total_agents = 0
|
452
|
-
real_work_agents = 0
|
453
|
-
mock_work_agents = 0
|
454
|
-
|
455
|
-
for result_key, result_data in workflow_results.items():
|
456
|
-
if isinstance(result_data, dict) and "_memra_metadata" in result_data:
|
457
|
-
metadata = result_data["_memra_metadata"]
|
458
|
-
total_agents += 1
|
459
|
-
|
460
|
-
if metadata["work_quality"] == "real":
|
461
|
-
real_work_agents += 1
|
462
|
-
else:
|
463
|
-
mock_work_agents += 1
|
464
|
-
|
465
|
-
# Determine overall quality
|
466
|
-
if real_work_agents > 0 and mock_work_agents == 0:
|
467
|
-
overall_quality = "real"
|
468
|
-
elif real_work_agents > mock_work_agents:
|
469
|
-
overall_quality = "mixed_mostly_real"
|
470
|
-
elif real_work_agents > 0:
|
471
|
-
overall_quality = "mixed_mostly_mock"
|
472
|
-
else:
|
473
|
-
overall_quality = "mock"
|
474
|
-
|
475
|
-
return {
|
476
|
-
"total_agents": total_agents,
|
477
|
-
"real_work_agents": real_work_agents,
|
478
|
-
"mock_work_agents": mock_work_agents,
|
479
|
-
"overall_quality": overall_quality,
|
480
|
-
"real_work_percentage": (real_work_agents / total_agents * 100) if total_agents > 0 else 0
|
481
|
-
}
|
memra-sdk/memra/models.py
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
from typing import List, Dict, Optional, Any, Union
|
2
|
-
from pydantic import BaseModel, Field
|
3
|
-
|
4
|
-
class LLM(BaseModel):
|
5
|
-
model: str
|
6
|
-
temperature: float = 0.0
|
7
|
-
max_tokens: Optional[int] = None
|
8
|
-
stop: Optional[List[str]] = None
|
9
|
-
|
10
|
-
class Tool(BaseModel):
|
11
|
-
name: str
|
12
|
-
hosted_by: str = "memra" # or "mcp" for customer's Model Context Protocol
|
13
|
-
description: Optional[str] = None
|
14
|
-
parameters: Optional[Dict[str, Any]] = None
|
15
|
-
config: Optional[Dict[str, Any]] = None
|
16
|
-
|
17
|
-
class Agent(BaseModel):
|
18
|
-
role: str
|
19
|
-
job: str
|
20
|
-
llm: Optional[Union[LLM, Dict[str, Any]]] = None
|
21
|
-
sops: List[str] = Field(default_factory=list)
|
22
|
-
tools: List[Union[Tool, Dict[str, Any]]] = Field(default_factory=list)
|
23
|
-
systems: List[str] = Field(default_factory=list)
|
24
|
-
input_keys: List[str] = Field(default_factory=list)
|
25
|
-
output_key: str
|
26
|
-
allow_delegation: bool = False
|
27
|
-
fallback_agents: Optional[Dict[str, str]] = None
|
28
|
-
config: Optional[Dict[str, Any]] = None
|
29
|
-
|
30
|
-
class ExecutionPolicy(BaseModel):
|
31
|
-
retry_on_fail: bool = True
|
32
|
-
max_retries: int = 2
|
33
|
-
halt_on_validation_error: bool = True
|
34
|
-
timeout_seconds: int = 300
|
35
|
-
|
36
|
-
class ExecutionTrace(BaseModel):
|
37
|
-
agents_executed: List[str] = Field(default_factory=list)
|
38
|
-
tools_invoked: List[str] = Field(default_factory=list)
|
39
|
-
execution_times: Dict[str, float] = Field(default_factory=dict)
|
40
|
-
errors: List[str] = Field(default_factory=list)
|
41
|
-
|
42
|
-
def show(self):
|
43
|
-
"""Display execution trace information"""
|
44
|
-
print("=== Execution Trace ===")
|
45
|
-
print(f"Agents executed: {', '.join(self.agents_executed)}")
|
46
|
-
print(f"Tools invoked: {', '.join(self.tools_invoked)}")
|
47
|
-
if self.errors:
|
48
|
-
print(f"Errors: {', '.join(self.errors)}")
|
49
|
-
|
50
|
-
class DepartmentResult(BaseModel):
|
51
|
-
success: bool
|
52
|
-
data: Optional[Dict[str, Any]] = None
|
53
|
-
error: Optional[str] = None
|
54
|
-
trace: ExecutionTrace = Field(default_factory=ExecutionTrace)
|
55
|
-
|
56
|
-
class DepartmentAudit(BaseModel):
|
57
|
-
agents_run: List[str]
|
58
|
-
tools_invoked: List[str]
|
59
|
-
duration_seconds: float
|
60
|
-
total_cost: Optional[float] = None
|
61
|
-
|
62
|
-
class Department(BaseModel):
|
63
|
-
name: str
|
64
|
-
mission: str
|
65
|
-
agents: List[Agent]
|
66
|
-
manager_agent: Optional[Agent] = None
|
67
|
-
default_llm: Optional[LLM] = None
|
68
|
-
workflow_order: List[str] = Field(default_factory=list)
|
69
|
-
dependencies: List[str] = Field(default_factory=list)
|
70
|
-
execution_policy: Optional[ExecutionPolicy] = None
|
71
|
-
context: Optional[Dict[str, Any]] = None
|
72
|
-
|
73
|
-
def run(self, input: Dict[str, Any]) -> DepartmentResult:
|
74
|
-
"""
|
75
|
-
Execute the department workflow with the given input data.
|
76
|
-
"""
|
77
|
-
# Import here to avoid circular imports
|
78
|
-
from .execution import ExecutionEngine
|
79
|
-
|
80
|
-
engine = ExecutionEngine()
|
81
|
-
return engine.execute_department(self, input)
|
82
|
-
|
83
|
-
def audit(self) -> DepartmentAudit:
|
84
|
-
"""
|
85
|
-
Return audit information about the last execution.
|
86
|
-
"""
|
87
|
-
# Import here to avoid circular imports
|
88
|
-
from .execution import ExecutionEngine
|
89
|
-
|
90
|
-
engine = ExecutionEngine()
|
91
|
-
audit = engine.get_last_audit()
|
92
|
-
if audit:
|
93
|
-
return audit
|
94
|
-
else:
|
95
|
-
return DepartmentAudit(
|
96
|
-
agents_run=[],
|
97
|
-
tools_invoked=[],
|
98
|
-
duration_seconds=0.0
|
99
|
-
)
|