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,230 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Simple MCP Bridge Server for local tool execution
4
- """
5
-
6
- import os
7
- import json
8
- import hmac
9
- import hashlib
10
- import logging
11
- import asyncio
12
- import psycopg2
13
- from aiohttp import web, web_request
14
- from typing import Dict, Any, Optional
15
-
16
- logging.basicConfig(level=logging.INFO)
17
- logger = logging.getLogger(__name__)
18
-
19
- class MCPBridgeServer:
20
- def __init__(self, postgres_url: str, bridge_secret: str):
21
- self.postgres_url = postgres_url
22
- self.bridge_secret = bridge_secret
23
-
24
- def verify_signature(self, request_body: str, signature: str) -> bool:
25
- """Verify HMAC signature"""
26
- expected = hmac.new(
27
- self.bridge_secret.encode(),
28
- request_body.encode(),
29
- hashlib.sha256
30
- ).hexdigest()
31
- return hmac.compare_digest(expected, signature)
32
-
33
- async def execute_tool(self, request: web_request.Request) -> web.Response:
34
- """Execute MCP tool endpoint"""
35
- try:
36
- # Get request body
37
- body = await request.text()
38
- data = json.loads(body)
39
-
40
- # Verify signature
41
- signature = request.headers.get('X-Bridge-Secret')
42
- if not signature or signature != self.bridge_secret:
43
- logger.warning("Invalid or missing bridge secret")
44
- return web.json_response({
45
- "success": False,
46
- "error": "Invalid authentication"
47
- }, status=401)
48
-
49
- tool_name = data.get('tool_name')
50
- input_data = data.get('input_data', {})
51
-
52
- logger.info(f"Executing MCP tool: {tool_name}")
53
-
54
- if tool_name == "DataValidator":
55
- result = await self.data_validator(input_data)
56
- elif tool_name == "PostgresInsert":
57
- result = await self.postgres_insert(input_data)
58
- else:
59
- return web.json_response({
60
- "success": False,
61
- "error": f"Unknown tool: {tool_name}"
62
- }, status=400)
63
-
64
- return web.json_response(result)
65
-
66
- except Exception as e:
67
- logger.error(f"Tool execution failed: {str(e)}")
68
- return web.json_response({
69
- "success": False,
70
- "error": str(e)
71
- }, status=500)
72
-
73
- async def data_validator(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
74
- """Validate data against schema"""
75
- try:
76
- invoice_data = input_data.get('invoice_data', {})
77
-
78
- # Perform basic validation
79
- validation_errors = []
80
-
81
- # Check required fields
82
- required_fields = ['headerSection', 'billingDetails', 'chargesSummary']
83
- for field in required_fields:
84
- if field not in invoice_data:
85
- validation_errors.append(f"Missing required field: {field}")
86
-
87
- # Validate header section
88
- if 'headerSection' in invoice_data:
89
- header = invoice_data['headerSection']
90
- if not header.get('vendorName'):
91
- validation_errors.append("Missing vendor name in header")
92
- if not header.get('subtotal'):
93
- validation_errors.append("Missing subtotal in header")
94
-
95
- # Validate billing details
96
- if 'billingDetails' in invoice_data:
97
- billing = invoice_data['billingDetails']
98
- if not billing.get('invoiceNumber'):
99
- validation_errors.append("Missing invoice number")
100
- if not billing.get('invoiceDate'):
101
- validation_errors.append("Missing invoice date")
102
-
103
- is_valid = len(validation_errors) == 0
104
-
105
- logger.info(f"Data validation completed: {'valid' if is_valid else 'invalid'}")
106
-
107
- return {
108
- "success": True,
109
- "data": {
110
- "is_valid": is_valid,
111
- "validation_errors": validation_errors,
112
- "validated_data": invoice_data
113
- }
114
- }
115
-
116
- except Exception as e:
117
- logger.error(f"Data validation failed: {str(e)}")
118
- return {
119
- "success": False,
120
- "error": str(e)
121
- }
122
-
123
- async def postgres_insert(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
124
- """Insert data into PostgreSQL"""
125
- try:
126
- invoice_data = input_data.get('invoice_data', {})
127
- table_name = input_data.get('table_name', 'invoices')
128
-
129
- # Extract key fields from invoice data
130
- header = invoice_data.get('headerSection', {})
131
- billing = invoice_data.get('billingDetails', {})
132
- charges = invoice_data.get('chargesSummary', {})
133
-
134
- # Prepare insert data
135
- insert_data = {
136
- 'invoice_number': billing.get('invoiceNumber', ''),
137
- 'vendor_name': header.get('vendorName', ''),
138
- 'invoice_date': billing.get('invoiceDate', ''),
139
- 'total_amount': charges.get('document_total', 0),
140
- 'tax_amount': charges.get('secondary_tax', 0),
141
- 'line_items': json.dumps(charges.get('lineItemsBreakdown', [])),
142
- 'status': 'processed'
143
- }
144
-
145
- # Connect to database and insert
146
- conn = psycopg2.connect(self.postgres_url)
147
- cursor = conn.cursor()
148
-
149
- # Build insert query
150
- columns = ', '.join(insert_data.keys())
151
- placeholders = ', '.join(['%s'] * len(insert_data))
152
- query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING id"
153
-
154
- cursor.execute(query, list(insert_data.values()))
155
- record_id = cursor.fetchone()[0]
156
-
157
- conn.commit()
158
- cursor.close()
159
- conn.close()
160
-
161
- logger.info(f"Successfully inserted record with ID: {record_id}")
162
-
163
- return {
164
- "success": True,
165
- "data": {
166
- "success": True,
167
- "record_id": record_id,
168
- "database_table": table_name,
169
- "inserted_data": insert_data
170
- }
171
- }
172
-
173
- except Exception as e:
174
- logger.error(f"Database insert failed: {str(e)}")
175
- return {
176
- "success": False,
177
- "error": str(e)
178
- }
179
-
180
- async def health_check(self, request: web_request.Request) -> web.Response:
181
- """Health check endpoint"""
182
- return web.json_response({"status": "healthy", "service": "mcp-bridge"})
183
-
184
- def create_app(self) -> web.Application:
185
- """Create aiohttp application"""
186
- app = web.Application()
187
-
188
- # Add routes
189
- app.router.add_post('/execute_tool', self.execute_tool)
190
- app.router.add_get('/health', self.health_check)
191
-
192
- return app
193
-
194
- async def start(self, port: int = 8081):
195
- """Start the server"""
196
- app = self.create_app()
197
- runner = web.AppRunner(app)
198
- await runner.setup()
199
-
200
- site = web.TCPSite(runner, 'localhost', port)
201
- await site.start()
202
-
203
- logger.info(f"MCP Bridge Server started on http://localhost:{port}")
204
- logger.info(f"Available endpoints:")
205
- logger.info(f" POST /execute_tool - Execute MCP tools")
206
- logger.info(f" GET /health - Health check")
207
-
208
- # Keep running
209
- try:
210
- await asyncio.Future() # Run forever
211
- except KeyboardInterrupt:
212
- logger.info("Shutting down server...")
213
- finally:
214
- await runner.cleanup()
215
-
216
- def main():
217
- # Get configuration from environment
218
- postgres_url = os.getenv('MCP_POSTGRES_URL', 'postgresql://tarpus@localhost:5432/memra_invoice_db')
219
- bridge_secret = os.getenv('MCP_BRIDGE_SECRET', 'test-secret-for-development')
220
-
221
- logger.info(f"Starting MCP Bridge Server...")
222
- logger.info(f"PostgreSQL URL: {postgres_url}")
223
- logger.info(f"Bridge Secret: {'*' * len(bridge_secret)}")
224
-
225
- # Create and start server
226
- server = MCPBridgeServer(postgres_url, bridge_secret)
227
- asyncio.run(server.start())
228
-
229
- if __name__ == '__main__':
230
- main()
@@ -1,87 +0,0 @@
1
- from memra.sdk.models import Agent, Department, Tool
2
-
3
- # Define the tools that our agents will use
4
- data_extraction_tool = Tool(
5
- name="PropaneDataExtractor",
6
- description="Extracts propane-related data from various sources",
7
- hosted_by="memra"
8
- )
9
-
10
- planning_tool = Tool(
11
- name="PropaneDeliveryPlanner",
12
- description="Plans optimal propane delivery routes and schedules",
13
- hosted_by="memra"
14
- )
15
-
16
- execution_tool = Tool(
17
- name="PropaneDeliveryExecutor",
18
- description="Executes and tracks propane deliveries",
19
- hosted_by="memra"
20
- )
21
-
22
- # Define our agents
23
- data_extractor = Agent(
24
- role="Data Extraction Specialist",
25
- job="Extract and validate propane delivery data",
26
- tools=[data_extraction_tool],
27
- systems=["CustomerDatabase", "PropaneLevelsAPI"],
28
- input_keys=["customer_ids", "date_range"],
29
- output_key="extracted_data"
30
- )
31
-
32
- delivery_planner = Agent(
33
- role="Delivery Route Planner",
34
- job="Plan optimal delivery routes and schedules",
35
- tools=[planning_tool],
36
- systems=["RouteOptimizationEngine"],
37
- input_keys=["extracted_data"],
38
- output_key="delivery_plan"
39
- )
40
-
41
- delivery_executor = Agent(
42
- role="Delivery Coordinator",
43
- job="Execute and monitor propane deliveries",
44
- tools=[execution_tool],
45
- systems=["DeliveryTrackingSystem"],
46
- input_keys=["delivery_plan"],
47
- output_key="delivery_status"
48
- )
49
-
50
- # Define the manager agent that oversees the workflow
51
- manager = Agent(
52
- role="Propane Operations Manager",
53
- job="Oversee and coordinate the propane delivery workflow",
54
- llm={"model": "claude-3-opus"},
55
- sops=[
56
- "Validate data quality",
57
- "Handle delivery exceptions",
58
- "Optimize resource allocation"
59
- ],
60
- input_keys=["extracted_data", "delivery_plan", "delivery_status"],
61
- output_key="workflow_status"
62
- )
63
-
64
- # Create the Propane Delivery Department
65
- propane_department = Department(
66
- name="Propane Delivery Operations",
67
- mission="Efficiently manage and execute propane deliveries",
68
- agents=[data_extractor, delivery_planner, delivery_executor],
69
- manager_agent=manager,
70
- workflow_order=[
71
- "Data Extraction Specialist",
72
- "Delivery Route Planner",
73
- "Delivery Coordinator"
74
- ]
75
- )
76
-
77
- # Example usage
78
- if __name__ == "__main__":
79
- # This is how a developer would use the department
80
- result = propane_department.run({
81
- "customer_ids": ["CUST001", "CUST002"],
82
- "date_range": {
83
- "start": "2024-03-20",
84
- "end": "2024-03-27"
85
- }
86
- })
87
- print(f"Workflow result: {result}")
@@ -1,208 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Complete Invoice Processing Workflow with Text-to-SQL Integration
4
- Demonstrates: PDF → Processing → Database → Natural Language Queries
5
- """
6
-
7
- import os
8
- import sys
9
- import json
10
- from pathlib import Path
11
-
12
- # Add the parent directory to the path so we can import memra
13
- sys.path.insert(0, str(Path(__file__).parent.parent))
14
-
15
- from memra.tool_registry import ToolRegistry
16
-
17
- def discover_invoice_files():
18
- """Discover PDF files in the invoices directory"""
19
- invoices_dir = Path("invoices")
20
- if not invoices_dir.exists():
21
- invoices_dir.mkdir()
22
- return []
23
-
24
- pdf_files = list(invoices_dir.glob("*.pdf")) + list(invoices_dir.glob("*.PDF"))
25
- return [{"filename": f.name, "path": str(f), "size": f"{f.stat().st_size // 1024}KB"} for f in pdf_files]
26
-
27
- def get_file_to_process():
28
- """Smart file discovery and selection"""
29
- files = discover_invoice_files()
30
-
31
- if len(files) == 0:
32
- print("📁 No PDF files found in invoices/ directory")
33
- return None
34
-
35
- elif len(files) == 1:
36
- file_info = files[0]
37
- print(f"📄 Found 1 file: {file_info['filename']} ({file_info['size']})")
38
- print(f"🔄 Auto-processing: {file_info['filename']}")
39
- return file_info['path']
40
-
41
- else:
42
- print(f"📁 Found {len(files)} PDF files:")
43
- for i, file_info in enumerate(files, 1):
44
- print(f" {i}. {file_info['filename']} ({file_info['size']})")
45
-
46
- print(f"\n🤔 Multiple files found. What would you like to do?")
47
- print(f" Enter 1-{len(files)} to process a specific file")
48
-
49
- choice = input("\n📎 Your choice: ").strip().lower()
50
-
51
- if choice.isdigit():
52
- file_num = int(choice)
53
- if 1 <= file_num <= len(files):
54
- selected_file = files[file_num - 1]
55
- print(f"🔄 Processing: {selected_file['filename']}")
56
- return selected_file['path']
57
-
58
- print("❌ Invalid choice")
59
- return None
60
-
61
- def process_invoice_via_api(file_path):
62
- """Process invoice using the Memra API (simulated for demo)"""
63
- print(f"\n🔄 Step 1: Processing PDF via Memra API...")
64
- print(f"📄 File: {Path(file_path).name}")
65
-
66
- # In a real implementation, this would call the actual Memra API
67
- # For demo purposes, we'll simulate the processing
68
- print("⚡ Extracting invoice data...")
69
- print("✅ Invoice data extracted successfully")
70
- print("⚡ Validating data...")
71
- print("✅ Data validation passed")
72
- print("⚡ Inserting into database...")
73
- print("✅ Database insertion completed")
74
-
75
- # Return mock record ID
76
- return 23
77
-
78
- def run_text_to_sql_queries(registry, record_id=None):
79
- """Run interactive text-to-SQL queries after invoice processing"""
80
- print(f"\n{'='*60}")
81
- print("🧠 Business Intelligence Mode")
82
- print("Now you can ask questions about your invoice data!")
83
- print(f"{'='*60}")
84
-
85
- # MCP bridge configuration
86
- mcp_config = {
87
- "bridge_url": "http://localhost:8081",
88
- "bridge_secret": "test-secret-for-development"
89
- }
90
-
91
- # Suggested queries
92
- suggested_queries = [
93
- "How many invoices do we have in the database?",
94
- "What is the total amount of all invoices?",
95
- "Show me all invoices from Air Liquide",
96
- "Show me the most recent 5 invoices"
97
- ]
98
-
99
- if record_id:
100
- suggested_queries.insert(0, f"Show me the invoice that was just processed (ID {record_id})")
101
-
102
- print("💡 Suggested questions:")
103
- for i, query in enumerate(suggested_queries, 1):
104
- print(f" {i}. {query}")
105
-
106
- while True:
107
- print(f"\n🤔 What would you like to know about the invoices?")
108
- print(" (Enter a question, number for suggestions, or 'done' to finish)")
109
-
110
- user_input = input("\n❓ Your question: ").strip()
111
-
112
- if user_input.lower() in ['done', 'exit', 'quit', 'finish']:
113
- print("👋 Exiting Business Intelligence mode")
114
- break
115
-
116
- # Handle numbered suggestions
117
- if user_input.isdigit():
118
- question_num = int(user_input)
119
- if 1 <= question_num <= len(suggested_queries):
120
- question = suggested_queries[question_num - 1]
121
- else:
122
- print(f"❌ Please enter a number between 1 and {len(suggested_queries)}")
123
- continue
124
- else:
125
- question = user_input
126
-
127
- if not question:
128
- print("❌ Please enter a question")
129
- continue
130
-
131
- try:
132
- print(f"\n🔍 Processing: '{question}'")
133
-
134
- # Step 1: Generate SQL
135
- sql_result = registry.execute_tool(
136
- tool_name="TextToSQLGenerator",
137
- hosted_by="mcp",
138
- input_data={
139
- "question": question,
140
- "schema_info": {"tables": ["invoices"]}
141
- },
142
- config=mcp_config
143
- )
144
-
145
- if not sql_result.get("success"):
146
- print(f"❌ SQL generation failed: {sql_result.get('error')}")
147
- continue
148
-
149
- sql_data = sql_result.get("data", {})
150
- generated_sql = sql_data.get("generated_sql", "")
151
- print(f"📝 Generated SQL: {generated_sql}")
152
-
153
- # Step 2: Execute SQL
154
- execution_result = registry.execute_tool(
155
- tool_name="SQLExecutor",
156
- hosted_by="mcp",
157
- input_data={"sql_query": generated_sql},
158
- config=mcp_config
159
- )
160
-
161
- if execution_result.get("success"):
162
- query_results = execution_result.get("data", {})
163
- results = query_results.get("results", [])
164
- row_count = query_results.get("row_count", 0)
165
-
166
- print(f"\n📊 Results ({row_count} rows):")
167
- if results:
168
- for i, row in enumerate(results[:3]):
169
- print(f" {i+1}. {row}")
170
- if len(results) > 3:
171
- print(f" ... and {len(results) - 3} more rows")
172
- else:
173
- print(" No results found")
174
-
175
- if not query_results.get("_mock"):
176
- print("✅ Real database results!")
177
- else:
178
- print(f"❌ Query execution failed: {execution_result.get('error')}")
179
-
180
- except Exception as e:
181
- print(f"❌ Error: {str(e)}")
182
-
183
- def main():
184
- """Main workflow: PDF processing + Text-to-SQL queries"""
185
- print("🚀 Complete Invoice Processing & Analytics Workflow")
186
- print("=" * 60)
187
-
188
- # Step 1: Discover and select files
189
- file_to_process = get_file_to_process()
190
- if not file_to_process:
191
- print("❌ No file selected. Exiting.")
192
- return
193
-
194
- # Step 2: Process the invoice
195
- record_id = process_invoice_via_api(file_to_process)
196
-
197
- if record_id:
198
- print(f"\n✅ Invoice processing completed!")
199
- print(f"📊 Database Record ID: {record_id}")
200
-
201
- # Step 3: Offer text-to-SQL queries
202
- registry = ToolRegistry()
203
- run_text_to_sql_queries(registry, record_id)
204
- else:
205
- print("❌ Invoice processing failed")
206
-
207
- if __name__ == "__main__":
208
- main()