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.
Files changed (62) hide show
  1. memra/cli.py +322 -51
  2. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/METADATA +1 -1
  3. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/RECORD +7 -61
  4. memra-0.2.15.dist-info/top_level.txt +1 -0
  5. memra-0.2.13.dist-info/top_level.txt +0 -4
  6. memra-ops/app.py +0 -808
  7. memra-ops/config/config.py +0 -25
  8. memra-ops/config.py +0 -34
  9. memra-ops/logic/__init__.py +0 -1
  10. memra-ops/logic/file_tools.py +0 -43
  11. memra-ops/logic/invoice_tools.py +0 -668
  12. memra-ops/logic/invoice_tools_fix.py +0 -66
  13. memra-ops/mcp_bridge_server.py +0 -1178
  14. memra-ops/scripts/check_database.py +0 -37
  15. memra-ops/scripts/clear_database.py +0 -48
  16. memra-ops/scripts/monitor_database.py +0 -67
  17. memra-ops/scripts/release.py +0 -133
  18. memra-ops/scripts/reset_database.py +0 -65
  19. memra-ops/scripts/start_memra.py +0 -334
  20. memra-ops/scripts/stop_memra.py +0 -132
  21. memra-ops/server_tool_registry.py +0 -190
  22. memra-ops/tests/test_llm_text_to_sql.py +0 -115
  23. memra-ops/tests/test_llm_vs_pattern.py +0 -130
  24. memra-ops/tests/test_mcp_schema_aware.py +0 -124
  25. memra-ops/tests/test_schema_aware_sql.py +0 -139
  26. memra-ops/tests/test_schema_aware_sql_simple.py +0 -66
  27. memra-ops/tests/test_text_to_sql_demo.py +0 -140
  28. memra-ops/tools/mcp_bridge_server.py +0 -851
  29. memra-sdk/examples/accounts_payable.py +0 -215
  30. memra-sdk/examples/accounts_payable_client.py +0 -217
  31. memra-sdk/examples/accounts_payable_mcp.py +0 -200
  32. memra-sdk/examples/ask_questions.py +0 -123
  33. memra-sdk/examples/invoice_processing.py +0 -116
  34. memra-sdk/examples/propane_delivery.py +0 -87
  35. memra-sdk/examples/simple_text_to_sql.py +0 -158
  36. memra-sdk/memra/__init__.py +0 -31
  37. memra-sdk/memra/discovery.py +0 -15
  38. memra-sdk/memra/discovery_client.py +0 -49
  39. memra-sdk/memra/execution.py +0 -481
  40. memra-sdk/memra/models.py +0 -99
  41. memra-sdk/memra/tool_registry.py +0 -343
  42. memra-sdk/memra/tool_registry_client.py +0 -106
  43. memra-sdk/scripts/release.py +0 -133
  44. memra-sdk/setup.py +0 -52
  45. memra-workflows/accounts_payable/accounts_payable.py +0 -215
  46. memra-workflows/accounts_payable/accounts_payable_client.py +0 -216
  47. memra-workflows/accounts_payable/accounts_payable_mcp.py +0 -200
  48. memra-workflows/accounts_payable/accounts_payable_smart.py +0 -221
  49. memra-workflows/invoice_processing/invoice_processing.py +0 -116
  50. memra-workflows/invoice_processing/smart_invoice_processor.py +0 -220
  51. memra-workflows/logic/__init__.py +0 -1
  52. memra-workflows/logic/file_tools.py +0 -50
  53. memra-workflows/logic/invoice_tools.py +0 -501
  54. memra-workflows/logic/propane_agents.py +0 -52
  55. memra-workflows/mcp_bridge_server.py +0 -230
  56. memra-workflows/propane_delivery/propane_delivery.py +0 -87
  57. memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +0 -208
  58. memra-workflows/text_to_sql/complete_text_to_sql_system.py +0 -266
  59. memra-workflows/text_to_sql/file_discovery_demo.py +0 -156
  60. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/LICENSE +0 -0
  61. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/WHEEL +0 -0
  62. {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/entry_points.txt +0 -0
@@ -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
- )