memra 0.0.1__py3-none-any.whl → 0.2.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.
@@ -0,0 +1,418 @@
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_client import ToolRegistryClient
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class ExecutionEngine:
10
+ """Engine that executes department workflows by coordinating agents and tools"""
11
+
12
+ def __init__(self):
13
+ self.tool_registry = ToolRegistryClient()
14
+ self.last_execution_audit: Optional[DepartmentAudit] = None
15
+
16
+ def execute_department(self, department: Department, input_data: Dict[str, Any]) -> DepartmentResult:
17
+ """Execute a department workflow"""
18
+ start_time = time.time()
19
+ trace = ExecutionTrace()
20
+
21
+ try:
22
+ print(f"\n🏢 Starting {department.name} Department")
23
+ print(f"📋 Mission: {department.mission}")
24
+ print(f"👥 Team: {', '.join([agent.role for agent in department.agents])}")
25
+ if department.manager_agent:
26
+ print(f"👔 Manager: {department.manager_agent.role}")
27
+ print(f"🔄 Workflow: {' → '.join(department.workflow_order)}")
28
+ print("=" * 60)
29
+
30
+ logger.info(f"Starting execution of department: {department.name}")
31
+
32
+ # Initialize execution context
33
+ context = {
34
+ "input": input_data,
35
+ "department_context": department.context or {},
36
+ "results": {}
37
+ }
38
+
39
+ # Execute agents in workflow order
40
+ for i, agent_role in enumerate(department.workflow_order, 1):
41
+ print(f"\n🔄 Step {i}/{len(department.workflow_order)}: {agent_role}")
42
+
43
+ agent = self._find_agent_by_role(department, agent_role)
44
+ if not agent:
45
+ error_msg = f"Agent with role '{agent_role}' not found in department"
46
+ print(f"❌ Error: {error_msg}")
47
+ trace.errors.append(error_msg)
48
+ return DepartmentResult(
49
+ success=False,
50
+ error=error_msg,
51
+ trace=trace
52
+ )
53
+
54
+ # Execute agent
55
+ agent_start = time.time()
56
+ result = self._execute_agent(agent, context, trace)
57
+ agent_duration = time.time() - agent_start
58
+
59
+ trace.agents_executed.append(agent.role)
60
+ trace.execution_times[agent.role] = agent_duration
61
+
62
+ if not result.get("success", False):
63
+ # Try fallback if available
64
+ if department.manager_agent and agent.role in (department.manager_agent.fallback_agents or {}):
65
+ fallback_role = department.manager_agent.fallback_agents[agent.role]
66
+ print(f"🔄 {department.manager_agent.role}: Let me try {fallback_role} as backup for {agent.role}")
67
+ fallback_agent = self._find_agent_by_role(department, fallback_role)
68
+ if fallback_agent:
69
+ logger.info(f"Trying fallback agent: {fallback_role}")
70
+ result = self._execute_agent(fallback_agent, context, trace)
71
+ trace.agents_executed.append(fallback_agent.role)
72
+
73
+ if not result.get("success", False):
74
+ error_msg = f"Agent {agent.role} failed: {result.get('error', 'Unknown error')}"
75
+ print(f"❌ Workflow stopped: {error_msg}")
76
+ trace.errors.append(error_msg)
77
+ return DepartmentResult(
78
+ success=False,
79
+ error=error_msg,
80
+ trace=trace
81
+ )
82
+
83
+ # Store result for next agent
84
+ context["results"][agent.output_key] = result.get("data")
85
+ print(f"✅ Step {i} completed in {agent_duration:.1f}s")
86
+
87
+ # Execute manager agent for final validation if present
88
+ if department.manager_agent:
89
+ print(f"\n🔍 Final Review Phase")
90
+ manager_start = time.time()
91
+
92
+ # Prepare manager input with all workflow results
93
+ manager_input = {
94
+ "workflow_results": context["results"],
95
+ "department_context": context["department_context"]
96
+ }
97
+
98
+ # Add connection if available
99
+ if "connection" in context["input"]:
100
+ manager_input["connection"] = context["input"]["connection"]
101
+
102
+ # Execute manager validation
103
+ manager_result = self._execute_manager_validation(department.manager_agent, manager_input, trace)
104
+ manager_duration = time.time() - manager_start
105
+
106
+ trace.agents_executed.append(department.manager_agent.role)
107
+ trace.execution_times[department.manager_agent.role] = manager_duration
108
+
109
+ # Store manager validation results
110
+ context["results"][department.manager_agent.output_key] = manager_result.get("data")
111
+
112
+ # Check if manager validation failed
113
+ if not manager_result.get("success", False):
114
+ error_msg = f"Manager validation failed: {manager_result.get('error', 'Unknown error')}"
115
+ print(f"❌ {error_msg}")
116
+ trace.errors.append(error_msg)
117
+ return DepartmentResult(
118
+ success=False,
119
+ error=error_msg,
120
+ trace=trace
121
+ )
122
+
123
+ print(f"✅ Manager review completed in {manager_duration:.1f}s")
124
+
125
+ # Create audit record
126
+ total_duration = time.time() - start_time
127
+ self.last_execution_audit = DepartmentAudit(
128
+ agents_run=trace.agents_executed,
129
+ tools_invoked=trace.tools_invoked,
130
+ duration_seconds=total_duration
131
+ )
132
+
133
+ print(f"\n🎉 {department.name} Department workflow completed!")
134
+ print(f"⏱️ Total time: {total_duration:.1f}s")
135
+ print("=" * 60)
136
+
137
+ return DepartmentResult(
138
+ success=True,
139
+ data=context["results"],
140
+ trace=trace
141
+ )
142
+
143
+ except Exception as e:
144
+ print(f"💥 Unexpected error in {department.name} Department: {str(e)}")
145
+ logger.error(f"Execution failed: {str(e)}")
146
+ trace.errors.append(str(e))
147
+ return DepartmentResult(
148
+ success=False,
149
+ error=str(e),
150
+ trace=trace
151
+ )
152
+
153
+ def _find_agent_by_role(self, department: Department, role: str) -> Optional[Agent]:
154
+ """Find an agent by role in the department"""
155
+ for agent in department.agents:
156
+ if agent.role == role:
157
+ return agent
158
+ return None
159
+
160
+ def _execute_agent(self, agent: Agent, context: Dict[str, Any], trace: ExecutionTrace) -> Dict[str, Any]:
161
+ """Execute a single agent"""
162
+ print(f"\n👤 {agent.role}: Hi! I'm starting my work now...")
163
+ logger.info(f"Executing agent: {agent.role}")
164
+
165
+ try:
166
+ # Show what the agent is thinking about
167
+ print(f"💭 {agent.role}: My job is to {agent.job.lower()}")
168
+
169
+ # Prepare input data for agent
170
+ agent_input = {}
171
+ for key in agent.input_keys:
172
+ if key in context["input"]:
173
+ agent_input[key] = context["input"][key]
174
+ print(f"📥 {agent.role}: I received '{key}' as input")
175
+ elif key in context["results"]:
176
+ agent_input[key] = context["results"][key]
177
+ print(f"📥 {agent.role}: I got '{key}' from a previous agent")
178
+ else:
179
+ print(f"🤔 {agent.role}: Hmm, I'm missing input '{key}' but I'll try to work without it")
180
+ logger.warning(f"Missing input key '{key}' for agent {agent.role}")
181
+
182
+ # Always include connection string if available (for database tools)
183
+ if "connection" in context["input"]:
184
+ agent_input["connection"] = context["input"]["connection"]
185
+
186
+ # Execute agent's tools
187
+ result_data = {}
188
+ tools_with_real_work = []
189
+ tools_with_mock_work = []
190
+
191
+ print(f"🔧 {agent.role}: I need to use {len(agent.tools)} tool(s) to complete my work...")
192
+
193
+ for i, tool_spec in enumerate(agent.tools, 1):
194
+ tool_name = tool_spec["name"] if isinstance(tool_spec, dict) else tool_spec.name
195
+ hosted_by = tool_spec.get("hosted_by", "memra") if isinstance(tool_spec, dict) else tool_spec.hosted_by
196
+
197
+ print(f"⚡ {agent.role}: Using tool {i}/{len(agent.tools)}: {tool_name}")
198
+
199
+ trace.tools_invoked.append(tool_name)
200
+
201
+ # Get tool from registry and execute
202
+ tool_result = self.tool_registry.execute_tool(
203
+ tool_name,
204
+ hosted_by,
205
+ agent_input,
206
+ agent.config
207
+ )
208
+
209
+ if not tool_result.get("success", False):
210
+ print(f"😟 {agent.role}: Oh no! Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}")
211
+ return {
212
+ "success": False,
213
+ "error": f"Tool {tool_name} failed: {tool_result.get('error', 'Unknown error')}"
214
+ }
215
+
216
+ # Check if this tool did real work or mock work
217
+ tool_data = tool_result.get("data", {})
218
+ if self._is_real_work(tool_name, tool_data):
219
+ tools_with_real_work.append(tool_name)
220
+ print(f"✅ {agent.role}: Great! {tool_name} did real work and gave me useful results")
221
+ else:
222
+ tools_with_mock_work.append(tool_name)
223
+ print(f"🔄 {agent.role}: {tool_name} gave me simulated results (that's okay for testing)")
224
+
225
+ result_data.update(tool_data)
226
+
227
+ # Add metadata about real vs mock work
228
+ result_data["_memra_metadata"] = {
229
+ "agent_role": agent.role,
230
+ "tools_real_work": tools_with_real_work,
231
+ "tools_mock_work": tools_with_mock_work,
232
+ "work_quality": "real" if tools_with_real_work else "mock"
233
+ }
234
+
235
+ # Agent reports completion
236
+ if tools_with_real_work:
237
+ print(f"🎉 {agent.role}: Perfect! I completed my work with real data processing")
238
+ else:
239
+ print(f"📝 {agent.role}: I finished my work, but used simulated data (still learning!)")
240
+
241
+ print(f"📤 {agent.role}: Passing my results to the next agent via '{agent.output_key}'")
242
+
243
+ return {
244
+ "success": True,
245
+ "data": result_data
246
+ }
247
+
248
+ except Exception as e:
249
+ print(f"😰 {agent.role}: I encountered an error and couldn't complete my work: {str(e)}")
250
+ logger.error(f"Agent {agent.role} execution failed: {str(e)}")
251
+ return {
252
+ "success": False,
253
+ "error": str(e)
254
+ }
255
+
256
+ def _is_real_work(self, tool_name: str, tool_data: Dict[str, Any]) -> bool:
257
+ """Determine if a tool did real work or returned mock data"""
258
+
259
+ # Check for specific indicators of real work
260
+ if tool_name == "PDFProcessor":
261
+ # Real work if it has actual image paths and file size
262
+ return (
263
+ "metadata" in tool_data and
264
+ "file_size" in tool_data["metadata"] and
265
+ tool_data["metadata"]["file_size"] > 1000 and # Real file size
266
+ "pages" in tool_data and
267
+ len(tool_data["pages"]) > 0 and
268
+ "image_path" in tool_data["pages"][0]
269
+ )
270
+
271
+ elif tool_name == "InvoiceExtractionWorkflow":
272
+ # Real work if it has actual extracted data with specific vendor info
273
+ return (
274
+ "headerSection" in tool_data and
275
+ "vendorName" in tool_data["headerSection"] and
276
+ tool_data["headerSection"]["vendorName"] not in ["", "UNKNOWN", "Sample Vendor"] and
277
+ "chargesSummary" in tool_data and
278
+ "memra_checksum" in tool_data["chargesSummary"]
279
+ )
280
+
281
+ elif tool_name == "DatabaseQueryTool":
282
+ # Real work if it loaded the actual schema file (more than 3 columns)
283
+ return (
284
+ "columns" in tool_data and
285
+ len(tool_data["columns"]) > 3
286
+ )
287
+
288
+ elif tool_name == "DataValidator":
289
+ # Real work if it actually validated real data with meaningful validation
290
+ return (
291
+ "validation_errors" in tool_data and
292
+ isinstance(tool_data["validation_errors"], list) and
293
+ "is_valid" in tool_data and
294
+ # Check if it's validating real extracted data (not just mock data)
295
+ len(str(tool_data)) > 100 # Real validation results are more substantial
296
+ )
297
+
298
+ elif tool_name == "PostgresInsert":
299
+ # Real work if it successfully inserted into a real database
300
+ return (
301
+ "success" in tool_data and
302
+ tool_data["success"] == True and
303
+ "record_id" in tool_data and
304
+ isinstance(tool_data["record_id"], int) and # Real DB returns integer IDs
305
+ "database_table" in tool_data # Real implementation includes table name
306
+ )
307
+
308
+ # Default to mock work
309
+ return False
310
+
311
+ def get_last_audit(self) -> Optional[DepartmentAudit]:
312
+ """Get audit information from the last execution"""
313
+ return self.last_execution_audit
314
+
315
+ def _execute_manager_validation(self, manager_agent: Agent, manager_input: Dict[str, Any], trace: ExecutionTrace) -> Dict[str, Any]:
316
+ """Execute manager agent to validate workflow results"""
317
+ print(f"\n👔 {manager_agent.role}: Time for me to review everyone's work...")
318
+ logger.info(f"Manager {manager_agent.role} validating workflow results")
319
+
320
+ try:
321
+ # Analyze workflow results for real vs mock work
322
+ workflow_analysis = self._analyze_workflow_quality(manager_input["workflow_results"])
323
+
324
+ print(f"🔍 {manager_agent.role}: Let me analyze what each agent accomplished...")
325
+
326
+ # Prepare validation report
327
+ validation_report = {
328
+ "workflow_analysis": workflow_analysis,
329
+ "validation_status": "pass" if workflow_analysis["overall_quality"] == "real" else "fail",
330
+ "recommendations": [],
331
+ "agent_performance": {}
332
+ }
333
+
334
+ # Analyze each agent's performance
335
+ for result_key, result_data in manager_input["workflow_results"].items():
336
+ if isinstance(result_data, dict) and "_memra_metadata" in result_data:
337
+ metadata = result_data["_memra_metadata"]
338
+ agent_role = metadata["agent_role"]
339
+
340
+ if metadata["work_quality"] == "real":
341
+ print(f"👍 {manager_agent.role}: {agent_role} did excellent real work!")
342
+ else:
343
+ print(f"📋 {manager_agent.role}: {agent_role} completed their tasks but with simulated data")
344
+
345
+ validation_report["agent_performance"][agent_role] = {
346
+ "work_quality": metadata["work_quality"],
347
+ "tools_real_work": metadata["tools_real_work"],
348
+ "tools_mock_work": metadata["tools_mock_work"],
349
+ "status": "completed_real_work" if metadata["work_quality"] == "real" else "completed_mock_work"
350
+ }
351
+
352
+ # Add recommendations for mock work
353
+ if metadata["work_quality"] == "mock":
354
+ recommendation = f"Agent {agent_role} performed mock work - implement real {', '.join(metadata['tools_mock_work'])} functionality"
355
+ validation_report["recommendations"].append(recommendation)
356
+ print(f"💡 {manager_agent.role}: I recommend upgrading {agent_role}'s tools for production")
357
+
358
+ # Overall workflow validation
359
+ if workflow_analysis["overall_quality"] == "real":
360
+ validation_report["summary"] = "Workflow completed successfully with real data processing"
361
+ print(f"🎯 {manager_agent.role}: Excellent! This workflow is production-ready")
362
+ elif workflow_analysis["overall_quality"].startswith("mixed"):
363
+ validation_report["summary"] = "Workflow completed with mixed real and simulated data"
364
+ print(f"⚖️ {manager_agent.role}: Good progress! Some agents are production-ready, others need work")
365
+ else:
366
+ validation_report["summary"] = "Workflow completed but with mock/simulated data - production readiness requires real implementations"
367
+ print(f"🚧 {manager_agent.role}: This workflow needs more development before production use")
368
+
369
+ real_percentage = workflow_analysis["real_work_percentage"]
370
+ print(f"📊 {manager_agent.role}: Overall assessment: {real_percentage:.0f}% of agents did real work")
371
+
372
+ return {
373
+ "success": True,
374
+ "data": validation_report
375
+ }
376
+
377
+ except Exception as e:
378
+ print(f"😰 {manager_agent.role}: I had trouble analyzing the workflow: {str(e)}")
379
+ logger.error(f"Manager validation failed: {str(e)}")
380
+ return {
381
+ "success": False,
382
+ "error": str(e)
383
+ }
384
+
385
+ def _analyze_workflow_quality(self, workflow_results: Dict[str, Any]) -> Dict[str, Any]:
386
+ """Analyze the overall quality of workflow execution"""
387
+
388
+ total_agents = 0
389
+ real_work_agents = 0
390
+ mock_work_agents = 0
391
+
392
+ for result_key, result_data in workflow_results.items():
393
+ if isinstance(result_data, dict) and "_memra_metadata" in result_data:
394
+ metadata = result_data["_memra_metadata"]
395
+ total_agents += 1
396
+
397
+ if metadata["work_quality"] == "real":
398
+ real_work_agents += 1
399
+ else:
400
+ mock_work_agents += 1
401
+
402
+ # Determine overall quality
403
+ if real_work_agents > 0 and mock_work_agents == 0:
404
+ overall_quality = "real"
405
+ elif real_work_agents > mock_work_agents:
406
+ overall_quality = "mixed_mostly_real"
407
+ elif real_work_agents > 0:
408
+ overall_quality = "mixed_mostly_mock"
409
+ else:
410
+ overall_quality = "mock"
411
+
412
+ return {
413
+ "total_agents": total_agents,
414
+ "real_work_agents": real_work_agents,
415
+ "mock_work_agents": mock_work_agents,
416
+ "overall_quality": overall_quality,
417
+ "real_work_percentage": (real_work_agents / total_agents * 100) if total_agents > 0 else 0
418
+ }
@@ -0,0 +1,98 @@
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
+
16
+ class Agent(BaseModel):
17
+ role: str
18
+ job: str
19
+ llm: Optional[Union[LLM, Dict[str, Any]]] = None
20
+ sops: List[str] = Field(default_factory=list)
21
+ tools: List[Union[Tool, Dict[str, Any]]] = Field(default_factory=list)
22
+ systems: List[str] = Field(default_factory=list)
23
+ input_keys: List[str] = Field(default_factory=list)
24
+ output_key: str
25
+ allow_delegation: bool = False
26
+ fallback_agents: Optional[Dict[str, str]] = None
27
+ config: Optional[Dict[str, Any]] = None
28
+
29
+ class ExecutionPolicy(BaseModel):
30
+ retry_on_fail: bool = True
31
+ max_retries: int = 2
32
+ halt_on_validation_error: bool = True
33
+ timeout_seconds: int = 300
34
+
35
+ class ExecutionTrace(BaseModel):
36
+ agents_executed: List[str] = Field(default_factory=list)
37
+ tools_invoked: List[str] = Field(default_factory=list)
38
+ execution_times: Dict[str, float] = Field(default_factory=dict)
39
+ errors: List[str] = Field(default_factory=list)
40
+
41
+ def show(self):
42
+ """Display execution trace information"""
43
+ print("=== Execution Trace ===")
44
+ print(f"Agents executed: {', '.join(self.agents_executed)}")
45
+ print(f"Tools invoked: {', '.join(self.tools_invoked)}")
46
+ if self.errors:
47
+ print(f"Errors: {', '.join(self.errors)}")
48
+
49
+ class DepartmentResult(BaseModel):
50
+ success: bool
51
+ data: Optional[Dict[str, Any]] = None
52
+ error: Optional[str] = None
53
+ trace: ExecutionTrace = Field(default_factory=ExecutionTrace)
54
+
55
+ class DepartmentAudit(BaseModel):
56
+ agents_run: List[str]
57
+ tools_invoked: List[str]
58
+ duration_seconds: float
59
+ total_cost: Optional[float] = None
60
+
61
+ class Department(BaseModel):
62
+ name: str
63
+ mission: str
64
+ agents: List[Agent]
65
+ manager_agent: Optional[Agent] = None
66
+ default_llm: Optional[LLM] = None
67
+ workflow_order: List[str] = Field(default_factory=list)
68
+ dependencies: List[str] = Field(default_factory=list)
69
+ execution_policy: Optional[ExecutionPolicy] = None
70
+ context: Optional[Dict[str, Any]] = None
71
+
72
+ def run(self, input: Dict[str, Any]) -> DepartmentResult:
73
+ """
74
+ Execute the department workflow with the given input data.
75
+ """
76
+ # Import here to avoid circular imports
77
+ from .execution import ExecutionEngine
78
+
79
+ engine = ExecutionEngine()
80
+ return engine.execute_department(self, input)
81
+
82
+ def audit(self) -> DepartmentAudit:
83
+ """
84
+ Return audit information about the last execution.
85
+ """
86
+ # Import here to avoid circular imports
87
+ from .execution import ExecutionEngine
88
+
89
+ engine = ExecutionEngine()
90
+ audit = engine.get_last_audit()
91
+ if audit:
92
+ return audit
93
+ else:
94
+ return DepartmentAudit(
95
+ agents_run=[],
96
+ tools_invoked=[],
97
+ duration_seconds=0.0
98
+ )
@@ -0,0 +1,105 @@
1
+ import httpx
2
+ import logging
3
+ import os
4
+ from typing import Dict, Any, List, Optional
5
+ import asyncio
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class ToolRegistryClient:
10
+ """Client-side registry that calls Memra API for tool execution"""
11
+
12
+ def __init__(self):
13
+ self.api_base = os.getenv("MEMRA_API_URL", "https://api.memra.co")
14
+ self.api_key = os.getenv("MEMRA_API_KEY")
15
+ if not self.api_key:
16
+ raise ValueError(
17
+ "MEMRA_API_KEY environment variable is required. "
18
+ "Contact info@memra.co to request access."
19
+ )
20
+ self.tools_cache = None
21
+
22
+ def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
23
+ """Discover available tools from the API"""
24
+ try:
25
+ # Use sync httpx for compatibility with existing sync code
26
+ with httpx.Client(timeout=30.0) as client:
27
+ response = client.get(
28
+ f"{self.api_base}/tools/discover",
29
+ headers={"X-API-Key": self.api_key}
30
+ )
31
+ response.raise_for_status()
32
+
33
+ data = response.json()
34
+ tools = data.get("tools", [])
35
+
36
+ # Filter by hosted_by if specified
37
+ if hosted_by:
38
+ tools = [t for t in tools if t.get("hosted_by") == hosted_by]
39
+
40
+ self.tools_cache = tools
41
+ logger.info(f"Discovered {len(tools)} tools from API")
42
+ return tools
43
+
44
+ except Exception as e:
45
+ logger.error(f"Failed to discover tools from API: {e}")
46
+ # Return empty list if API is unavailable
47
+ return []
48
+
49
+ def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
50
+ config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
51
+ """Execute a tool via the API"""
52
+ try:
53
+ logger.info(f"Executing tool {tool_name} via API")
54
+
55
+ # Prepare request payload
56
+ payload = {
57
+ "tool_name": tool_name,
58
+ "hosted_by": hosted_by,
59
+ "input_data": input_data,
60
+ "config": config
61
+ }
62
+
63
+ # Make API call
64
+ with httpx.Client(timeout=120.0) as client: # Longer timeout for tool execution
65
+ response = client.post(
66
+ f"{self.api_base}/tools/execute",
67
+ headers={
68
+ "X-API-Key": self.api_key,
69
+ "Content-Type": "application/json"
70
+ },
71
+ json=payload
72
+ )
73
+ response.raise_for_status()
74
+
75
+ result = response.json()
76
+ logger.info(f"Tool {tool_name} executed successfully via API")
77
+ return result
78
+
79
+ except httpx.TimeoutException:
80
+ logger.error(f"Tool {tool_name} execution timed out")
81
+ return {
82
+ "success": False,
83
+ "error": f"Tool execution timed out after 120 seconds"
84
+ }
85
+ except httpx.HTTPStatusError as e:
86
+ logger.error(f"API error for tool {tool_name}: {e.response.status_code}")
87
+ return {
88
+ "success": False,
89
+ "error": f"API error: {e.response.status_code} - {e.response.text}"
90
+ }
91
+ except Exception as e:
92
+ logger.error(f"Tool execution failed for {tool_name}: {str(e)}")
93
+ return {
94
+ "success": False,
95
+ "error": str(e)
96
+ }
97
+
98
+ def health_check(self) -> bool:
99
+ """Check if the API is available"""
100
+ try:
101
+ with httpx.Client(timeout=10.0) as client:
102
+ response = client.get(f"{self.api_base}/health")
103
+ return response.status_code == 200
104
+ except:
105
+ return False
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: memra
3
- Version: 0.0.1
4
- Summary: Reserved package for the Memra AI orchestration framework.
5
- Home-page: https://memra.co
6
- Author: Memra Team
7
- Author-email: hello@memra.co
8
- Classifier: Development Status :: 1 - Planning
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.8
12
- Description-Content-Type: text/plain
13
- License-File: LICENSE
14
- Dynamic: author
15
- Dynamic: author-email
16
- Dynamic: classifier
17
- Dynamic: description
18
- Dynamic: description-content-type
19
- Dynamic: home-page
20
- Dynamic: license-file
21
- Dynamic: requires-python
22
- Dynamic: summary
23
-
24
- This package is reserved for Memra's official SDK. More coming soon.
@@ -1,5 +0,0 @@
1
- memra-0.0.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- memra-0.0.1.dist-info/METADATA,sha256=a8nhKxJay6jHbvOkyWofOjZCdKEwhmC6RY-pDf2oNo4,687
3
- memra-0.0.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
4
- memra-0.0.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
- memra-0.0.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
-