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
memra-ops/scripts/stop_memra.py
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Memra System Stop Script
|
4
|
-
Gracefully stops all Memra system dependencies
|
5
|
-
"""
|
6
|
-
|
7
|
-
import os
|
8
|
-
import sys
|
9
|
-
import subprocess
|
10
|
-
import requests
|
11
|
-
import signal
|
12
|
-
from pathlib import Path
|
13
|
-
|
14
|
-
class MemraStop:
|
15
|
-
def __init__(self):
|
16
|
-
self.project_root = Path(__file__).parent.parent
|
17
|
-
|
18
|
-
def print_banner(self):
|
19
|
-
"""Print stop banner"""
|
20
|
-
print("=" * 60)
|
21
|
-
print("🛑 MEMRA SYSTEM SHUTDOWN")
|
22
|
-
print("=" * 60)
|
23
|
-
print("Stopping all Memra system dependencies...")
|
24
|
-
print()
|
25
|
-
|
26
|
-
def stop_mcp_bridge(self):
|
27
|
-
"""Stop the MCP bridge server"""
|
28
|
-
print("🌉 Stopping MCP Bridge Server...")
|
29
|
-
|
30
|
-
try:
|
31
|
-
# Try to send a graceful shutdown signal
|
32
|
-
response = requests.get('http://localhost:8081/health', timeout=5)
|
33
|
-
if response.status_code == 200:
|
34
|
-
print(" MCP Bridge Server is running, stopping...")
|
35
|
-
|
36
|
-
# Find and kill the MCP bridge process
|
37
|
-
result = subprocess.run(['pkill', '-f', 'mcp_bridge_server.py'],
|
38
|
-
capture_output=True, text=True)
|
39
|
-
|
40
|
-
if result.returncode == 0:
|
41
|
-
print("✅ MCP Bridge Server stopped")
|
42
|
-
else:
|
43
|
-
print("⚠️ MCP Bridge Server process not found (may already be stopped)")
|
44
|
-
else:
|
45
|
-
print("✅ MCP Bridge Server is not running")
|
46
|
-
|
47
|
-
except requests.RequestException:
|
48
|
-
print("✅ MCP Bridge Server is not running")
|
49
|
-
|
50
|
-
def stop_postgresql(self):
|
51
|
-
"""Stop PostgreSQL using Docker Compose"""
|
52
|
-
print("🐘 Stopping PostgreSQL...")
|
53
|
-
|
54
|
-
try:
|
55
|
-
# Check if PostgreSQL container is running
|
56
|
-
result = subprocess.run(['docker', 'ps', '--filter', 'name=memra-postgres'],
|
57
|
-
capture_output=True, text=True)
|
58
|
-
|
59
|
-
if 'memra-postgres' in result.stdout:
|
60
|
-
print(" PostgreSQL container is running, stopping...")
|
61
|
-
|
62
|
-
# Stop PostgreSQL container
|
63
|
-
result = subprocess.run(['docker-compose', 'stop', 'postgres'],
|
64
|
-
cwd=self.project_root, capture_output=True, text=True)
|
65
|
-
|
66
|
-
if result.returncode == 0:
|
67
|
-
print("✅ PostgreSQL stopped")
|
68
|
-
else:
|
69
|
-
print(f"⚠️ Warning: Failed to stop PostgreSQL: {result.stderr}")
|
70
|
-
else:
|
71
|
-
print("✅ PostgreSQL is not running")
|
72
|
-
|
73
|
-
except Exception as e:
|
74
|
-
print(f"⚠️ Warning: Error stopping PostgreSQL: {e}")
|
75
|
-
|
76
|
-
def cleanup_docker(self):
|
77
|
-
"""Clean up any orphaned Docker containers"""
|
78
|
-
print("🧹 Cleaning up Docker containers...")
|
79
|
-
|
80
|
-
try:
|
81
|
-
# Remove any stopped containers
|
82
|
-
result = subprocess.run(['docker', 'container', 'prune', '-f'],
|
83
|
-
capture_output=True, text=True)
|
84
|
-
|
85
|
-
if result.returncode == 0:
|
86
|
-
print("✅ Docker containers cleaned up")
|
87
|
-
else:
|
88
|
-
print("⚠️ Warning: Failed to clean up Docker containers")
|
89
|
-
|
90
|
-
except Exception as e:
|
91
|
-
print(f"⚠️ Warning: Error cleaning up Docker: {e}")
|
92
|
-
|
93
|
-
def stop(self):
|
94
|
-
"""Main stop sequence"""
|
95
|
-
try:
|
96
|
-
self.print_banner()
|
97
|
-
|
98
|
-
# Stop MCP bridge server
|
99
|
-
self.stop_mcp_bridge()
|
100
|
-
|
101
|
-
# Stop PostgreSQL
|
102
|
-
self.stop_postgresql()
|
103
|
-
|
104
|
-
# Clean up Docker
|
105
|
-
self.cleanup_docker()
|
106
|
-
|
107
|
-
print("\n" + "=" * 60)
|
108
|
-
print("✅ MEMRA SYSTEM STOPPED SUCCESSFULLY!")
|
109
|
-
print("=" * 60)
|
110
|
-
print("All services have been stopped:")
|
111
|
-
print(" • MCP Bridge Server")
|
112
|
-
print(" • PostgreSQL Database")
|
113
|
-
print(" • Docker containers cleaned up")
|
114
|
-
print()
|
115
|
-
print("💡 To start the system again, run:")
|
116
|
-
print(" ./scripts/start_memra.sh")
|
117
|
-
print("=" * 60)
|
118
|
-
|
119
|
-
except Exception as e:
|
120
|
-
print(f"❌ Stop failed: {e}")
|
121
|
-
return False
|
122
|
-
|
123
|
-
return True
|
124
|
-
|
125
|
-
def main():
|
126
|
-
"""Main entry point"""
|
127
|
-
stop = MemraStop()
|
128
|
-
success = stop.stop()
|
129
|
-
sys.exit(0 if success else 1)
|
130
|
-
|
131
|
-
if __name__ == "__main__":
|
132
|
-
main()
|
@@ -1,190 +0,0 @@
|
|
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 ServerToolRegistry:
|
11
|
-
"""Server-side registry for managing and executing tools from logic directory"""
|
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 (project root)
|
21
|
-
current_dir = Path(__file__).parent
|
22
|
-
|
23
|
-
if str(current_dir) not in sys.path:
|
24
|
-
sys.path.insert(0, str(current_dir))
|
25
|
-
|
26
|
-
def _load_builtin_tools(self):
|
27
|
-
"""Load tools from the logic directory"""
|
28
|
-
try:
|
29
|
-
# Load invoice tools
|
30
|
-
from logic.invoice_tools import (
|
31
|
-
DatabaseQueryTool, PDFProcessor, OCRTool,
|
32
|
-
InvoiceExtractionWorkflow, DataValidator, PostgresInsert
|
33
|
-
)
|
34
|
-
|
35
|
-
self.register_tool("DatabaseQueryTool", DatabaseQueryTool, "memra",
|
36
|
-
"Query database schemas and data")
|
37
|
-
self.register_tool("PDFProcessor", PDFProcessor, "memra",
|
38
|
-
"Process PDF files and extract content")
|
39
|
-
self.register_tool("OCRTool", OCRTool, "memra",
|
40
|
-
"Perform OCR on images and documents")
|
41
|
-
self.register_tool("InvoiceExtractionWorkflow", InvoiceExtractionWorkflow, "memra",
|
42
|
-
"Extract structured data from invoices")
|
43
|
-
self.register_tool("DataValidator", DataValidator, "memra",
|
44
|
-
"Validate data against schemas")
|
45
|
-
self.register_tool("PostgresInsert", PostgresInsert, "memra",
|
46
|
-
"Insert data into PostgreSQL database")
|
47
|
-
|
48
|
-
# Load file tools
|
49
|
-
from logic.file_tools import FileReader
|
50
|
-
self.register_tool("FileReader", FileReader, "memra",
|
51
|
-
"Read files from the filesystem")
|
52
|
-
|
53
|
-
logger.info(f"Loaded {len(self.tools)} builtin tools")
|
54
|
-
|
55
|
-
except ImportError as e:
|
56
|
-
logger.warning(f"Could not load some tools: {e}")
|
57
|
-
|
58
|
-
def register_tool(self, name: str, tool_class: type, hosted_by: str, description: str):
|
59
|
-
"""Register a tool in the registry"""
|
60
|
-
self.tools[name] = {
|
61
|
-
"class": tool_class,
|
62
|
-
"hosted_by": hosted_by,
|
63
|
-
"description": description
|
64
|
-
}
|
65
|
-
logger.debug(f"Registered tool: {name} (hosted by {hosted_by})")
|
66
|
-
|
67
|
-
def discover_tools(self, hosted_by: Optional[str] = None) -> List[Dict[str, Any]]:
|
68
|
-
"""Discover available tools, optionally filtered by host"""
|
69
|
-
tools = []
|
70
|
-
for name, info in self.tools.items():
|
71
|
-
if hosted_by is None or info["hosted_by"] == hosted_by:
|
72
|
-
tools.append({
|
73
|
-
"name": name,
|
74
|
-
"hosted_by": info["hosted_by"],
|
75
|
-
"description": info["description"]
|
76
|
-
})
|
77
|
-
return tools
|
78
|
-
|
79
|
-
def execute_tool(self, tool_name: str, hosted_by: str, input_data: Dict[str, Any],
|
80
|
-
config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
81
|
-
"""Execute a tool with the given input data"""
|
82
|
-
if tool_name not in self.tools:
|
83
|
-
return {
|
84
|
-
"success": False,
|
85
|
-
"error": f"Tool '{tool_name}' not found in registry"
|
86
|
-
}
|
87
|
-
|
88
|
-
tool_info = self.tools[tool_name]
|
89
|
-
if tool_info["hosted_by"] != hosted_by:
|
90
|
-
return {
|
91
|
-
"success": False,
|
92
|
-
"error": f"Tool '{tool_name}' is hosted by '{tool_info['hosted_by']}', not '{hosted_by}'"
|
93
|
-
}
|
94
|
-
|
95
|
-
try:
|
96
|
-
# Instantiate tool
|
97
|
-
tool_class = tool_info["class"]
|
98
|
-
|
99
|
-
# Some tools need credentials/config for initialization
|
100
|
-
if tool_name in ["DatabaseQueryTool", "PostgresInsert"]:
|
101
|
-
if "connection" in input_data:
|
102
|
-
# Parse connection string or use credentials
|
103
|
-
credentials = self._parse_connection(input_data["connection"])
|
104
|
-
tool_instance = tool_class(credentials)
|
105
|
-
else:
|
106
|
-
return {
|
107
|
-
"success": False,
|
108
|
-
"error": f"Tool '{tool_name}' requires database credentials"
|
109
|
-
}
|
110
|
-
elif tool_name == "InvoiceExtractionWorkflow":
|
111
|
-
# This tool needs to be instantiated to initialize the LLM client
|
112
|
-
tool_instance = tool_class()
|
113
|
-
else:
|
114
|
-
tool_instance = tool_class()
|
115
|
-
|
116
|
-
# Execute tool based on its type
|
117
|
-
result = self._execute_tool_method(tool_instance, tool_name, input_data, config)
|
118
|
-
|
119
|
-
return {
|
120
|
-
"success": True,
|
121
|
-
"data": result
|
122
|
-
}
|
123
|
-
|
124
|
-
except Exception as e:
|
125
|
-
logger.error(f"Tool execution failed for {tool_name}: {str(e)}")
|
126
|
-
return {
|
127
|
-
"success": False,
|
128
|
-
"error": str(e)
|
129
|
-
}
|
130
|
-
|
131
|
-
def _execute_tool_method(self, tool_instance: Any, tool_name: str,
|
132
|
-
input_data: Dict[str, Any], config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
133
|
-
"""Execute the appropriate method on the tool instance"""
|
134
|
-
|
135
|
-
if tool_name == "DatabaseQueryTool":
|
136
|
-
return tool_instance.get_schema("invoices") # Default to invoices table
|
137
|
-
|
138
|
-
elif tool_name == "PDFProcessor":
|
139
|
-
file_path = input_data.get("file", "")
|
140
|
-
# Handle both "schema" and "invoice_schema" keys
|
141
|
-
schema = input_data.get("schema") or input_data.get("invoice_schema", {})
|
142
|
-
return tool_instance.process_pdf(file_path, schema)
|
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}
|
@@ -1,115 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Test script to verify LLM-based text-to-SQL generation
|
4
|
-
"""
|
5
|
-
|
6
|
-
import requests
|
7
|
-
import json
|
8
|
-
import time
|
9
|
-
|
10
|
-
def test_llm_text_to_sql():
|
11
|
-
"""Test the new LLM-based text-to-SQL generation"""
|
12
|
-
bridge_url = "http://localhost:8081"
|
13
|
-
bridge_secret = "test-secret-for-development"
|
14
|
-
|
15
|
-
# Test questions to verify LLM vs pattern matching
|
16
|
-
test_questions = [
|
17
|
-
"Show me all invoices from Air Liquide",
|
18
|
-
"What is the total amount of all invoices?",
|
19
|
-
"How many invoices do we have?",
|
20
|
-
"Show me the 3 most recent invoices",
|
21
|
-
"What is the average invoice amount?",
|
22
|
-
"Find invoices with amounts greater than 1000", # This should test LLM capabilities
|
23
|
-
"Show me invoices from last month", # This should test LLM capabilities
|
24
|
-
"Which vendor has the highest total invoice amount?" # Complex query for LLM
|
25
|
-
]
|
26
|
-
|
27
|
-
# Schema info for context
|
28
|
-
schema_info = {
|
29
|
-
"schema": {
|
30
|
-
"invoices": {
|
31
|
-
"columns": [
|
32
|
-
{"name": "id", "type": "integer"},
|
33
|
-
{"name": "vendor_name", "type": "text"},
|
34
|
-
{"name": "invoice_number", "type": "text"},
|
35
|
-
{"name": "invoice_date", "type": "date"},
|
36
|
-
{"name": "total_amount", "type": "numeric"},
|
37
|
-
{"name": "tax_amount", "type": "numeric"},
|
38
|
-
{"name": "line_items", "type": "jsonb"},
|
39
|
-
{"name": "status", "type": "text"}
|
40
|
-
]
|
41
|
-
}
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
headers = {
|
46
|
-
"Content-Type": "application/json",
|
47
|
-
"X-Bridge-Secret": bridge_secret
|
48
|
-
}
|
49
|
-
|
50
|
-
print("🧪 Testing LLM-based Text-to-SQL Generation")
|
51
|
-
print("=" * 60)
|
52
|
-
|
53
|
-
for i, question in enumerate(test_questions, 1):
|
54
|
-
print(f"\n🎯 Test {i}: {question}")
|
55
|
-
print("-" * 50)
|
56
|
-
|
57
|
-
# Prepare request
|
58
|
-
request_data = {
|
59
|
-
"tool_name": "TextToSQLGenerator",
|
60
|
-
"input_data": {
|
61
|
-
"question": question,
|
62
|
-
"schema_info": schema_info
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
|
-
try:
|
67
|
-
start_time = time.time()
|
68
|
-
|
69
|
-
# Make request to MCP bridge
|
70
|
-
response = requests.post(
|
71
|
-
f"{bridge_url}/execute_tool",
|
72
|
-
json=request_data,
|
73
|
-
headers=headers,
|
74
|
-
timeout=30 # Longer timeout for LLM calls
|
75
|
-
)
|
76
|
-
|
77
|
-
end_time = time.time()
|
78
|
-
duration = end_time - start_time
|
79
|
-
|
80
|
-
if response.status_code == 200:
|
81
|
-
result = response.json()
|
82
|
-
|
83
|
-
if result.get("success"):
|
84
|
-
data = result.get("data", {})
|
85
|
-
sql_query = data.get("generated_sql", "")
|
86
|
-
explanation = data.get("explanation", "")
|
87
|
-
confidence = data.get("confidence", "unknown")
|
88
|
-
method = data.get("method", "unknown")
|
89
|
-
|
90
|
-
print(f"✅ Success ({duration:.1f}s)")
|
91
|
-
print(f"📝 SQL: {sql_query}")
|
92
|
-
print(f"💡 Method: {method}")
|
93
|
-
print(f"🎯 Confidence: {confidence}")
|
94
|
-
print(f"📖 Explanation: {explanation}")
|
95
|
-
|
96
|
-
# Highlight if this is using LLM
|
97
|
-
if method == "llm":
|
98
|
-
print("🚀 Using LLM generation!")
|
99
|
-
elif method == "pattern_matching":
|
100
|
-
print("⚠️ Fallback to pattern matching")
|
101
|
-
|
102
|
-
else:
|
103
|
-
print(f"❌ Failed: {result.get('error', 'Unknown error')}")
|
104
|
-
|
105
|
-
else:
|
106
|
-
print(f"❌ HTTP Error: {response.status_code}")
|
107
|
-
print(f"Response: {response.text}")
|
108
|
-
|
109
|
-
except Exception as e:
|
110
|
-
print(f"❌ Exception: {str(e)}")
|
111
|
-
|
112
|
-
print(f"\n✨ LLM Text-to-SQL testing completed!")
|
113
|
-
|
114
|
-
if __name__ == "__main__":
|
115
|
-
test_llm_text_to_sql()
|
@@ -1,130 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Test script to demonstrate LLM vs Pattern Matching for text-to-SQL
|
4
|
-
"""
|
5
|
-
|
6
|
-
import requests
|
7
|
-
import json
|
8
|
-
import time
|
9
|
-
|
10
|
-
def test_query(question, description=""):
|
11
|
-
"""Test a single query and return results"""
|
12
|
-
bridge_url = "http://localhost:8081"
|
13
|
-
bridge_secret = "test-secret-for-development"
|
14
|
-
|
15
|
-
schema_info = {
|
16
|
-
"schema": {
|
17
|
-
"invoices": {
|
18
|
-
"columns": [
|
19
|
-
{"name": "id", "type": "integer"},
|
20
|
-
{"name": "vendor_name", "type": "text"},
|
21
|
-
{"name": "invoice_number", "type": "text"},
|
22
|
-
{"name": "invoice_date", "type": "date"},
|
23
|
-
{"name": "total_amount", "type": "numeric"},
|
24
|
-
{"name": "tax_amount", "type": "numeric"},
|
25
|
-
{"name": "line_items", "type": "jsonb"},
|
26
|
-
{"name": "status", "type": "text"}
|
27
|
-
]
|
28
|
-
}
|
29
|
-
}
|
30
|
-
}
|
31
|
-
|
32
|
-
headers = {
|
33
|
-
"Content-Type": "application/json",
|
34
|
-
"X-Bridge-Secret": bridge_secret
|
35
|
-
}
|
36
|
-
|
37
|
-
request_data = {
|
38
|
-
"tool_name": "TextToSQLGenerator",
|
39
|
-
"input_data": {
|
40
|
-
"question": question,
|
41
|
-
"schema_info": schema_info
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
print(f"\n🎯 {description}")
|
46
|
-
print(f"❓ Question: {question}")
|
47
|
-
print("-" * 60)
|
48
|
-
|
49
|
-
try:
|
50
|
-
start_time = time.time()
|
51
|
-
|
52
|
-
response = requests.post(
|
53
|
-
f"{bridge_url}/execute_tool",
|
54
|
-
json=request_data,
|
55
|
-
headers=headers,
|
56
|
-
timeout=30
|
57
|
-
)
|
58
|
-
|
59
|
-
end_time = time.time()
|
60
|
-
duration = end_time - start_time
|
61
|
-
|
62
|
-
if response.status_code == 200:
|
63
|
-
result = response.json()
|
64
|
-
|
65
|
-
if result.get("success"):
|
66
|
-
data = result.get("data", {})
|
67
|
-
sql_query = data.get("generated_sql", "")
|
68
|
-
method = data.get("method", "unknown")
|
69
|
-
confidence = data.get("confidence", "unknown")
|
70
|
-
|
71
|
-
print(f"✅ Success ({duration:.1f}s)")
|
72
|
-
print(f"📝 SQL: {sql_query}")
|
73
|
-
print(f"💡 Method: {method}")
|
74
|
-
print(f"🎯 Confidence: {confidence}")
|
75
|
-
|
76
|
-
if method == "llm":
|
77
|
-
print("🚀 LLM Generation - Advanced natural language understanding!")
|
78
|
-
elif method == "pattern_matching":
|
79
|
-
print("⚙️ Pattern Matching - Rule-based fallback")
|
80
|
-
else:
|
81
|
-
print("❓ Unknown method")
|
82
|
-
|
83
|
-
return True
|
84
|
-
else:
|
85
|
-
print(f"❌ Failed: {result.get('error', 'Unknown error')}")
|
86
|
-
return False
|
87
|
-
else:
|
88
|
-
print(f"❌ HTTP Error: {response.status_code}")
|
89
|
-
return False
|
90
|
-
|
91
|
-
except Exception as e:
|
92
|
-
print(f"❌ Exception: {str(e)}")
|
93
|
-
return False
|
94
|
-
|
95
|
-
def main():
|
96
|
-
"""Test various queries to demonstrate LLM capabilities"""
|
97
|
-
print("🧪 LLM vs Pattern Matching Text-to-SQL Comparison")
|
98
|
-
print("=" * 70)
|
99
|
-
|
100
|
-
# Test cases that should showcase LLM capabilities
|
101
|
-
test_cases = [
|
102
|
-
("Show me all invoices from Air Liquide", "Simple vendor filtering (both methods can handle)"),
|
103
|
-
("Find invoices with amounts greater than 1000", "Numeric comparison (LLM advantage)"),
|
104
|
-
("Show me invoices from last month", "Date range query (LLM advantage)"),
|
105
|
-
("Which vendor has the highest total invoice amount?", "Complex aggregation with grouping (LLM advantage)"),
|
106
|
-
("Find invoices where the tax amount is more than 10% of total", "Complex calculation (LLM advantage)"),
|
107
|
-
("Show me all pending invoices sorted by amount", "Status filtering with sorting (LLM advantage)"),
|
108
|
-
("What is the average invoice amount?", "Simple aggregation (both methods can handle)"),
|
109
|
-
("List all unique vendors", "Distinct query (both methods can handle)"),
|
110
|
-
("Find invoices with line items containing 'software'", "JSON field querying (LLM advantage)"),
|
111
|
-
("Show me the top 5 vendors by total invoice value", "Complex grouping and ranking (LLM advantage)")
|
112
|
-
]
|
113
|
-
|
114
|
-
success_count = 0
|
115
|
-
llm_count = 0
|
116
|
-
pattern_count = 0
|
117
|
-
|
118
|
-
for question, description in test_cases:
|
119
|
-
if test_query(question, description):
|
120
|
-
success_count += 1
|
121
|
-
# Note: We'd need to parse the response to count methods accurately
|
122
|
-
|
123
|
-
print(f"\n✨ Testing completed!")
|
124
|
-
print(f"📊 {success_count}/{len(test_cases)} queries successful")
|
125
|
-
print(f"\n🎉 The LLM-based text-to-SQL system is now active!")
|
126
|
-
print(f"🔄 It automatically falls back to pattern matching if LLM fails")
|
127
|
-
print(f"🚀 Complex queries now benefit from advanced natural language understanding")
|
128
|
-
|
129
|
-
if __name__ == "__main__":
|
130
|
-
main()
|