memra 0.1.2__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.
memra/__init__.py CHANGED
@@ -1,28 +1,24 @@
1
1
  """
2
- Memra SDK - A declarative orchestration framework for AI-powered business workflows
2
+ Memra SDK - Declarative framework for enterprise workflows with MCP integration
3
+
4
+ A powerful orchestration framework that allows you to build AI-powered business workflows
5
+ with hybrid cloud/local execution capabilities.
3
6
  """
4
7
 
5
- from .models import (
6
- Agent,
7
- Department,
8
- LLM,
9
- Tool,
10
- ExecutionPolicy,
11
- ExecutionTrace,
12
- DepartmentResult,
13
- DepartmentAudit
14
- )
15
- from .discovery_client import discover_tools, check_api_health, get_api_status
8
+ __version__ = "0.2.0"
9
+ __author__ = "Memra"
10
+ __email__ = "info@memra.co"
11
+
12
+ # Core imports
13
+ from .models import Agent, Department, LLM, Tool
14
+ from .execution import ExecutionEngine
16
15
 
17
- __version__ = "0.1.2"
16
+ # Make key classes available at package level
18
17
  __all__ = [
19
- "Agent",
18
+ "Agent",
20
19
  "Department",
21
- "LLM",
22
- "Tool",
23
- "ExecutionPolicy",
24
- "ExecutionTrace",
25
- "DepartmentResult",
26
- "DepartmentAudit",
27
- "discover_tools"
20
+ "LLM",
21
+ "Tool",
22
+ "ExecutionEngine",
23
+ "__version__"
28
24
  ]
memra/discovery.py ADDED
@@ -0,0 +1,15 @@
1
+ from typing import List, Dict, Any, Optional
2
+ from .tool_registry import ToolRegistry
3
+
4
+ def discover_tools(hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
5
+ """
6
+ Discover available tools in the Memra platform.
7
+
8
+ Args:
9
+ hosted_by: Filter tools by host ("memra" or "mcp"). If None, returns all tools.
10
+
11
+ Returns:
12
+ List of available tools with their metadata
13
+ """
14
+ registry = ToolRegistry()
15
+ return registry.discover_tools(hosted_by)
memra/execution.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import time
2
2
  import logging
3
+ import os
3
4
  from typing import Dict, Any, List, Optional
4
5
  from .models import Department, Agent, DepartmentResult, ExecutionTrace, DepartmentAudit
5
- from .tool_registry_client import ToolRegistryClient
6
6
 
7
7
  logger = logging.getLogger(__name__)
8
8
 
@@ -10,7 +10,16 @@ class ExecutionEngine:
10
10
  """Engine that executes department workflows by coordinating agents and tools"""
11
11
 
12
12
  def __init__(self):
13
- self.tool_registry = ToolRegistryClient()
13
+ # Use API client if MEMRA_API_KEY is set, otherwise use local tools
14
+ if os.getenv('MEMRA_API_KEY'):
15
+ from .tool_registry_client import ToolRegistryClient
16
+ self.tool_registry = ToolRegistryClient()
17
+ logger.info("Using API client for tool execution")
18
+ else:
19
+ from .tool_registry import ToolRegistry
20
+ self.tool_registry = ToolRegistry()
21
+ logger.info("Using local tool registry")
22
+
14
23
  self.last_execution_audit: Optional[DepartmentAudit] = None
15
24
 
16
25
  def execute_department(self, department: Department, input_data: Dict[str, Any]) -> DepartmentResult:
@@ -194,6 +203,13 @@ class ExecutionEngine:
194
203
  tool_name = tool_spec["name"] if isinstance(tool_spec, dict) else tool_spec.name
195
204
  hosted_by = tool_spec.get("hosted_by", "memra") if isinstance(tool_spec, dict) else tool_spec.hosted_by
196
205
 
206
+ # Extract tool-level config if available, otherwise use agent config
207
+ tool_config = None
208
+ if isinstance(tool_spec, dict) and "config" in tool_spec:
209
+ tool_config = tool_spec["config"]
210
+ elif agent.config:
211
+ tool_config = agent.config
212
+
197
213
  print(f"⚡ {agent.role}: Using tool {i}/{len(agent.tools)}: {tool_name}")
198
214
 
199
215
  trace.tools_invoked.append(tool_name)
@@ -203,7 +219,7 @@ class ExecutionEngine:
203
219
  tool_name,
204
220
  hosted_by,
205
221
  agent_input,
206
- agent.config
222
+ tool_config
207
223
  )
208
224
 
209
225
  if not tool_result.get("success", False):
memra/tool_registry.py ADDED
@@ -0,0 +1,190 @@
1
+ import importlib
2
+ import logging
3
+ import sys
4
+ import os
5
+ from typing import Dict, Any, List, Optional, Callable
6
+ from pathlib import Path
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class ToolRegistry:
11
+ """Registry for managing and executing tools"""
12
+
13
+ def __init__(self):
14
+ self.tools: Dict[str, Dict[str, Any]] = {}
15
+ self._add_project_to_path()
16
+ self._load_builtin_tools()
17
+
18
+ def _add_project_to_path(self):
19
+ """Add the project root to Python path so we can import logic modules"""
20
+ # Get the directory containing this file (memra package)
21
+ current_dir = Path(__file__).parent
22
+ # Go up one level to get the project root
23
+ project_root = current_dir.parent
24
+
25
+ if str(project_root) not in sys.path:
26
+ sys.path.insert(0, str(project_root))
27
+
28
+ def _load_builtin_tools(self):
29
+ """Load tools from the logic directory"""
30
+ try:
31
+ # Load invoice tools
32
+ from logic.invoice_tools import (
33
+ DatabaseQueryTool, PDFProcessor, OCRTool,
34
+ InvoiceExtractionWorkflow, DataValidator, PostgresInsert
35
+ )
36
+
37
+ self.register_tool("DatabaseQueryTool", DatabaseQueryTool, "memra",
38
+ "Query database schemas and data")
39
+ self.register_tool("PDFProcessor", PDFProcessor, "memra",
40
+ "Process PDF files and extract content")
41
+ self.register_tool("OCRTool", OCRTool, "memra",
42
+ "Perform OCR on images and documents")
43
+ self.register_tool("InvoiceExtractionWorkflow", InvoiceExtractionWorkflow, "memra",
44
+ "Extract structured data from invoices")
45
+ self.register_tool("DataValidator", DataValidator, "memra",
46
+ "Validate data against schemas")
47
+ self.register_tool("PostgresInsert", PostgresInsert, "memra",
48
+ "Insert data into PostgreSQL database")
49
+
50
+ # Load file tools
51
+ from logic.file_tools import FileReader
52
+ self.register_tool("FileReader", FileReader, "memra",
53
+ "Read files from the filesystem")
54
+
55
+ logger.info(f"Loaded {len(self.tools)} builtin tools")
56
+
57
+ except ImportError as e:
58
+ logger.warning(f"Could not load some tools: {e}")
59
+
60
+ def register_tool(self, name: str, tool_class: type, hosted_by: str, description: str):
61
+ """Register a tool in the registry"""
62
+ self.tools[name] = {
63
+ "class": tool_class,
64
+ "hosted_by": hosted_by,
65
+ "description": description
66
+ }
67
+ logger.debug(f"Registered tool: {name} (hosted by {hosted_by})")
68
+
69
+ def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
70
+ """Discover available tools, optionally filtered by host"""
71
+ tools = []
72
+ for name, info in self.tools.items():
73
+ if hosted_by is None or info["hosted_by"] == hosted_by:
74
+ tools.append({
75
+ "name": name,
76
+ "hosted_by": info["hosted_by"],
77
+ "description": info["description"]
78
+ })
79
+ return tools
80
+
81
+ def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
82
+ config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
83
+ """Execute a tool with the given input data"""
84
+ if tool_name not in self.tools:
85
+ return {
86
+ "success": False,
87
+ "error": f"Tool '{tool_name}' not found in registry"
88
+ }
89
+
90
+ tool_info = self.tools[tool_name]
91
+ if tool_info["hosted_by"] != hosted_by:
92
+ return {
93
+ "success": False,
94
+ "error": f"Tool '{tool_name}' is hosted by '{tool_info['hosted_by']}', not '{hosted_by}'"
95
+ }
96
+
97
+ try:
98
+ # Instantiate tool
99
+ tool_class = tool_info["class"]
100
+
101
+ # Some tools need credentials/config for initialization
102
+ if tool_name in ["DatabaseQueryTool", "PostgresInsert"]:
103
+ if "connection" in input_data:
104
+ # Parse connection string or use credentials
105
+ credentials = self._parse_connection(input_data["connection"])
106
+ tool_instance = tool_class(credentials)
107
+ else:
108
+ return {
109
+ "success": False,
110
+ "error": f"Tool '{tool_name}' requires database credentials"
111
+ }
112
+ elif tool_name == "InvoiceExtractionWorkflow":
113
+ # This tool needs to be instantiated to initialize the LLM client
114
+ tool_instance = tool_class()
115
+ else:
116
+ tool_instance = tool_class()
117
+
118
+ # Execute tool based on its type
119
+ result = self._execute_tool_method(tool_instance, tool_name, input_data, config)
120
+
121
+ return {
122
+ "success": True,
123
+ "data": result
124
+ }
125
+
126
+ except Exception as e:
127
+ logger.error(f"Tool execution failed for {tool_name}: {str(e)}")
128
+ return {
129
+ "success": False,
130
+ "error": str(e)
131
+ }
132
+
133
+ def _execute_tool_method(self, tool_instance: Any, tool_name: str,
134
+ input_data: Dict[str, Any], config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
135
+ """Execute the appropriate method on the tool instance"""
136
+
137
+ if tool_name == "DatabaseQueryTool":
138
+ return tool_instance.get_schema("invoices") # Default to invoices table
139
+
140
+ elif tool_name == "PDFProcessor":
141
+ file_path = input_data.get("file", "")
142
+ return tool_instance.process_pdf(file_path)
143
+
144
+ elif tool_name == "OCRTool":
145
+ # Assume PDF processor output is passed as input
146
+ return {"extracted_text": tool_instance.extract_text(input_data)}
147
+
148
+ elif tool_name == "InvoiceExtractionWorkflow":
149
+ text = input_data.get("extracted_text", "")
150
+ schema = input_data.get("invoice_schema", {})
151
+ return tool_instance.extract_data(text, schema)
152
+
153
+ elif tool_name == "DataValidator":
154
+ data = input_data.get("invoice_data", {})
155
+ schema = input_data.get("invoice_schema", {})
156
+ return tool_instance.validate(data, schema)
157
+
158
+ elif tool_name == "PostgresInsert":
159
+ data = input_data.get("invoice_data", {})
160
+ return tool_instance.insert_record("invoices", data)
161
+
162
+ elif tool_name == "FileReader":
163
+ file_path = config.get("path") if config else input_data.get("file_path")
164
+ if not file_path:
165
+ raise ValueError("FileReader requires a file path")
166
+ return tool_instance.read_file(file_path)
167
+
168
+ else:
169
+ raise ValueError(f"Unknown tool execution method for {tool_name}")
170
+
171
+ def _parse_connection(self, connection_string: str) -> Dict[str, Any]:
172
+ """Parse a connection string into credentials"""
173
+ # Simple parser for postgres://user:pass@host:port/database
174
+ if connection_string.startswith("postgres://"):
175
+ # This is a simplified parser - in production you'd use a proper URL parser
176
+ parts = connection_string.replace("postgres://", "").split("/")
177
+ db_part = parts[1] if len(parts) > 1 else "finance"
178
+ auth_host = parts[0].split("@")
179
+ host_port = auth_host[1].split(":") if len(auth_host) > 1 else ["localhost", "5432"]
180
+ user_pass = auth_host[0].split(":") if len(auth_host) > 1 else ["user", "pass"]
181
+
182
+ return {
183
+ "host": host_port[0],
184
+ "port": int(host_port[1]) if len(host_port) > 1 else 5432,
185
+ "database": db_part,
186
+ "user": user_pass[0],
187
+ "password": user_pass[1] if len(user_pass) > 1 else ""
188
+ }
189
+
190
+ return {"connection_string": connection_string}
@@ -12,12 +12,13 @@ class ToolRegistryClient:
12
12
  def __init__(self):
13
13
  self.api_base = os.getenv("MEMRA_API_URL", "https://api.memra.co")
14
14
  self.api_key = os.getenv("MEMRA_API_KEY")
15
+ self.tools_cache = None
16
+
15
17
  if not self.api_key:
16
18
  raise ValueError(
17
19
  "MEMRA_API_KEY environment variable is required. "
18
- "Contact info@memra.co to request access."
20
+ "Please contact info@memra.co for an API key."
19
21
  )
20
- self.tools_cache = None
21
22
 
22
23
  def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
23
24
  """Discover available tools from the API"""
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: memra
3
+ Version: 0.2.0
4
+ Summary: Declarative framework for enterprise workflows with MCP integration
5
+ Home-page: https://github.com/memra/memra-sdk
6
+ Author: Memra
7
+ Author-email: Memra <info@memra.co>
8
+ License: MIT
9
+ Project-URL: Homepage, https://memra.co
10
+ Project-URL: Repository, https://github.com/memra-platform/memra-sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pydantic>=1.8.0
24
+ Requires-Dist: httpx>=0.24.0
25
+ Requires-Dist: typing-extensions>=4.0.0
26
+ Requires-Dist: aiohttp>=3.8.0
27
+ Requires-Dist: aiohttp-cors>=0.7.0
28
+ Requires-Dist: psycopg2-binary>=2.9.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=6.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio; extra == "dev"
32
+ Requires-Dist: black; extra == "dev"
33
+ Requires-Dist: flake8; extra == "dev"
34
+ Dynamic: author
35
+ Dynamic: home-page
36
+ Dynamic: license-file
37
+ Dynamic: requires-python
38
+
39
+ # Memra SDK
40
+
41
+ A declarative orchestration framework for AI-powered business workflows. Think of it as "Kubernetes for business logic" where agents are the pods and departments are the deployments.
42
+
43
+ ## 🚀 Team Setup
44
+
45
+ **New team member?** See the complete setup guide: **[TEAM_SETUP.md](TEAM_SETUP.md)**
46
+
47
+ This includes:
48
+ - Database setup (PostgreSQL + Docker)
49
+ - Local development environment
50
+ - Testing instructions
51
+ - Troubleshooting guide
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from memra.sdk.models import Agent, Department, Tool
57
+
58
+ # Define your agents
59
+ data_extractor = Agent(
60
+ role="Data Extraction Specialist",
61
+ job="Extract and validate data",
62
+ tools=[Tool(name="DataExtractor", hosted_by="memra")],
63
+ input_keys=["input_data"],
64
+ output_key="extracted_data"
65
+ )
66
+
67
+ # Create a department
68
+ dept = Department(
69
+ name="Data Processing",
70
+ mission="Process and validate data",
71
+ agents=[data_extractor]
72
+ )
73
+
74
+ # Run the workflow
75
+ result = dept.run({"input_data": {...}})
76
+ ```
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ pip install memra
82
+ ```
83
+
84
+ ## API Access
85
+
86
+ Memra requires an API key to execute workflows on the hosted infrastructure.
87
+
88
+ ### Get Your API Key
89
+ Contact [info@memra.co](mailto:info@memra.co) for API access.
90
+
91
+ ### Set Your API Key
92
+ ```bash
93
+ # Set environment variable
94
+ export MEMRA_API_KEY="your-api-key-here"
95
+
96
+ # Or add to your shell profile for persistence
97
+ echo 'export MEMRA_API_KEY="your-api-key-here"' >> ~/.zshrc
98
+ ```
99
+
100
+ ### Test Your Setup
101
+ ```bash
102
+ python examples/accounts_payable_client.py
103
+ ```
104
+
105
+ ## Documentation
106
+
107
+ Documentation is coming soon. For now, see the examples below and in the `examples/` directory.
108
+
109
+ ## Example: Propane Delivery Workflow
110
+
111
+ See the `examples/propane_delivery.py` file for a complete example of how to use Memra to orchestrate a propane delivery workflow.
112
+
113
+ ## Contributing
114
+
115
+ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) for details.
116
+
117
+ ## License
118
+
119
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
120
+
121
+ ## Examples
122
+
123
+ ```
124
+ ├── examples/
125
+ │ ├── accounts_payable_client.py # API-based example
126
+ │ ├── accounts_payable.py # Local example
127
+ │ ├── invoice_processing.py # Simple workflow
128
+ │ └── propane_delivery.py # Domain example
129
+ ├── memra/ # Core SDK
130
+ ├── logic/ # Tool implementations
131
+ ├── local/dependencies/ # Database setup & schemas
132
+ └── docker-compose.yml # Database setup
133
+ ```
134
+
135
+ ## ✨ New: MCP Integration
136
+
137
+ Memra now supports **Model Context Protocol (MCP)** integration, allowing you to execute operations on your local infrastructure while leveraging Memra's cloud-based AI processing.
138
+
139
+ **Key Benefits:**
140
+ - 🔒 **Keep sensitive data local** - Your databases stay on your infrastructure
141
+ - ⚡ **Hybrid processing** - AI processing in the cloud, data operations locally
142
+ - 🔐 **Secure communication** - HMAC-authenticated requests between cloud and local
143
+ - 🛠️ **Easy setup** - Simple bridge server connects your local resources
144
+
145
+ **Quick Example:**
146
+ ```python
147
+ # Agent that uses local database via MCP
148
+ agent = Agent(
149
+ role="Data Writer",
150
+ tools=[{
151
+ "name": "PostgresInsert",
152
+ "hosted_by": "mcp", # Routes to your local infrastructure
153
+ "config": {
154
+ "bridge_url": "http://localhost:8081",
155
+ "bridge_secret": "your-secret"
156
+ }
157
+ }]
158
+ )
159
+ ```
160
+
161
+ 📖 **[Complete MCP Integration Guide →](docs/mcp_integration.md)**
@@ -0,0 +1,19 @@
1
+ memra/__init__.py,sha256=XLSWpo42Ffp_pi5mvk4xvdYBZ8eNLAJF4_3Oi102i90,560
2
+ memra/discovery.py,sha256=yJIQnrDQu1nyzKykCIuzG_5SW5dIXHCEBLLKRWacIoY,480
3
+ memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
4
+ memra/execution.py,sha256=5NIyFVtQEeatYQ-fxexT0eWMtCh28k1hRC2Y6cfQaac,20917
5
+ memra/models.py,sha256=nTaYLAp0tRzQ0CQaBLNBURfhBQ5_gyty0ams4mghyIc,3289
6
+ memra/tool_registry.py,sha256=zdyKRShcmKtG7BVfmAHflW9FDl7rooPPAgbdVV4gJ8o,8268
7
+ memra/tool_registry_client.py,sha256=uzMQ4COvRams9vuPLcqcdljUpDlAYU_tyFxrRhrA0Lc,4009
8
+ memra-0.2.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ memra-sdk-package/examples/accounts_payable_client.py,sha256=Vu_h5C-qc6_80uz5dXJH4G3zfIbgUNAhQ2y8mWauao0,7401
10
+ memra-sdk-package/memra/__init__.py,sha256=QRk72YETLgL15GVt26tN_rBraCQkhZO7UB9T6d4u_uU,543
11
+ memra-sdk-package/memra/discovery_client.py,sha256=AbnKn6qhyrf7vmOvknEeDzH4tiGHsqPHtDaein_qaW0,1271
12
+ memra-sdk-package/memra/execution.py,sha256=UJ_MJ4getuSk4HJW1sCi7lc26avX-G6-GxnvE-DiSwk,20191
13
+ memra-sdk-package/memra/models.py,sha256=nTaYLAp0tRzQ0CQaBLNBURfhBQ5_gyty0ams4mghyIc,3289
14
+ memra-sdk-package/memra/tool_registry_client.py,sha256=KyNNxj84248E-8MoWNj6pJmlllUG8s0lmeXXmbu0U7o,3996
15
+ memra-0.2.0.dist-info/METADATA,sha256=eOuvH39VFUh-QxTdE5RwT6isgRIJkptEC2lsqlF2AA4,4816
16
+ memra-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ memra-0.2.0.dist-info/entry_points.txt,sha256=LBVjwWoxWJRzNLgeByPn6xUvWFIRnqnemvAZgIoSt08,41
18
+ memra-0.2.0.dist-info/top_level.txt,sha256=5dqePB77aj_pPFavlwxtBvdkUM-kP-WiQD3LRbQswwc,24
19
+ memra-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ memra = memra.cli:main
@@ -0,0 +1,2 @@
1
+ memra
2
+ memra-sdk-package