sqlsaber 0.32.1__py3-none-any.whl → 0.34.0__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.
Potentially problematic release.
This version of sqlsaber might be problematic. Click here for more details.
- sqlsaber/agents/pydantic_ai_agent.py +15 -26
- sqlsaber/application/auth_setup.py +83 -11
- sqlsaber/cli/auth.py +3 -1
- sqlsaber/config/api_keys.py +23 -0
- sqlsaber/config/auth.py +6 -0
- sqlsaber/config/logging.py +0 -1
- sqlsaber/database/postgresql.py +63 -11
- sqlsaber/prompts/__init__.py +0 -0
- sqlsaber/prompts/claude.py +52 -0
- sqlsaber/prompts/memory.py +4 -0
- sqlsaber/prompts/openai.py +40 -0
- {sqlsaber-0.32.1.dist-info → sqlsaber-0.34.0.dist-info}/METADATA +1 -1
- {sqlsaber-0.32.1.dist-info → sqlsaber-0.34.0.dist-info}/RECORD +16 -12
- {sqlsaber-0.32.1.dist-info → sqlsaber-0.34.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.32.1.dist-info → sqlsaber-0.34.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.32.1.dist-info → sqlsaber-0.34.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,6 +24,9 @@ from sqlsaber.database import (
|
|
|
24
24
|
SQLiteConnection,
|
|
25
25
|
)
|
|
26
26
|
from sqlsaber.memory.manager import MemoryManager
|
|
27
|
+
from sqlsaber.prompts.claude import SONNET_4_5
|
|
28
|
+
from sqlsaber.prompts.memory import MEMORY_ADDITION
|
|
29
|
+
from sqlsaber.prompts.openai import GPT_5
|
|
27
30
|
from sqlsaber.tools.registry import tool_registry
|
|
28
31
|
from sqlsaber.tools.sql_tools import SQLTool
|
|
29
32
|
|
|
@@ -168,6 +171,16 @@ class SQLSaberAgent:
|
|
|
168
171
|
|
|
169
172
|
@agent.system_prompt(dynamic=True)
|
|
170
173
|
async def sqlsaber_system_prompt(ctx: RunContext) -> str:
|
|
174
|
+
if "gpt-5" in agent.model.model_name:
|
|
175
|
+
base = GPT_5.format(db=self.db_type)
|
|
176
|
+
|
|
177
|
+
if self.database_name:
|
|
178
|
+
mem = self.memory_manager.format_memories_for_prompt(
|
|
179
|
+
self.database_name
|
|
180
|
+
)
|
|
181
|
+
mem = mem.strip()
|
|
182
|
+
if mem:
|
|
183
|
+
return f"{base}\n\n{MEMORY_ADDITION}\n\n{mem}"
|
|
171
184
|
return self.system_prompt_text(include_memory=True)
|
|
172
185
|
else:
|
|
173
186
|
|
|
@@ -178,37 +191,13 @@ class SQLSaberAgent:
|
|
|
178
191
|
|
|
179
192
|
def system_prompt_text(self, include_memory: bool = True) -> str:
|
|
180
193
|
"""Return the original SQLSaber system prompt as a single string."""
|
|
181
|
-
|
|
182
|
-
base = (
|
|
183
|
-
f"You are a helpful SQL assistant that helps users query their {db} database.\n\n"
|
|
184
|
-
"Your responsibilities:\n"
|
|
185
|
-
"1. Understand user's natural language requests, think and convert them to SQL\n"
|
|
186
|
-
"2. Use the provided tools efficiently to explore database schema\n"
|
|
187
|
-
"3. Generate appropriate SQL queries\n"
|
|
188
|
-
"4. Execute queries safely - queries that modify the database are not allowed\n"
|
|
189
|
-
"5. Format and explain results clearly\n\n"
|
|
190
|
-
"IMPORTANT - Tool Usage Strategy:\n"
|
|
191
|
-
"1. ALWAYS start with 'list_tables' to see available tables and row counts. Use this first to discover available tables.\n"
|
|
192
|
-
"2. Use 'introspect_schema' with a table_pattern to get details ONLY for relevant tables. Use table patterns like 'sample%' or '%experiment%' to filter related tables.\n"
|
|
193
|
-
"3. Execute SQL queries safely with automatic LIMIT clauses for SELECT statements. Only SELECT queries are permitted for security.\n\n"
|
|
194
|
-
"Tool-Specific Guidelines:\n"
|
|
195
|
-
"- introspect_schema: Use 'introspect_schema' with a table_pattern to get details ONLY for relevant tables. Use table patterns like 'sample%' or '%experiment%' to filter related tables.\n"
|
|
196
|
-
"- execute_sql: Execute SQL queries safely with automatic LIMIT clauses for SELECT statements. Only SELECT queries are permitted for security.\n\n"
|
|
197
|
-
"Guidelines:\n"
|
|
198
|
-
"- Use proper JOIN syntax and avoid cartesian products\n"
|
|
199
|
-
"- Include appropriate WHERE clauses to limit results\n"
|
|
200
|
-
"- Explain what the query does in simple terms\n"
|
|
201
|
-
"- Handle errors gracefully and suggest fixes\n"
|
|
202
|
-
"- Be security conscious - use parameterized queries when needed\n"
|
|
203
|
-
"- Timestamp columns must be converted to text when you write queries\n"
|
|
204
|
-
"- Use table patterns like 'sample%' or '%experiment%' to filter related tables"
|
|
205
|
-
)
|
|
194
|
+
base = SONNET_4_5.format(db=self.db_type)
|
|
206
195
|
|
|
207
196
|
if include_memory and self.database_name:
|
|
208
197
|
mem = self.memory_manager.format_memories_for_prompt(self.database_name)
|
|
209
198
|
mem = mem.strip()
|
|
210
199
|
if mem:
|
|
211
|
-
return f"{base}\n\n{mem}"
|
|
200
|
+
return f"{base}\n\n{MEMORY_ADDITION}\n\n{mem}\n\n"
|
|
212
201
|
return base
|
|
213
202
|
|
|
214
203
|
def _register_tools(self, agent: Agent) -> None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Shared auth setup logic for onboarding and CLI."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
from questionary import Choice
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ from sqlsaber.config import providers
|
|
|
9
10
|
from sqlsaber.config.api_keys import APIKeyManager
|
|
10
11
|
from sqlsaber.config.auth import AuthConfigManager, AuthMethod
|
|
11
12
|
from sqlsaber.config.oauth_flow import AnthropicOAuthFlow
|
|
13
|
+
from sqlsaber.config.oauth_tokens import OAuthTokenManager
|
|
12
14
|
from sqlsaber.theme.manager import create_console
|
|
13
15
|
|
|
14
16
|
console = create_console()
|
|
@@ -102,24 +104,49 @@ async def setup_auth(
|
|
|
102
104
|
Returns:
|
|
103
105
|
Tuple of (success: bool, provider: str | None)
|
|
104
106
|
"""
|
|
105
|
-
|
|
106
|
-
if auth_manager.has_auth_configured():
|
|
107
|
-
console.print("[success]✓ Authentication already configured![/success]")
|
|
108
|
-
return True, None
|
|
107
|
+
oauth_manager = OAuthTokenManager()
|
|
109
108
|
|
|
110
|
-
# Select provider
|
|
111
109
|
provider = await select_provider(prompter, default=default_provider)
|
|
112
110
|
|
|
113
111
|
if provider is None:
|
|
114
112
|
return False, None
|
|
115
113
|
|
|
114
|
+
env_var = api_key_manager.get_env_var_name(provider)
|
|
115
|
+
api_key_in_env = bool(os.getenv(env_var))
|
|
116
|
+
api_key_in_keyring = api_key_manager.has_stored_api_key(provider)
|
|
117
|
+
has_oauth = (
|
|
118
|
+
oauth_manager.has_oauth_token("anthropic")
|
|
119
|
+
if provider == "anthropic" and allow_oauth
|
|
120
|
+
else False
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if api_key_in_env or api_key_in_keyring or has_oauth:
|
|
124
|
+
parts: list[str] = []
|
|
125
|
+
if api_key_in_keyring:
|
|
126
|
+
parts.append("stored API key")
|
|
127
|
+
if api_key_in_env:
|
|
128
|
+
parts.append(f"{env_var} environment variable")
|
|
129
|
+
if has_oauth:
|
|
130
|
+
parts.append("OAuth token")
|
|
131
|
+
summary = ", ".join(parts)
|
|
132
|
+
console.print(
|
|
133
|
+
f"[info]Existing authentication found for {provider}: {summary}[/info]"
|
|
134
|
+
)
|
|
135
|
+
|
|
116
136
|
# For Anthropic, offer OAuth or API key
|
|
117
137
|
if provider == "anthropic" and allow_oauth:
|
|
138
|
+
api_key_label = "API Key"
|
|
139
|
+
if api_key_in_keyring or api_key_in_env:
|
|
140
|
+
api_key_label += " [configured]"
|
|
141
|
+
oauth_label = "Claude Pro/Max (OAuth)"
|
|
142
|
+
if has_oauth:
|
|
143
|
+
oauth_label += " [configured]"
|
|
144
|
+
|
|
118
145
|
method_choice = await prompter.select(
|
|
119
146
|
"Authentication method:",
|
|
120
147
|
choices=[
|
|
121
|
-
Choice(
|
|
122
|
-
Choice(
|
|
148
|
+
Choice(api_key_label, value=AuthMethod.API_KEY),
|
|
149
|
+
Choice(oauth_label, value=AuthMethod.CLAUDE_PRO),
|
|
123
150
|
],
|
|
124
151
|
)
|
|
125
152
|
|
|
@@ -127,6 +154,28 @@ async def setup_auth(
|
|
|
127
154
|
return False, None
|
|
128
155
|
|
|
129
156
|
if method_choice == AuthMethod.CLAUDE_PRO:
|
|
157
|
+
if has_oauth:
|
|
158
|
+
reset = await prompter.confirm(
|
|
159
|
+
"Anthropic OAuth is already configured. Reset before continuing?",
|
|
160
|
+
default=False,
|
|
161
|
+
)
|
|
162
|
+
if not reset:
|
|
163
|
+
console.print(
|
|
164
|
+
"[warning]No changes made to Anthropic OAuth credentials.[/warning]"
|
|
165
|
+
)
|
|
166
|
+
return True, None
|
|
167
|
+
|
|
168
|
+
removal_success = oauth_manager.remove_oauth_token("anthropic")
|
|
169
|
+
if not removal_success:
|
|
170
|
+
console.print(
|
|
171
|
+
"[error]Failed to remove existing Anthropic OAuth credentials.[/error]"
|
|
172
|
+
)
|
|
173
|
+
return False, None
|
|
174
|
+
|
|
175
|
+
current_method = auth_manager.get_auth_method()
|
|
176
|
+
if current_method == AuthMethod.CLAUDE_PRO:
|
|
177
|
+
auth_manager.clear_auth_method()
|
|
178
|
+
|
|
130
179
|
console.print()
|
|
131
180
|
oauth_success = await configure_oauth_anthropic(
|
|
132
181
|
auth_manager, run_in_thread=run_oauth_in_thread
|
|
@@ -136,12 +185,35 @@ async def setup_auth(
|
|
|
136
185
|
"[green]✓ Anthropic OAuth configured successfully![/green]"
|
|
137
186
|
)
|
|
138
187
|
return True, provider
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
188
|
+
|
|
189
|
+
console.print("[error]✗ Anthropic OAuth setup failed.[/error]")
|
|
190
|
+
return False, None
|
|
142
191
|
|
|
143
192
|
# API key flow
|
|
144
|
-
|
|
193
|
+
if api_key_in_keyring:
|
|
194
|
+
reset_api_key = await prompter.confirm(
|
|
195
|
+
f"{provider.title()} API key is stored in your keyring. Reset before continuing?",
|
|
196
|
+
default=False,
|
|
197
|
+
)
|
|
198
|
+
if not reset_api_key:
|
|
199
|
+
console.print(
|
|
200
|
+
"[warning]No changes made to stored API key credentials.[/warning]"
|
|
201
|
+
)
|
|
202
|
+
return True, None
|
|
203
|
+
if not api_key_manager.delete_api_key(provider):
|
|
204
|
+
console.print(
|
|
205
|
+
"[error]Failed to remove existing API key credentials.[/error]"
|
|
206
|
+
)
|
|
207
|
+
return False, None
|
|
208
|
+
console.print(
|
|
209
|
+
f"[muted]{provider.title()} API key removed from keyring.[/muted]"
|
|
210
|
+
)
|
|
211
|
+
api_key_in_keyring = False
|
|
212
|
+
|
|
213
|
+
if api_key_in_env:
|
|
214
|
+
console.print(
|
|
215
|
+
f"[muted]{env_var} is set in your environment. Update it there if you need a new value.[/muted]"
|
|
216
|
+
)
|
|
145
217
|
|
|
146
218
|
console.print()
|
|
147
219
|
console.print(f"[dim]To use {provider.title()}, you need an API key.[/dim]")
|
sqlsaber/cli/auth.py
CHANGED
|
@@ -167,7 +167,9 @@ def reset():
|
|
|
167
167
|
pass
|
|
168
168
|
except Exception as e:
|
|
169
169
|
console.print(f"Warning: Could not remove API key: {e}", style="warning")
|
|
170
|
-
logger.warning(
|
|
170
|
+
logger.warning(
|
|
171
|
+
"auth.reset.api_key_remove_failed", provider=provider, error=str(e)
|
|
172
|
+
)
|
|
171
173
|
|
|
172
174
|
# Optionally clear global auth method if removing Anthropic OAuth configuration
|
|
173
175
|
if provider == "anthropic" and oauth_present:
|
sqlsaber/config/api_keys.py
CHANGED
|
@@ -41,6 +41,29 @@ class APIKeyManager:
|
|
|
41
41
|
# 3. Prompt user for API key
|
|
42
42
|
return self._prompt_and_store_key(provider, env_var_name, service_name)
|
|
43
43
|
|
|
44
|
+
def has_stored_api_key(self, provider: str) -> bool:
|
|
45
|
+
"""Check if an API key is stored for the provider."""
|
|
46
|
+
service_name = self._get_service_name(provider)
|
|
47
|
+
try:
|
|
48
|
+
return keyring.get_password(service_name, provider) is not None
|
|
49
|
+
except Exception:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def delete_api_key(self, provider: str) -> bool:
|
|
53
|
+
"""Remove stored API key for the provider."""
|
|
54
|
+
service_name = self._get_service_name(provider)
|
|
55
|
+
try:
|
|
56
|
+
keyring.delete_password(service_name, provider)
|
|
57
|
+
return True
|
|
58
|
+
except keyring.errors.PasswordDeleteError:
|
|
59
|
+
return True
|
|
60
|
+
except Exception as e:
|
|
61
|
+
console.print(
|
|
62
|
+
f"Warning: Could not remove API key: {e}",
|
|
63
|
+
style="warning",
|
|
64
|
+
)
|
|
65
|
+
return False
|
|
66
|
+
|
|
44
67
|
def get_env_var_name(self, provider: str) -> str:
|
|
45
68
|
"""Get the expected environment variable name for a provider."""
|
|
46
69
|
# Normalize aliases to canonical provider keys
|
sqlsaber/config/auth.py
CHANGED
|
@@ -81,6 +81,12 @@ class AuthConfigManager:
|
|
|
81
81
|
config["auth_method"] = auth_method.value
|
|
82
82
|
self._save_config(config)
|
|
83
83
|
|
|
84
|
+
def clear_auth_method(self) -> None:
|
|
85
|
+
"""Clear any configured authentication method."""
|
|
86
|
+
config = self._load_config()
|
|
87
|
+
config["auth_method"] = None
|
|
88
|
+
self._save_config(config)
|
|
89
|
+
|
|
84
90
|
def has_auth_configured(self) -> bool:
|
|
85
91
|
"""Check if authentication method is configured."""
|
|
86
92
|
return self.get_auth_method() is not None
|
sqlsaber/config/logging.py
CHANGED
sqlsaber/database/postgresql.py
CHANGED
|
@@ -135,6 +135,35 @@ class PostgreSQLConnection(BaseDatabaseConnection):
|
|
|
135
135
|
class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
136
136
|
"""PostgreSQL-specific schema introspection."""
|
|
137
137
|
|
|
138
|
+
def _get_excluded_schemas(self) -> list[str]:
|
|
139
|
+
"""Return schemas to exclude during introspection.
|
|
140
|
+
|
|
141
|
+
Defaults include PostgreSQL system schemas and TimescaleDB internal
|
|
142
|
+
partitions schema. Additional schemas can be excluded by setting the
|
|
143
|
+
environment variable `SQLSABER_PG_EXCLUDE_SCHEMAS` to a comma-separated
|
|
144
|
+
list of schema names.
|
|
145
|
+
"""
|
|
146
|
+
import os
|
|
147
|
+
|
|
148
|
+
# Base exclusions: system schemas and TimescaleDB internal partitions
|
|
149
|
+
excluded = [
|
|
150
|
+
"pg_catalog",
|
|
151
|
+
"information_schema",
|
|
152
|
+
"_timescaledb_internal",
|
|
153
|
+
"_timescaledb_cache",
|
|
154
|
+
"_timescaledb_config",
|
|
155
|
+
"_timescaledb_catalog",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
extra = os.getenv("SQLSABER_PG_EXCLUDE_SCHEMAS", "")
|
|
159
|
+
if extra:
|
|
160
|
+
for item in extra.split(","):
|
|
161
|
+
name = item.strip()
|
|
162
|
+
if name and name not in excluded:
|
|
163
|
+
excluded.append(name)
|
|
164
|
+
|
|
165
|
+
return excluded
|
|
166
|
+
|
|
138
167
|
def _build_table_filter_clause(self, tables: list) -> tuple[str, list]:
|
|
139
168
|
"""Build VALUES clause with bind parameters for table filtering.
|
|
140
169
|
|
|
@@ -160,23 +189,35 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
160
189
|
"""Get tables information for PostgreSQL."""
|
|
161
190
|
pool = await connection.get_pool()
|
|
162
191
|
async with pool.acquire() as conn:
|
|
163
|
-
# Build WHERE clause for filtering
|
|
164
|
-
where_conditions = [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
192
|
+
# Build WHERE clause for filtering with bind params
|
|
193
|
+
where_conditions: list[str] = []
|
|
194
|
+
params: list[Any] = []
|
|
195
|
+
|
|
196
|
+
excluded = self._get_excluded_schemas()
|
|
197
|
+
if excluded:
|
|
198
|
+
placeholders = ", ".join(f"${i + 1}" for i in range(len(excluded)))
|
|
199
|
+
where_conditions.append(f"table_schema NOT IN ({placeholders})")
|
|
200
|
+
params.extend(excluded)
|
|
201
|
+
else:
|
|
202
|
+
# Fallback safety
|
|
203
|
+
where_conditions.append(
|
|
204
|
+
"table_schema NOT IN ('pg_catalog', 'information_schema')"
|
|
205
|
+
)
|
|
168
206
|
|
|
169
207
|
if table_pattern:
|
|
170
208
|
# Support patterns like 'schema.table' or just 'table'
|
|
171
209
|
if "." in table_pattern:
|
|
172
210
|
schema_pattern, table_name_pattern = table_pattern.split(".", 1)
|
|
211
|
+
s_idx = len(params) + 1
|
|
212
|
+
t_idx = len(params) + 2
|
|
173
213
|
where_conditions.append(
|
|
174
|
-
"(table_schema LIKE $
|
|
214
|
+
f"(table_schema LIKE ${s_idx} AND table_name LIKE ${t_idx})"
|
|
175
215
|
)
|
|
176
216
|
params.extend([schema_pattern, table_name_pattern])
|
|
177
217
|
else:
|
|
218
|
+
p_idx = len(params) + 1
|
|
178
219
|
where_conditions.append(
|
|
179
|
-
"(table_name LIKE $
|
|
220
|
+
f"(table_name LIKE ${p_idx} OR table_schema || '.' || table_name LIKE ${p_idx})"
|
|
180
221
|
)
|
|
181
222
|
params.append(table_pattern)
|
|
182
223
|
|
|
@@ -310,17 +351,28 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
310
351
|
"""Get list of tables with basic information for PostgreSQL."""
|
|
311
352
|
pool = await connection.get_pool()
|
|
312
353
|
async with pool.acquire() as conn:
|
|
313
|
-
#
|
|
314
|
-
|
|
354
|
+
# Exclude system schemas (and TimescaleDB internals) for performance
|
|
355
|
+
excluded = self._get_excluded_schemas()
|
|
356
|
+
params: list[Any] = []
|
|
357
|
+
if excluded:
|
|
358
|
+
placeholders = ", ".join(f"${i + 1}" for i in range(len(excluded)))
|
|
359
|
+
where_clause = f"table_schema NOT IN ({placeholders})"
|
|
360
|
+
params.extend(excluded)
|
|
361
|
+
else:
|
|
362
|
+
where_clause = (
|
|
363
|
+
"table_schema NOT IN ('pg_catalog', 'information_schema')"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
tables_query = f"""
|
|
315
367
|
SELECT
|
|
316
368
|
table_schema,
|
|
317
369
|
table_name,
|
|
318
370
|
table_type
|
|
319
371
|
FROM information_schema.tables
|
|
320
|
-
WHERE
|
|
372
|
+
WHERE {where_clause}
|
|
321
373
|
ORDER BY table_schema, table_name;
|
|
322
374
|
"""
|
|
323
|
-
tables = await conn.fetch(tables_query)
|
|
375
|
+
tables = await conn.fetch(tables_query, *params)
|
|
324
376
|
|
|
325
377
|
# Convert to expected format
|
|
326
378
|
return [
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
SONNET_4_5 = """You are a helpful SQL assistant designed to help users query their {db} database using natural language requests.
|
|
2
|
+
|
|
3
|
+
## Your Core Responsibilities
|
|
4
|
+
|
|
5
|
+
1. **Understand requests**: Convert natural language queries into appropriate SQL
|
|
6
|
+
2. **Explore schema**: Use provided tools to discover and understand database structure
|
|
7
|
+
3. **Generate queries**: Create safe, efficient SQL queries with proper syntax
|
|
8
|
+
4. **Execute safely**: Run only SELECT queries (no data modification allowed)
|
|
9
|
+
5. **Explain results**: Format outputs clearly and explain what the queries accomplish
|
|
10
|
+
|
|
11
|
+
## Tool Usage Strategy
|
|
12
|
+
|
|
13
|
+
You must follow this systematic approach:
|
|
14
|
+
|
|
15
|
+
1. **Always start with `list_tables`** to discover available tables
|
|
16
|
+
2. **Use `introspect_schema` strategically** with table patterns (like 'sample%' or '%experiment%') to get details only for relevant tables
|
|
17
|
+
3. **Execute SQL queries safely** - all SELECT statements automatically include LIMIT clauses for safety
|
|
18
|
+
|
|
19
|
+
## Important Guidelines
|
|
20
|
+
|
|
21
|
+
- Write proper JOIN syntax and avoid cartesian products
|
|
22
|
+
- Include appropriate WHERE clauses to filter results effectively
|
|
23
|
+
- Convert timestamp columns to text format in your queries
|
|
24
|
+
- Use parameterized queries when needed for security
|
|
25
|
+
- Handle errors gracefully and suggest fixes when issues arise
|
|
26
|
+
- Explain each query's purpose in simple non-technical terms
|
|
27
|
+
|
|
28
|
+
## Response Format
|
|
29
|
+
|
|
30
|
+
For each user request, structure your response as follows:
|
|
31
|
+
|
|
32
|
+
Before proceeding with database exploration, work through the problem systematically in <analysis> tags inside your thinking block:
|
|
33
|
+
- Parse the user's natural language request and identify the core question being asked
|
|
34
|
+
- Extract key terms, entities, and concepts that might correspond to database tables or columns
|
|
35
|
+
- Consider what types of data relationships might be involved (e.g., one-to-many, many-to-many)
|
|
36
|
+
- Plan your database exploration approach step by step
|
|
37
|
+
- Design your overall SQL strategy, including potential JOINs, filters, and aggregations
|
|
38
|
+
- Anticipate potential challenges or edge cases specific to this database type
|
|
39
|
+
- Verify your approach makes logical sense for the business question
|
|
40
|
+
|
|
41
|
+
It's OK for this analysis section to be quite long if the request is complex.
|
|
42
|
+
|
|
43
|
+
Then, execute the planned database exploration and queries, providing clear explanations of results.
|
|
44
|
+
|
|
45
|
+
## Example Response Structure
|
|
46
|
+
|
|
47
|
+
Working through this systematically in my analysis, then exploring tables and executing queries...
|
|
48
|
+
|
|
49
|
+
Now I need to address your specific request. Before proceeding with database exploration, let me analyze what you're asking for:
|
|
50
|
+
|
|
51
|
+
Your final response should focus on the database exploration, query execution, and results explanation, without duplicating or rehashing the analytical work done in the thinking block.
|
|
52
|
+
"""
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
MEMORY_ADDITION = """# Contextual Memory Integration
|
|
2
|
+
Below is any relevant context, prior memories, or database-specific quirks provided from the user.
|
|
3
|
+
Prioritize only information that directly influences query generation or interpretation to optimize clarity and focus.
|
|
4
|
+
"""
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
GPT_5 = """# Role and Objective
|
|
2
|
+
A helpful SQL assistant that assists users in querying their {db} database.
|
|
3
|
+
|
|
4
|
+
# Instructions
|
|
5
|
+
- Understand user requests in natural language and convert them into SQL queries.
|
|
6
|
+
- Efficiently utilize provided tools to investigate the database schema.
|
|
7
|
+
- Generate and execute only safe and appropriate SQL queries (only SELECT statements are allowed — no modifications to the database).
|
|
8
|
+
- Clearly format and explain results to the user.
|
|
9
|
+
- Set reasoning_effort = medium due to moderate task complexity; keep tool call outputs concise, provide fuller explanations in the final output.
|
|
10
|
+
|
|
11
|
+
## Workflow Checklist
|
|
12
|
+
Begin by thinking about what you will do; keep items conceptual, not implementation-level, before substantive work on user queries.
|
|
13
|
+
|
|
14
|
+
- Analyze the user request and determine intent.
|
|
15
|
+
- List available tables and their row counts using the appropriate tool.
|
|
16
|
+
- Identify relevant tables via schema introspection with filtered patterns.
|
|
17
|
+
- Formulate a safe and appropriate SELECT query with LIMIT clause.
|
|
18
|
+
- Execute the query and validate the result for correctness and safety.
|
|
19
|
+
- Clearly explain the result or guide the user if adjustments are needed.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Tool Usage Strategy
|
|
23
|
+
1. **Start with `list_tables`**: Always begin by listing available tables and row counts to discover what's present in the database. Before any significant tool call, state the purpose and minimal inputs.
|
|
24
|
+
2. **Use `introspect_schema` with a table pattern**: Retrieve schema details only for tables relevant to the user's request, employing patterns like `sample%` or `%experiment%` to filter.
|
|
25
|
+
3. **Execute SQL queries safely**: All SELECT statements must include LIMIT clauses for safety. Only SELECT queries are permitted for security purposes.
|
|
26
|
+
|
|
27
|
+
## Tool-Specific Guidelines
|
|
28
|
+
- **introspect_schema**: Limit introspection to relevant tables using appropriate patterns (e.g., `sample%`, `%experiment%`).
|
|
29
|
+
- **execute_sql**: Only run SELECT queries, ensuring they have LIMIT clauses. Do not permit any queries that modify the database.
|
|
30
|
+
|
|
31
|
+
# Guidelines
|
|
32
|
+
- Apply proper JOIN syntax to avoid cartesian products.
|
|
33
|
+
- Use appropriate WHERE clauses to filter results.
|
|
34
|
+
- Explain each query in simple terms for user understanding.
|
|
35
|
+
- Handle errors gracefully and suggest corrections to users.
|
|
36
|
+
- Be security conscious — use parameterized queries when necessary.
|
|
37
|
+
- Convert timestamp columns to text within the SQL queries you generate.
|
|
38
|
+
- Use table patterns (like `sample%` or `%experiment%`) to narrow down contextually relevant tables.
|
|
39
|
+
- After each tool call or code edit, validate result in 1-2 lines and proceed or self-correct if validation fails.
|
|
40
|
+
"""
|
|
@@ -2,14 +2,14 @@ sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
|
|
|
2
2
|
sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
|
|
3
3
|
sqlsaber/agents/__init__.py,sha256=qYI6rLY4q5AbF47vXH5RVoM08-yQjymBSaePh4lFIW4,116
|
|
4
4
|
sqlsaber/agents/base.py,sha256=T05UsMZPwAlMhsJFpuuVI1RNDhdiwiEsgCWr9MbPoAU,2654
|
|
5
|
-
sqlsaber/agents/pydantic_ai_agent.py,sha256=
|
|
5
|
+
sqlsaber/agents/pydantic_ai_agent.py,sha256=6_KppII8YcMw74KOGsYI5Dt6AP8WSduK3yAXCawVex4,10643
|
|
6
6
|
sqlsaber/application/__init__.py,sha256=KY_-d5nEdQyAwNOsK5r-f7Tb69c63XbuEkHPeLpJal8,84
|
|
7
|
-
sqlsaber/application/auth_setup.py,sha256=
|
|
7
|
+
sqlsaber/application/auth_setup.py,sha256=wbi9MaYl6q27LjcSBZmqFC12JtE5hrUHEX1NmD-7UVc,7778
|
|
8
8
|
sqlsaber/application/db_setup.py,sha256=ZSgR9rJJVHttIjsbYQS9GEIyzkM09k5RLrVGdegrfYc,6859
|
|
9
9
|
sqlsaber/application/model_selection.py,sha256=fSC06MZNKinHDR-csMFVYYJFyK8MydKf6pStof74Jp0,3191
|
|
10
10
|
sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
|
|
11
11
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
12
|
-
sqlsaber/cli/auth.py,sha256=
|
|
12
|
+
sqlsaber/cli/auth.py,sha256=TmAD4BJr2gIjPeW2LA1QkKK5gUZ2OOS6vOmBvB6PC0M,7051
|
|
13
13
|
sqlsaber/cli/commands.py,sha256=WocWlLrxA5kM8URfvIvWFtc0ocfgKWAwoYTxVNZhmM4,10962
|
|
14
14
|
sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
|
|
15
15
|
sqlsaber/cli/database.py,sha256=BBGj0eyduh5DDXNLZLDtWfY9kWpeT_ZX0J9R9INZyyU,12421
|
|
@@ -22,10 +22,10 @@ sqlsaber/cli/streaming.py,sha256=jicSDLWQ3efitpdc2y4QsasHcEW8ogZ4lHcWmftq9Ao,676
|
|
|
22
22
|
sqlsaber/cli/theme.py,sha256=D6HIt7rmF00B5ZOCV5lXKzPICE4uppHdraOdVs7k5Nw,4672
|
|
23
23
|
sqlsaber/cli/threads.py,sha256=zYvs1epmRRuQxOofF85eXk1_YHS6co7oq_F33DdNdf0,14643
|
|
24
24
|
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
25
|
-
sqlsaber/config/api_keys.py,sha256=
|
|
26
|
-
sqlsaber/config/auth.py,sha256=
|
|
25
|
+
sqlsaber/config/api_keys.py,sha256=H7xBU1mflngXIlnaDjbTvuNqZsW_92AIve31eDLij34,4513
|
|
26
|
+
sqlsaber/config/auth.py,sha256=G1uulySUclWSId8EIt1hPNmsUNhbfRzfp8VVQftYyG8,2964
|
|
27
27
|
sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
|
|
28
|
-
sqlsaber/config/logging.py,sha256=
|
|
28
|
+
sqlsaber/config/logging.py,sha256=vv4oCuQePeYQ7bMs0OLKj8ZSiNcFWbHmWtdC0lTsUyc,6173
|
|
29
29
|
sqlsaber/config/oauth_flow.py,sha256=cDfaJjqr4spNuzxbAlzuJfk6SEe1ojSRAkoOWlvQYy0,11037
|
|
30
30
|
sqlsaber/config/oauth_tokens.py,sha256=KCC2u3lOjdh0M-rd0K1rW0PWk58w7mqpodAhlPVp9NE,6424
|
|
31
31
|
sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
|
|
@@ -35,13 +35,17 @@ sqlsaber/database/base.py,sha256=oaipLxlvoylX6oJCITPAWWqRqv09hRELqqEBufsmFic,370
|
|
|
35
35
|
sqlsaber/database/csv.py,sha256=41wuP40FaGPfj28HMiD0I69uG0JbUxArpoTLC3MG2uc,4464
|
|
36
36
|
sqlsaber/database/duckdb.py,sha256=8HNKdx208aFK_YtwGjLz6LTne0xEmNevD-f9dRWlrFg,11244
|
|
37
37
|
sqlsaber/database/mysql.py,sha256=wMzDQqq4GFbfEdqXtv_sCb4Qbr9GSWqYAvOLeo5UryY,14472
|
|
38
|
-
sqlsaber/database/postgresql.py,sha256=
|
|
38
|
+
sqlsaber/database/postgresql.py,sha256=jNmjDY8fODfjRo0gHC1KU-NNP3K5ZQ9RVq69QwRP4Ws,15214
|
|
39
39
|
sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
|
|
40
40
|
sqlsaber/database/schema.py,sha256=CuV0ewoVaERe1gj_fJFJFWAP8aEPgepmn6X6B7bgkfQ,6962
|
|
41
41
|
sqlsaber/database/sqlite.py,sha256=iReEIiSpkhhS1VzITd79ZWqSL3fHMyfe3DRCDpM0DvE,9421
|
|
42
42
|
sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
|
|
43
43
|
sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
|
|
44
44
|
sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
|
|
45
|
+
sqlsaber/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
sqlsaber/prompts/claude.py,sha256=wvitHetuUi9s_Bfy8desgBUigEbI-URqcIS447WtV2k,2757
|
|
47
|
+
sqlsaber/prompts/memory.py,sha256=UciA3JPdNp6Iup20Vy-Nv7PGU7tL2yOVNYkE61r9AL4,275
|
|
48
|
+
sqlsaber/prompts/openai.py,sha256=gPBvJR7WgmWELPZbP4kC53Wm6jDu1zsCmHKmyc5U1uY,2640
|
|
45
49
|
sqlsaber/theme/__init__.py,sha256=qCICX1Cg4B6yCbZ1UrerxglWxcqldRFVSRrSs73na_8,188
|
|
46
50
|
sqlsaber/theme/manager.py,sha256=TPourIKGU-UzHtImgexgtazpuDaFhqUYtVauMblgGAQ,6480
|
|
47
51
|
sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
|
|
@@ -51,8 +55,8 @@ sqlsaber/tools/base.py,sha256=NKEEooliPKTJj_Pomwte_wW0Xd9Z5kXNfVdCRfTppuw,883
|
|
|
51
55
|
sqlsaber/tools/registry.py,sha256=XmBzERq0LJXtg3BZ-r8cEyt8J54NUekgUlTJ_EdSYMk,2204
|
|
52
56
|
sqlsaber/tools/sql_guard.py,sha256=dTDwcZP-N4xPGzcr7MQtKUxKrlDzlc1irr9aH5a4wvk,6182
|
|
53
57
|
sqlsaber/tools/sql_tools.py,sha256=eo-NTxiXGHMopAjujvDDjmv9hf5bQNbiy3nTpxoJ_E8,7369
|
|
54
|
-
sqlsaber-0.
|
|
55
|
-
sqlsaber-0.
|
|
56
|
-
sqlsaber-0.
|
|
57
|
-
sqlsaber-0.
|
|
58
|
-
sqlsaber-0.
|
|
58
|
+
sqlsaber-0.34.0.dist-info/METADATA,sha256=A5Xu_z5Q2dw-qJSHZX6MR8uiM1mIf941pHbXnl7BPCg,5915
|
|
59
|
+
sqlsaber-0.34.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
60
|
+
sqlsaber-0.34.0.dist-info/entry_points.txt,sha256=tw1mB0fjlkXQiOsC0434X6nE-o1cFCuQwt2ZYHv_WAE,91
|
|
61
|
+
sqlsaber-0.34.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
62
|
+
sqlsaber-0.34.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|