memra 0.2.12__py3-none-any.whl → 0.2.13__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 +114 -8
- memra/demos/etl_invoice_processing/check_after_workflow.py +50 -0
- memra/demos/etl_invoice_processing/check_database.py +44 -0
- memra/demos/etl_invoice_processing/check_recent_db.py +42 -0
- memra/demos/etl_invoice_processing/data/README.md +112 -0
- memra/demos/etl_invoice_processing/data/invoices/10352259401.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352259823.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352260169.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352260417.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352260599.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352260912.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352261134.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352261563.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352261647.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352261720.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352261811.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352262025.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352262454.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352262702.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352262884.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352263346.PDF +0 -0
- memra/demos/etl_invoice_processing/data/invoices/10352263429.PDF +0 -0
- memra/demos/etl_invoice_processing/database_monitor_agent.py +89 -0
- memra/demos/etl_invoice_processing/debug_mcp.py +66 -0
- memra/demos/etl_invoice_processing/debug_schema.py +45 -0
- memra/demos/etl_invoice_processing/etl_invoice_demo.py +1233 -0
- memra/demos/etl_invoice_processing/modify_database.py +65 -0
- memra/demos/etl_invoice_processing/run_etl_batch.py +60 -0
- memra/demos/etl_invoice_processing/setup_demo_data.py +154 -0
- memra/demos/etl_invoice_processing/simple_pdf_processor.py +181 -0
- memra/demos/etl_invoice_processing/test_agent3.py +56 -0
- memra/demos/etl_invoice_processing/test_agent3_v2.py +32 -0
- memra/demos/etl_invoice_processing/test_api.py +28 -0
- memra/demos/etl_invoice_processing/test_api_client_direct.py +89 -0
- memra/demos/etl_invoice_processing/test_conversion.py +172 -0
- memra/demos/etl_invoice_processing/test_debug.py +41 -0
- memra/demos/etl_invoice_processing/test_direct_vision.py +114 -0
- memra/demos/etl_invoice_processing/test_full_response.py +22 -0
- memra/demos/etl_invoice_processing/test_memra_response.py +124 -0
- memra/demos/etl_invoice_processing/test_pdf_processor_response.py +118 -0
- memra/demos/etl_invoice_processing/test_pdfprocessor_direct.py +96 -0
- memra/demos/etl_invoice_processing/test_postgres_insert.py +120 -0
- memra/demos/etl_invoice_processing/test_remote_upload.py +143 -0
- memra/demos/etl_invoice_processing/test_schema_format.py +39 -0
- memra/demos/etl_invoice_processing/test_sql_executor.py +58 -0
- memra/demos/etl_invoice_processing/test_sql_executor_extra_fields.py +61 -0
- memra/demos/etl_invoice_processing/test_sql_executor_fix.py +40 -0
- memra/demos/etl_invoice_processing/test_updated_server.py +50 -0
- memra/demos/etl_invoice_processing/test_upload_functionality.py +156 -0
- memra/demos/etl_invoice_processing/test_upload_server.py +232 -0
- memra/demos/etl_invoice_processing/test_vision_output.py +75 -0
- memra/demos/etl_invoice_processing/test_vision_prompt.py +43 -0
- memra/demos/etl_invoice_processing/test_vision_simple.py +60 -0
- {memra-0.2.12.dist-info → memra-0.2.13.dist-info}/METADATA +53 -78
- memra-0.2.13.dist-info/RECORD +120 -0
- {memra-0.2.12.dist-info → memra-0.2.13.dist-info}/WHEEL +1 -1
- memra-0.2.12.dist-info/RECORD +0 -68
- {memra-0.2.12.dist-info/licenses → memra-0.2.13.dist-info}/LICENSE +0 -0
- {memra-0.2.12.dist-info → memra-0.2.13.dist-info}/entry_points.txt +0 -0
- {memra-0.2.12.dist-info → memra-0.2.13.dist-info}/top_level.txt +0 -0
memra/cli.py
CHANGED
@@ -79,15 +79,121 @@ def setup_demo_environment():
|
|
79
79
|
def extract_bundled_files(demo_dir):
|
80
80
|
"""Extract files bundled with the PyPI package"""
|
81
81
|
try:
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
import pkg_resources
|
83
|
+
import shutil
|
84
|
+
from pathlib import Path
|
85
|
+
|
86
|
+
# Extract demo files from package data
|
87
|
+
demo_dir.mkdir(exist_ok=True)
|
88
|
+
|
89
|
+
# Copy the main ETL demo script
|
90
|
+
try:
|
91
|
+
demo_script = pkg_resources.resource_filename('memra', 'demos/etl_invoice_processing/etl_invoice_demo.py')
|
92
|
+
if Path(demo_script).exists():
|
93
|
+
shutil.copy2(demo_script, demo_dir / "etl_invoice_demo.py")
|
94
|
+
print("✅ Copied ETL demo script")
|
95
|
+
else:
|
96
|
+
print("⚠️ ETL demo script not found in package")
|
97
|
+
except Exception as e:
|
98
|
+
print(f"⚠️ Could not copy ETL demo script: {e}")
|
99
|
+
|
100
|
+
# Copy supporting Python files
|
101
|
+
demo_files = [
|
102
|
+
"database_monitor_agent.py",
|
103
|
+
"simple_pdf_processor.py",
|
104
|
+
"setup_demo_data.py"
|
105
|
+
]
|
106
|
+
|
107
|
+
for file_name in demo_files:
|
108
|
+
try:
|
109
|
+
file_path = pkg_resources.resource_filename('memra', f'demos/etl_invoice_processing/{file_name}')
|
110
|
+
if Path(file_path).exists():
|
111
|
+
shutil.copy2(file_path, demo_dir / file_name)
|
112
|
+
print(f"✅ Copied {file_name}")
|
113
|
+
else:
|
114
|
+
print(f"⚠️ {file_name} not found in package")
|
115
|
+
except Exception as e:
|
116
|
+
print(f"⚠️ Could not copy {file_name}: {e}")
|
117
|
+
|
118
|
+
# Copy sample data directory
|
119
|
+
try:
|
120
|
+
data_source = pkg_resources.resource_filename('memra', 'demos/etl_invoice_processing/data')
|
121
|
+
if Path(data_source).exists():
|
122
|
+
data_dir = demo_dir / "data"
|
123
|
+
shutil.copytree(data_source, data_dir, dirs_exist_ok=True)
|
124
|
+
print("✅ Copied sample invoice data")
|
87
125
|
else:
|
88
|
-
|
89
|
-
|
90
|
-
|
126
|
+
print("⚠️ Sample data not found in package")
|
127
|
+
except Exception as e:
|
128
|
+
print(f"⚠️ Could not copy sample data: {e}")
|
129
|
+
|
130
|
+
# Create memra-ops directory with docker-compose
|
131
|
+
ops_dir = demo_dir / "memra-ops"
|
132
|
+
ops_dir.mkdir(exist_ok=True)
|
133
|
+
|
134
|
+
# Create basic docker-compose.yml
|
135
|
+
compose_content = """version: '3.8'
|
136
|
+
services:
|
137
|
+
postgres:
|
138
|
+
image: postgres:15
|
139
|
+
environment:
|
140
|
+
POSTGRES_DB: local_workflow
|
141
|
+
POSTGRES_USER: postgres
|
142
|
+
POSTGRES_PASSWORD: postgres
|
143
|
+
ports:
|
144
|
+
- "5432:5432"
|
145
|
+
volumes:
|
146
|
+
- postgres_data:/var/lib/postgresql/data
|
147
|
+
|
148
|
+
volumes:
|
149
|
+
postgres_data:
|
150
|
+
"""
|
151
|
+
|
152
|
+
with open(ops_dir / "docker-compose.yml", "w") as f:
|
153
|
+
f.write(compose_content)
|
154
|
+
|
155
|
+
# Create basic MCP bridge server
|
156
|
+
mcp_content = """#!/usr/bin/env python3
|
157
|
+
import asyncio
|
158
|
+
import aiohttp
|
159
|
+
from aiohttp import web
|
160
|
+
import json
|
161
|
+
|
162
|
+
async def health_handler(request):
|
163
|
+
return web.json_response({"status": "healthy"})
|
164
|
+
|
165
|
+
async def execute_tool_handler(request):
|
166
|
+
data = await request.json()
|
167
|
+
tool_name = data.get('tool_name', 'unknown')
|
168
|
+
|
169
|
+
# Mock responses for demo
|
170
|
+
if tool_name == 'SQLExecutor':
|
171
|
+
return web.json_response({
|
172
|
+
"success": True,
|
173
|
+
"results": [{"message": "Demo SQL executed"}]
|
174
|
+
})
|
175
|
+
elif tool_name == 'PostgresInsert':
|
176
|
+
return web.json_response({
|
177
|
+
"success": True,
|
178
|
+
"id": 1
|
179
|
+
})
|
180
|
+
else:
|
181
|
+
return web.json_response({
|
182
|
+
"success": True,
|
183
|
+
"message": f"Demo {tool_name} executed"
|
184
|
+
})
|
185
|
+
|
186
|
+
app = web.Application()
|
187
|
+
app.router.add_get('/health', health_handler)
|
188
|
+
app.router.add_post('/execute_tool', execute_tool_handler)
|
189
|
+
|
190
|
+
if __name__ == '__main__':
|
191
|
+
web.run_app(app, host='0.0.0.0', port=8081)
|
192
|
+
"""
|
193
|
+
|
194
|
+
with open(ops_dir / "mcp_bridge_server.py", "w") as f:
|
195
|
+
f.write(mcp_content)
|
196
|
+
|
91
197
|
except Exception as e:
|
92
198
|
print(f"⚠️ Could not extract bundled files: {e}")
|
93
199
|
print("Creating minimal demo structure...")
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Check database after workflow
|
4
|
+
"""
|
5
|
+
|
6
|
+
import requests
|
7
|
+
import json
|
8
|
+
|
9
|
+
def check_database_after():
|
10
|
+
"""Show table contents after workflow"""
|
11
|
+
|
12
|
+
bridge_url = "http://localhost:8081"
|
13
|
+
bridge_secret = "test-secret-for-development"
|
14
|
+
|
15
|
+
headers = {
|
16
|
+
"Content-Type": "application/json",
|
17
|
+
"X-Bridge-Secret": bridge_secret
|
18
|
+
}
|
19
|
+
|
20
|
+
print("📊 Database contents AFTER workflow:")
|
21
|
+
payload = {
|
22
|
+
"tool_name": "SQLExecutor",
|
23
|
+
"input_data": {
|
24
|
+
"sql_query": "SELECT * FROM invoices ORDER BY id;"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
response = requests.post(f"{bridge_url}/execute_tool", json=payload, headers=headers)
|
29
|
+
if response.status_code == 200:
|
30
|
+
result = response.json()
|
31
|
+
if result.get("success"):
|
32
|
+
data = result["data"]
|
33
|
+
print(f"Query: {data['query']}")
|
34
|
+
print(f"Total Rows: {data['row_count']}")
|
35
|
+
print("\nAll records:")
|
36
|
+
for i, row in enumerate(data['results'], 1):
|
37
|
+
print(f"\n{i}. ID: {row['id']}")
|
38
|
+
print(f" Invoice Number: {row['invoice_number']}")
|
39
|
+
print(f" Vendor: {row['vendor_name']}")
|
40
|
+
print(f" Date: {row['invoice_date']}")
|
41
|
+
print(f" Amount: ${row['total_amount']}")
|
42
|
+
print(f" Status: {row['status']}")
|
43
|
+
print(f" Created: {row['created_at']}")
|
44
|
+
else:
|
45
|
+
print(f"Error: {result.get('error')}")
|
46
|
+
else:
|
47
|
+
print(f"HTTP Error: {response.status_code}")
|
48
|
+
|
49
|
+
if __name__ == "__main__":
|
50
|
+
check_database_after()
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import psycopg2
|
2
|
+
import json
|
3
|
+
|
4
|
+
try:
|
5
|
+
conn = psycopg2.connect(
|
6
|
+
host='localhost',
|
7
|
+
port=5432,
|
8
|
+
database='memra_invoice_db',
|
9
|
+
user='memra',
|
10
|
+
password='memra123'
|
11
|
+
)
|
12
|
+
|
13
|
+
cursor = conn.cursor()
|
14
|
+
|
15
|
+
# Get detailed records
|
16
|
+
cursor.execute('SELECT * FROM invoices ORDER BY created_at DESC LIMIT 5;')
|
17
|
+
rows = cursor.fetchall()
|
18
|
+
|
19
|
+
print('📄 Detailed Invoice Records:')
|
20
|
+
for row in rows:
|
21
|
+
print(f'\nID: {row[0]}')
|
22
|
+
print(f' Invoice Number: {row[1]}')
|
23
|
+
print(f' Vendor: {row[2]}')
|
24
|
+
print(f' Invoice Date: {row[3]}')
|
25
|
+
print(f' Due Date: {row[4]}')
|
26
|
+
print(f' Total Amount: ${row[5]}')
|
27
|
+
print(f' Tax Amount: ${row[6]}')
|
28
|
+
print(f' Status: {row[8]}')
|
29
|
+
print(f' Created: {row[9]}')
|
30
|
+
|
31
|
+
if row[7]: # line_items
|
32
|
+
try:
|
33
|
+
line_items = json.loads(row[7])
|
34
|
+
print(f' Line Items: {len(line_items)} items')
|
35
|
+
for i, item in enumerate(line_items[:2]): # Show first 2 items
|
36
|
+
print(f' {i+1}. {item.get("description", "N/A")} - Qty: {item.get("quantity", "N/A")} @ ${item.get("unit_price", "N/A")}')
|
37
|
+
except:
|
38
|
+
print(f' Line Items: {row[7]}')
|
39
|
+
|
40
|
+
cursor.close()
|
41
|
+
conn.close()
|
42
|
+
|
43
|
+
except Exception as e:
|
44
|
+
print(f'❌ Database connection failed: {e}')
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import psycopg2
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
# Database connection
|
5
|
+
conn = psycopg2.connect(
|
6
|
+
host="localhost",
|
7
|
+
database="local_workflow",
|
8
|
+
user="postgres",
|
9
|
+
password="postgres",
|
10
|
+
port=5433
|
11
|
+
)
|
12
|
+
|
13
|
+
try:
|
14
|
+
with conn.cursor() as cur:
|
15
|
+
# Get the 10 most recent records
|
16
|
+
cur.execute("""
|
17
|
+
SELECT id, vendor_name, invoice_number, invoice_date, due_date,
|
18
|
+
total_amount, tax_amount, created_at
|
19
|
+
FROM invoices
|
20
|
+
ORDER BY id DESC
|
21
|
+
LIMIT 10
|
22
|
+
""")
|
23
|
+
|
24
|
+
records = cur.fetchall()
|
25
|
+
|
26
|
+
if records:
|
27
|
+
print("🔍 Most Recent Invoice Records:\n")
|
28
|
+
for record in records:
|
29
|
+
id, vendor, invoice_num, inv_date, due_date, total, tax, created = record
|
30
|
+
print(f"ID: {id} (Created: {created})")
|
31
|
+
print(f" Vendor: {vendor}")
|
32
|
+
print(f" Invoice #: {invoice_num}")
|
33
|
+
print(f" Invoice Date: {inv_date}")
|
34
|
+
print(f" Due Date: {due_date}")
|
35
|
+
print(f" Total: ${total:.2f}")
|
36
|
+
print(f" Tax: ${tax:.2f}")
|
37
|
+
print("-" * 40)
|
38
|
+
else:
|
39
|
+
print("No records found in database")
|
40
|
+
|
41
|
+
finally:
|
42
|
+
conn.close()
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Demo Data Files
|
2
|
+
|
3
|
+
This directory contains sample data files for the ETL Invoice Processing demo.
|
4
|
+
|
5
|
+
## 📁 Directory Structure
|
6
|
+
|
7
|
+
```
|
8
|
+
data/
|
9
|
+
├── invoices/ # PDF invoice files for processing
|
10
|
+
│ ├── invoice_001.pdf
|
11
|
+
│ ├── invoice_002.pdf
|
12
|
+
│ └── ...
|
13
|
+
└── README.md # This file
|
14
|
+
```
|
15
|
+
|
16
|
+
## 📄 Invoice Files
|
17
|
+
|
18
|
+
The `invoices/` directory contains sample PDF invoice files that demonstrate:
|
19
|
+
|
20
|
+
- **Various invoice formats** from different vendors
|
21
|
+
- **Different data structures** (line items, totals, tax calculations)
|
22
|
+
- **Real-world scenarios** (missing data, edge cases)
|
23
|
+
- **Multiple currencies** and payment terms
|
24
|
+
|
25
|
+
## 🚀 Using the Demo Data
|
26
|
+
|
27
|
+
### Option 1: Auto-Discovery (Recommended)
|
28
|
+
The demo will automatically scan the `invoices/` directory:
|
29
|
+
|
30
|
+
```python
|
31
|
+
# The demo will find and process all PDF files
|
32
|
+
python etl_invoice_demo.py
|
33
|
+
```
|
34
|
+
|
35
|
+
### Option 2: Specific File Processing
|
36
|
+
Process a specific invoice file:
|
37
|
+
|
38
|
+
```python
|
39
|
+
# Update the demo to process a specific file
|
40
|
+
input_data = {
|
41
|
+
"invoice_file": "data/invoices/invoice_001.pdf",
|
42
|
+
"connection": "postgresql://memra:memra123@localhost:5432/memra_invoice_db"
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
### Option 3: External File Processing
|
47
|
+
Copy files from external locations:
|
48
|
+
|
49
|
+
```python
|
50
|
+
# The demo can copy files from Downloads or other locations
|
51
|
+
input_data = {
|
52
|
+
"source_path": "~/Downloads/new_invoice.pdf",
|
53
|
+
"connection": "postgresql://memra:memra123@localhost:5432/memra_invoice_db"
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
## 📊 Expected Data Structure
|
58
|
+
|
59
|
+
Each invoice file should contain:
|
60
|
+
|
61
|
+
- **Vendor Information**: Company name, address, contact details
|
62
|
+
- **Invoice Details**: Invoice number, date, due date
|
63
|
+
- **Line Items**: Description, quantity, unit price, total
|
64
|
+
- **Totals**: Subtotal, tax, shipping, grand total
|
65
|
+
- **Payment Terms**: Due date, payment methods
|
66
|
+
|
67
|
+
## 🔧 Customizing the Data
|
68
|
+
|
69
|
+
### Adding New Invoice Files
|
70
|
+
1. Place new PDF files in the `invoices/` directory
|
71
|
+
2. Ensure they follow the expected invoice format
|
72
|
+
3. Test with the demo to verify processing
|
73
|
+
|
74
|
+
### Modifying Existing Files
|
75
|
+
- Files are processed using AI vision models
|
76
|
+
- No specific format requirements
|
77
|
+
- The system adapts to different invoice layouts
|
78
|
+
|
79
|
+
## 📈 Demo Scenarios
|
80
|
+
|
81
|
+
The included files demonstrate:
|
82
|
+
|
83
|
+
| Scenario | Description |
|
84
|
+
|----------|-------------|
|
85
|
+
| **Standard Invoice** | Typical business invoice with line items |
|
86
|
+
| **Complex Invoice** | Multiple pages, detailed line items |
|
87
|
+
| **Simple Invoice** | Basic invoice with minimal details |
|
88
|
+
| **International** | Different currencies and formats |
|
89
|
+
| **Edge Cases** | Missing data, unusual formats |
|
90
|
+
|
91
|
+
## 🚨 Important Notes
|
92
|
+
|
93
|
+
- **File Size**: Each file is approximately 1MB
|
94
|
+
- **Total Size**: ~20MB for all demo files
|
95
|
+
- **Git LFS**: Not required for these file sizes
|
96
|
+
- **Version Control**: Files are tracked in Git for demo consistency
|
97
|
+
|
98
|
+
## 🔄 Updating Demo Data
|
99
|
+
|
100
|
+
When adding new invoice files:
|
101
|
+
|
102
|
+
1. **Test locally** first
|
103
|
+
2. **Verify processing** with the demo
|
104
|
+
3. **Update this README** if adding new scenarios
|
105
|
+
4. **Commit changes** with descriptive messages
|
106
|
+
|
107
|
+
## 📚 Related Documentation
|
108
|
+
|
109
|
+
- [ETL Demo Guide](../README.md)
|
110
|
+
- [Database Schema](../../../docs/database_schema.sql)
|
111
|
+
- [Sample Data](../../../docs/sample_data.sql)
|
112
|
+
- [Quick Start Guide](../../../QUICK_START.md)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,89 @@
|
|
1
|
+
"""
|
2
|
+
Database Monitor Agent
|
3
|
+
Monitors database state before and after ETL processes
|
4
|
+
"""
|
5
|
+
|
6
|
+
from memra import Agent, LLM
|
7
|
+
|
8
|
+
def create_database_monitor_agent():
|
9
|
+
"""Create a database monitoring agent"""
|
10
|
+
|
11
|
+
monitor_llm = LLM(
|
12
|
+
model="llama-3.2-11b-vision-preview",
|
13
|
+
temperature=0.1,
|
14
|
+
max_tokens=1000
|
15
|
+
)
|
16
|
+
|
17
|
+
monitor_agent = Agent(
|
18
|
+
role="Database Monitor",
|
19
|
+
job="Monitor database state and validate data integrity",
|
20
|
+
llm=monitor_llm,
|
21
|
+
sops=[
|
22
|
+
"Connect to database using provided credentials",
|
23
|
+
"Execute monitoring queries to count rows and validate data",
|
24
|
+
"Generate comprehensive monitoring report with statistics",
|
25
|
+
"Flag any data integrity issues or anomalies",
|
26
|
+
"Return structured monitoring results"
|
27
|
+
],
|
28
|
+
systems=["Database"],
|
29
|
+
tools=[
|
30
|
+
{"name": "SQLExecutor", "hosted_by": "mcp"}
|
31
|
+
],
|
32
|
+
input_keys=["table_name", "connection", "monitoring_phase"],
|
33
|
+
output_key="monitoring_report"
|
34
|
+
)
|
35
|
+
|
36
|
+
return monitor_agent
|
37
|
+
|
38
|
+
def get_monitoring_queries(table_name: str, phase: str):
|
39
|
+
"""Get appropriate SQL queries for monitoring phase"""
|
40
|
+
|
41
|
+
queries = {
|
42
|
+
"before": [
|
43
|
+
f"SELECT COUNT(*) as row_count FROM {table_name}",
|
44
|
+
f"SELECT COUNT(*) as null_vendor_count FROM {table_name} WHERE vendor_name IS NULL",
|
45
|
+
f"SELECT COUNT(*) as null_invoice_count FROM {table_name} WHERE invoice_number IS NULL",
|
46
|
+
f"SELECT COUNT(*) as null_amount_count FROM {table_name} WHERE total_amount IS NULL"
|
47
|
+
],
|
48
|
+
"after": [
|
49
|
+
f"SELECT COUNT(*) as row_count FROM {table_name}",
|
50
|
+
f"SELECT COUNT(*) as null_vendor_count FROM {table_name} WHERE vendor_name IS NULL",
|
51
|
+
f"SELECT COUNT(*) as null_invoice_count FROM {table_name} WHERE invoice_number IS NULL",
|
52
|
+
f"SELECT COUNT(*) as null_amount_count FROM {table_name} WHERE total_amount IS NULL",
|
53
|
+
f"SELECT COUNT(*) as duplicate_invoices FROM (SELECT invoice_number, COUNT(*) as cnt FROM {table_name} GROUP BY invoice_number HAVING COUNT(*) > 1) as dups",
|
54
|
+
f"SELECT MIN(total_amount) as min_amount, MAX(total_amount) as max_amount, AVG(total_amount) as avg_amount FROM {table_name}",
|
55
|
+
f"SELECT COUNT(*) as recent_records FROM {table_name} WHERE created_at >= NOW() - INTERVAL '1 hour'"
|
56
|
+
]
|
57
|
+
}
|
58
|
+
|
59
|
+
return queries.get(phase, queries["after"])
|
60
|
+
|
61
|
+
def create_simple_monitor_agent():
|
62
|
+
"""Create a simple database monitoring agent that works with the framework"""
|
63
|
+
|
64
|
+
monitor_llm = LLM(
|
65
|
+
model="llama-3.2-11b-vision-preview",
|
66
|
+
temperature=0.1,
|
67
|
+
max_tokens=1500
|
68
|
+
)
|
69
|
+
|
70
|
+
monitor_agent = Agent(
|
71
|
+
role="Database Monitor",
|
72
|
+
job="Monitor database state and validate data integrity",
|
73
|
+
llm=monitor_llm,
|
74
|
+
sops=[
|
75
|
+
"Connect to database using provided credentials",
|
76
|
+
"Execute monitoring query using sql_query input",
|
77
|
+
"Generate monitoring report with current statistics",
|
78
|
+
"Flag any data integrity issues",
|
79
|
+
"Return structured monitoring results"
|
80
|
+
],
|
81
|
+
systems=["Database"],
|
82
|
+
tools=[
|
83
|
+
{"name": "SQLExecutor", "hosted_by": "mcp", "input_keys": ["sql_query"]}
|
84
|
+
],
|
85
|
+
input_keys=["table_name", "connection", "monitoring_phase", "sql_query"],
|
86
|
+
output_key="monitoring_report"
|
87
|
+
)
|
88
|
+
|
89
|
+
return monitor_agent
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Debug script to test MCP bridge connection
|
4
|
+
"""
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
|
10
|
+
# Set up logging
|
11
|
+
logging.basicConfig(level=logging.INFO)
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
def test_mcp_bridge():
|
15
|
+
"""Test MCP bridge connection using httpx"""
|
16
|
+
|
17
|
+
bridge_url = "http://localhost:8081"
|
18
|
+
bridge_secret = "test-secret-for-development"
|
19
|
+
|
20
|
+
# Prepare request
|
21
|
+
payload = {
|
22
|
+
"tool_name": "SQLExecutor",
|
23
|
+
"input_data": {
|
24
|
+
"sql_query": "SELECT COUNT(*) as row_count FROM invoices"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
headers = {
|
29
|
+
"Content-Type": "application/json",
|
30
|
+
"X-Bridge-Secret": bridge_secret
|
31
|
+
}
|
32
|
+
|
33
|
+
logger.info(f"Testing MCP bridge at {bridge_url}")
|
34
|
+
logger.info(f"Payload: {json.dumps(payload, indent=2)}")
|
35
|
+
logger.info(f"Headers: {headers}")
|
36
|
+
|
37
|
+
try:
|
38
|
+
with httpx.Client(timeout=60.0) as client:
|
39
|
+
logger.info("Making HTTP request...")
|
40
|
+
response = client.post(f"{bridge_url}/execute_tool", json=payload, headers=headers)
|
41
|
+
|
42
|
+
logger.info(f"Response status: {response.status_code}")
|
43
|
+
logger.info(f"Response headers: {dict(response.headers)}")
|
44
|
+
|
45
|
+
if response.status_code == 200:
|
46
|
+
result = response.json()
|
47
|
+
logger.info(f"Success! Result: {json.dumps(result, indent=2)}")
|
48
|
+
return True
|
49
|
+
else:
|
50
|
+
logger.error(f"HTTP error: {response.status_code}")
|
51
|
+
logger.error(f"Response text: {response.text}")
|
52
|
+
return False
|
53
|
+
|
54
|
+
except httpx.TimeoutException:
|
55
|
+
logger.error("Request timed out")
|
56
|
+
return False
|
57
|
+
except httpx.HTTPStatusError as e:
|
58
|
+
logger.error(f"HTTP error: {e.response.status_code} - {e.response.text}")
|
59
|
+
return False
|
60
|
+
except Exception as e:
|
61
|
+
logger.error(f"Unexpected error: {str(e)}")
|
62
|
+
return False
|
63
|
+
|
64
|
+
if __name__ == "__main__":
|
65
|
+
success = test_mcp_bridge()
|
66
|
+
print(f"\n{'✅ SUCCESS' if success else '❌ FAILED'}")
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import os
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
|
5
|
+
api_url = "https://api.memra.co"
|
6
|
+
api_key = os.getenv("MEMRA_API_KEY", "test-secret-for-development")
|
7
|
+
|
8
|
+
# Test the exact format we're sending
|
9
|
+
schema = {
|
10
|
+
"columns": [
|
11
|
+
{"name": "invoice_number", "type": "character varying"},
|
12
|
+
{"name": "vendor_name", "type": "character varying"},
|
13
|
+
{"name": "invoice_date", "type": "date"},
|
14
|
+
{"name": "total_amount", "type": "numeric"}
|
15
|
+
]
|
16
|
+
}
|
17
|
+
|
18
|
+
print("Schema being sent:")
|
19
|
+
print(json.dumps(schema, indent=2))
|
20
|
+
|
21
|
+
resp = requests.post(
|
22
|
+
f"{api_url}/tools/execute",
|
23
|
+
json={
|
24
|
+
"tool_name": "PDFProcessor",
|
25
|
+
"hosted_by": "memra",
|
26
|
+
"input_data": {
|
27
|
+
"file": "/uploads/6f4538c0-8fce-4488-be49-1a78afc58a4a.pdf",
|
28
|
+
"schema": schema
|
29
|
+
}
|
30
|
+
},
|
31
|
+
headers={"X-API-Key": api_key}
|
32
|
+
)
|
33
|
+
|
34
|
+
print(f"\nResponse status: {resp.status_code}")
|
35
|
+
if resp.status_code == 200:
|
36
|
+
result = resp.json()
|
37
|
+
if result.get('success') and 'data' in result and 'data' in result['data']:
|
38
|
+
data = result['data']['data']
|
39
|
+
print(f"\nVision prompt length: {len(data.get('vision_prompt', ''))}")
|
40
|
+
print("Vision prompt preview:")
|
41
|
+
print(data.get('vision_prompt', '')[:500])
|
42
|
+
|
43
|
+
# Check if vendor was mentioned in response
|
44
|
+
vision_resp = data.get('vision_response', '')
|
45
|
+
print(f"\nVision response includes 'vendor': {'vendor' in vision_resp.lower()}")
|