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.
- memra/cli.py +322 -51
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/METADATA +1 -1
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/RECORD +7 -61
- memra-0.2.15.dist-info/top_level.txt +1 -0
- memra-0.2.13.dist-info/top_level.txt +0 -4
- memra-ops/app.py +0 -808
- memra-ops/config/config.py +0 -25
- memra-ops/config.py +0 -34
- memra-ops/logic/__init__.py +0 -1
- memra-ops/logic/file_tools.py +0 -43
- memra-ops/logic/invoice_tools.py +0 -668
- memra-ops/logic/invoice_tools_fix.py +0 -66
- memra-ops/mcp_bridge_server.py +0 -1178
- memra-ops/scripts/check_database.py +0 -37
- memra-ops/scripts/clear_database.py +0 -48
- memra-ops/scripts/monitor_database.py +0 -67
- memra-ops/scripts/release.py +0 -133
- memra-ops/scripts/reset_database.py +0 -65
- memra-ops/scripts/start_memra.py +0 -334
- memra-ops/scripts/stop_memra.py +0 -132
- memra-ops/server_tool_registry.py +0 -190
- memra-ops/tests/test_llm_text_to_sql.py +0 -115
- memra-ops/tests/test_llm_vs_pattern.py +0 -130
- memra-ops/tests/test_mcp_schema_aware.py +0 -124
- memra-ops/tests/test_schema_aware_sql.py +0 -139
- memra-ops/tests/test_schema_aware_sql_simple.py +0 -66
- memra-ops/tests/test_text_to_sql_demo.py +0 -140
- memra-ops/tools/mcp_bridge_server.py +0 -851
- memra-sdk/examples/accounts_payable.py +0 -215
- memra-sdk/examples/accounts_payable_client.py +0 -217
- memra-sdk/examples/accounts_payable_mcp.py +0 -200
- memra-sdk/examples/ask_questions.py +0 -123
- memra-sdk/examples/invoice_processing.py +0 -116
- memra-sdk/examples/propane_delivery.py +0 -87
- memra-sdk/examples/simple_text_to_sql.py +0 -158
- memra-sdk/memra/__init__.py +0 -31
- memra-sdk/memra/discovery.py +0 -15
- memra-sdk/memra/discovery_client.py +0 -49
- memra-sdk/memra/execution.py +0 -481
- memra-sdk/memra/models.py +0 -99
- memra-sdk/memra/tool_registry.py +0 -343
- memra-sdk/memra/tool_registry_client.py +0 -106
- memra-sdk/scripts/release.py +0 -133
- memra-sdk/setup.py +0 -52
- memra-workflows/accounts_payable/accounts_payable.py +0 -215
- memra-workflows/accounts_payable/accounts_payable_client.py +0 -216
- memra-workflows/accounts_payable/accounts_payable_mcp.py +0 -200
- memra-workflows/accounts_payable/accounts_payable_smart.py +0 -221
- memra-workflows/invoice_processing/invoice_processing.py +0 -116
- memra-workflows/invoice_processing/smart_invoice_processor.py +0 -220
- memra-workflows/logic/__init__.py +0 -1
- memra-workflows/logic/file_tools.py +0 -50
- memra-workflows/logic/invoice_tools.py +0 -501
- memra-workflows/logic/propane_agents.py +0 -52
- memra-workflows/mcp_bridge_server.py +0 -230
- memra-workflows/propane_delivery/propane_delivery.py +0 -87
- memra-workflows/text_to_sql/complete_invoice_workflow_with_queries.py +0 -208
- memra-workflows/text_to_sql/complete_text_to_sql_system.py +0 -266
- memra-workflows/text_to_sql/file_discovery_demo.py +0 -156
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/LICENSE +0 -0
- {memra-0.2.13.dist-info → memra-0.2.15.dist-info}/WHEEL +0 -0
- {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()
|