Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Stockings
eada1a829b Log LLM usage 2026-01-30 19:45:31 +11:00
Peter Stockings
1c500328d1 Fix AI SQL query generation 2026-01-30 19:37:40 +11:00
3 changed files with 28 additions and 8 deletions

View File

@@ -27,6 +27,19 @@ def record_sql_audit(query, success, error_message=None):
except Exception as e:
current_app.logger.error(f"Failed to record SQL audit: {e}")
def record_llm_audit(prompt, response, model, success, error_message=None):
"""Records an LLM interaction in the audit table."""
try:
person_id = getattr(current_user, 'id', None)
ip_address = get_client_ip()
sql = """
INSERT INTO llm_audit (person_id, prompt, response, model, ip_address, success, error_message)
VALUES (%s, %s, %s, %s, %s, %s, %s)
"""
db.execute(sql, [person_id, prompt, response, model, ip_address, success, error_message], commit=True)
except Exception as e:
current_app.logger.error(f"Failed to record LLM audit: {e}")
def _execute_sql(query):
"""Executes arbitrary SQL query, returning results, columns, and error."""
results, columns, error = None, [], None
@@ -76,10 +89,11 @@ def _generate_sql_from_natural_language(natural_query):
api_url = f"https://generativelanguage.googleapis.com/v1beta/models/{gemni_model}:generateContent?key={api_key}"
headers = {'Content-Type': 'application/json'}
prompt = natural_query
try:
# Get and format schema
schema_info = _get_schema_info()
schema_string = _generate_create_script(schema_info)
schema_info = db.schema.get_schema_info()
schema_string = db.schema.generate_create_script(schema_info)
prompt = f"""Given the following database schema:
```sql
@@ -128,14 +142,20 @@ Return ONLY the SQL query, without any explanation or surrounding text/markdown.
filtered_lines = [line for line in sql_lines if not line.strip().startswith('--')]
final_sql = "\n".join(filtered_lines).strip()
return final_sql, None
generated_sql, error = final_sql, None
record_llm_audit(prompt, generated_sql, gemni_model, True)
return generated_sql, error
except requests.exceptions.RequestException as e:
current_app.logger.error(f"Gemini API request error: {e}")
return None, f"Error communicating with API: {e}"
error_msg = f"Error communicating with API: {e}"
record_llm_audit(prompt, None, gemni_model, False, error_msg)
return None, error_msg
except (KeyError, IndexError, Exception) as e:
current_app.logger.error(f"Error processing Gemini API response: {e} - Response: {response_data if 'response_data' in locals() else 'N/A'}")
return None, f"Error processing API response: {e}"
error_msg = f"Error processing API response: {e}"
record_llm_audit(prompt, None, gemni_model, False, error_msg)
return None, error_msg
# --- Routes ---

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

@@ -32,8 +32,8 @@
class="flex-grow p-2 border border-gray-300 rounded-l-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder="e.g., 'Show me the number of workouts per person'">
<button type="button" hx-post="{{ url_for('sql_explorer.generate_sql') }}"
hx-include="[name='natural_query']" hx-target="#query" hx-swap="innerHTML"
hx-indicator="#sql-spinner"
hx-include="[name='natural_query']" hx-indicator="#sql-spinner" hx-swap="none"
_="on htmx:afterRequest set #query.value to detail.xhr.responseText then send input to #query"
class="bg-purple-600 text-white p-2.5 rounded-r-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50 inline-flex items-center">
Generate SQL
<span id="sql-spinner" class="htmx-indicator ml-2">