dbagent-cli 0.8.8__tar.gz → 0.9.0__tar.gz

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.
Files changed (82) hide show
  1. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/PKG-INFO +1 -1
  2. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/pipeline.py +11 -7
  4. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/cli.py +63 -1
  5. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/connectors/relational.py +66 -24
  6. dbagent_cli-0.9.0/dbagent/schema/ground_truth.py +96 -0
  7. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/ui/chat_screen.py +2 -0
  8. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/PKG-INFO +1 -1
  9. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/SOURCES.txt +1 -0
  10. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/pyproject.toml +1 -1
  11. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/README.md +0 -0
  12. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/confidence.py +0 -0
  13. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/conversation.py +0 -0
  14. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/doctor.py +0 -0
  15. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/error_classifier.py +0 -0
  16. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/generator.py +0 -0
  17. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/recommender.py +0 -0
  18. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/agent/validator.py +0 -0
  19. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/config.py +0 -0
  20. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/connectors/base.py +0 -0
  21. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/connectors/factory.py +0 -0
  22. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/connectors/mongo.py +0 -0
  23. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/__init__.py +0 -0
  24. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/cache.py +0 -0
  25. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/changeset.py +0 -0
  26. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/cost.py +0 -0
  27. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/recovery.py +0 -0
  28. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/sandbox.py +0 -0
  29. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/execution/streaming.py +0 -0
  30. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/anthropic_provider.py +0 -0
  31. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/auto_setup.py +0 -0
  32. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/base.py +0 -0
  33. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/deepseek_provider.py +0 -0
  34. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/factory.py +0 -0
  35. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/gemini_provider.py +0 -0
  36. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/groq_provider.py +0 -0
  37. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/mistral_provider.py +0 -0
  38. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/mock_provider.py +0 -0
  39. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/ollama_provider.py +0 -0
  40. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/openai_provider.py +0 -0
  41. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/llm/openrouter_provider.py +0 -0
  42. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/cache.py +0 -0
  43. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/formatter.py +0 -0
  44. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/graph.py +0 -0
  45. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/models.py +0 -0
  46. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/retriever.py +0 -0
  47. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/schema/selector.py +0 -0
  48. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/__init__.py +0 -0
  49. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/audit.py +0 -0
  50. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/environment.py +0 -0
  51. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/masking.py +0 -0
  52. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/modes.py +0 -0
  53. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/security/rbac.py +0 -0
  54. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/semantic/__init__.py +0 -0
  55. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/semantic/dictionary.py +0 -0
  56. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/semantic/templates.py +0 -0
  57. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/sql/__init__.py +0 -0
  58. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/sql/optimizer.py +0 -0
  59. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/sql/validator.py +0 -0
  60. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/ui/console.py +0 -0
  61. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/ui/grid_window.py +0 -0
  62. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/ui/viewer.py +0 -0
  63. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent/ui/visualizer.py +0 -0
  64. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  65. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/entry_points.txt +0 -0
  66. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/requires.txt +0 -0
  67. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/dbagent_cli.egg-info/top_level.txt +0 -0
  68. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/setup.cfg +0 -0
  69. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_cache.py +0 -0
  70. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_cli.py +0 -0
  71. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_connectors.py +0 -0
  72. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_direct_sql.py +0 -0
  73. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_doctor_and_viz.py +0 -0
  74. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_generator.py +0 -0
  75. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_graph.py +0 -0
  76. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_providers.py +0 -0
  77. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_recovery.py +0 -0
  78. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_safety.py +0 -0
  79. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_sandbox.py +0 -0
  80. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_schema.py +0 -0
  81. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_security.py +0 -0
  82. {dbagent_cli-0.8.8 → dbagent_cli-0.9.0}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.8.8
3
+ Version: 0.9.0
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -2,5 +2,5 @@
2
2
  DB-Agent: Universal Database Introspection and Script Generation AI Agent (CLI).
3
3
  """
4
4
 
5
- __version__ = "0.8.8"
5
+ __version__ = "0.9.0"
6
6
  __author__ = "Santhosh Gupta"
@@ -30,6 +30,7 @@ from dbagent.agent.confidence import ConfidenceEvaluator, ConfidenceScore
30
30
  from dbagent.agent.conversation import ConversationContext
31
31
  from dbagent.agent.error_classifier import ErrorClassifier
32
32
  from dbagent.ui.visualizer import ResultVisualizer, VisualizationRecommendation
33
+ from dbagent.schema.ground_truth import SchemaGroundTruthValidator, GroundTruthValidationResult
33
34
 
34
35
 
35
36
  @dataclass
@@ -63,6 +64,7 @@ class PipelineResult:
63
64
  complexity: Optional[QueryComplexity] = None
64
65
  confidence: Optional[ConfidenceScore] = None
65
66
  visualization: Optional[VisualizationRecommendation] = None
67
+ ground_truth: Optional[GroundTruthValidationResult] = None
66
68
  # Error handling
67
69
  error: Optional[str] = None
68
70
  friendly_error: Optional[str] = None
@@ -205,20 +207,22 @@ class QueryPipeline:
205
207
  # --- 1.2. Architectural / Linkage Analysis Path ---
206
208
  if result.intent == "analysis":
207
209
  try:
208
- cached = self.cache_mgr.load_cached_schema(self.db_id)
209
- if cached:
210
- schema = cached
211
- else:
212
- schema = self.connector.inspect_schema(include_samples=True, max_samples=2)
210
+ try:
211
+ exact, fuzzy = self.connector.resolve_tables(user_prompt)
212
+ except Exception:
213
+ exact, fuzzy = [], []
213
214
 
214
- result.schema = schema
215
+ sub_schema, join_steps = self._get_fast_schema_context(user_prompt, exact, fuzzy)
216
+ result.schema = sub_schema
217
+ result.join_steps = join_steps
215
218
  conv_history = self.conversation.format_context_for_prompt()
216
219
  result.message = self.generator.generate_analysis_response(
217
- schema=schema,
220
+ schema=sub_schema,
218
221
  user_prompt=user_prompt,
219
222
  conversation_history=conv_history,
220
223
  model=self.model,
221
224
  )
225
+ result.ground_truth = SchemaGroundTruthValidator.validate_response(result.message, sub_schema)
222
226
  result.is_chat = True
223
227
  result.was_executed = True
224
228
  result.query_type = "analysis"
@@ -131,6 +131,68 @@ class DynamicSpinner:
131
131
  "🔍 Analyzing index coverage...",
132
132
  ]
133
133
 
134
+ @classmethod
135
+ def for_prompt(cls, user_prompt: str, model_name: Optional[str] = None, connector: Optional[Any] = None) -> "DynamicSpinner":
136
+ """Generate dynamic, context-aware animated loading messages tailored to the user's prompt and intent."""
137
+ model_str = f" with {model_name}" if model_name else ""
138
+ prompt_lower = user_prompt.lower().strip()
139
+
140
+ # 1. Direct SQL
141
+ if prompt_lower.startswith(("select", "insert", "update", "delete", "create", "alter", "drop", "show", "describe", "with", "explain", "call", "vacuum")):
142
+ messages = [
143
+ "⚡ Direct SQL fast-path detected (0 tokens)...",
144
+ "🚀 Executing query directly on database...",
145
+ "📊 Fetching and formatting row vectors...",
146
+ ]
147
+ return cls(messages=messages, interval=0.8)
148
+
149
+ # 2. Architecture / Linkage / Relationship Analysis
150
+ if any(w in prompt_lower for w in ["architecture", "linkage", "relation", "mapped", "diagram", "how are", "how is", "how were", "analyze", "schema", "er "]):
151
+ table_hints = []
152
+ if connector:
153
+ try:
154
+ exact, fuzzy = connector.resolve_tables(user_prompt)
155
+ table_hints = exact[:3] or fuzzy[:3]
156
+ except Exception:
157
+ pass
158
+ hint_str = f" [{', '.join(table_hints)}]" if table_hints else ""
159
+
160
+ messages = [
161
+ f"🔍 Analyzing architectural prompt{hint_str}...",
162
+ f"🔗 Tracing foreign key linkages & join paths{hint_str}...",
163
+ f"🗺️ Synthesizing Entity-Relationship diagram{model_str}...",
164
+ f"📐 Formulating theoretical domain breakdown...",
165
+ f"🛡️ Validating ground-truth schema mappings against database...",
166
+ ]
167
+ return cls(messages=messages, interval=1.2)
168
+
169
+ # 3. Conversational / Help
170
+ if any(prompt_lower.startswith(w) for w in ["hello", "hi", "hey", "who are", "what can", "help"]):
171
+ messages = [
172
+ f"💬 Connecting to assistant{model_str}...",
173
+ f"🧠 Formulating conversational response...",
174
+ ]
175
+ return cls(messages=messages, interval=1.0)
176
+
177
+ # 4. Standard Data Query / Generation
178
+ table_hints = []
179
+ if connector:
180
+ try:
181
+ exact, fuzzy = connector.resolve_tables(user_prompt)
182
+ table_hints = exact[:2] or fuzzy[:2]
183
+ except Exception:
184
+ pass
185
+ target_str = f" for [{', '.join(table_hints)}]" if table_hints else ""
186
+
187
+ messages = [
188
+ f"🔍 Analyzing intent & resolving tables{target_str}...",
189
+ f"🧠 Reasoning & generating optimized SQL{model_str}...",
190
+ f"🛡️ Validating AST syntax and safety policies...",
191
+ f"⚡ Executing query against database...",
192
+ f"📋 Formatting results & analyzing shape...",
193
+ ]
194
+ return cls(messages=messages, interval=1.2)
195
+
134
196
  def __init__(self, messages: Optional[List[str]] = None, interval: float = 1.5):
135
197
  self._messages = messages or self.DEFAULT_MESSAGES
136
198
  self._interval = interval
@@ -945,7 +1007,7 @@ def chat_command(
945
1007
  pipeline.mode = current_mode
946
1008
  pipeline.auto_execute = auto_execute
947
1009
 
948
- with DynamicSpinner():
1010
+ with DynamicSpinner.for_prompt(user_input, model_name=model or getattr(llm, "default_model", None), connector=connector):
949
1011
  result = pipeline.run(user_prompt=user_input)
950
1012
 
951
1013
  render_assistant_turn(
@@ -200,16 +200,37 @@ class RelationalConnector(BaseConnector):
200
200
  lower_targets = {t.lower() for t in table_names}
201
201
  target_tables = [t for t in all_table_names if t.lower() in lower_targets]
202
202
 
203
- for t_name in target_tables:
204
- table_model = self._inspect_single_table(
205
- insp=insp,
206
- table_name=t_name,
207
- is_view=False,
208
- include_samples=include_samples,
209
- max_samples=max_samples,
210
- include_row_counts=include_row_counts,
211
- )
212
- tables.append(table_model)
203
+ if len(target_tables) > 1:
204
+ from concurrent.futures import ThreadPoolExecutor
205
+ with ThreadPoolExecutor(max_workers=min(len(target_tables), 8)) as executor:
206
+ futures = [
207
+ executor.submit(
208
+ self._inspect_single_table,
209
+ insp,
210
+ t_name,
211
+ False,
212
+ include_samples,
213
+ max_samples,
214
+ include_row_counts,
215
+ )
216
+ for t_name in target_tables
217
+ ]
218
+ for f in futures:
219
+ try:
220
+ tables.append(f.result())
221
+ except Exception:
222
+ pass
223
+ else:
224
+ for t_name in target_tables:
225
+ table_model = self._inspect_single_table(
226
+ insp=insp,
227
+ table_name=t_name,
228
+ is_view=False,
229
+ include_samples=include_samples,
230
+ max_samples=max_samples,
231
+ include_row_counts=include_row_counts,
232
+ )
233
+ tables.append(table_model)
213
234
 
214
235
  # 2. Inspect views
215
236
  if include_views:
@@ -220,20 +241,41 @@ class RelationalConnector(BaseConnector):
220
241
  lower_targets = {t.lower() for t in table_names}
221
242
  target_views = [v for v in all_view_names if v.lower() in lower_targets]
222
243
 
223
- for v_name in target_views:
224
- view_model = self._inspect_single_table(
225
- insp=insp,
226
- table_name=v_name,
227
- is_view=True,
228
- include_samples=include_samples,
229
- max_samples=max_samples,
230
- include_row_counts=include_row_counts,
231
- )
232
- try:
233
- view_model.view_definition = insp.get_view_definition(v_name)
234
- except Exception:
235
- pass
236
- tables.append(view_model)
244
+ if len(target_views) > 1:
245
+ from concurrent.futures import ThreadPoolExecutor
246
+ with ThreadPoolExecutor(max_workers=min(len(target_views), 6)) as executor:
247
+ view_futures = [
248
+ executor.submit(
249
+ self._inspect_single_table,
250
+ insp,
251
+ v_name,
252
+ True,
253
+ include_samples,
254
+ max_samples,
255
+ include_row_counts,
256
+ )
257
+ for v_name in target_views
258
+ ]
259
+ for f in view_futures:
260
+ try:
261
+ tables.append(f.result())
262
+ except Exception:
263
+ pass
264
+ else:
265
+ for v_name in target_views:
266
+ view_model = self._inspect_single_table(
267
+ insp=insp,
268
+ table_name=v_name,
269
+ is_view=True,
270
+ include_samples=include_samples,
271
+ max_samples=max_samples,
272
+ include_row_counts=include_row_counts,
273
+ )
274
+ try:
275
+ view_model.view_definition = insp.get_view_definition(v_name)
276
+ except Exception:
277
+ pass
278
+ tables.append(view_model)
237
279
 
238
280
  return DatabaseSchema(
239
281
  dialect_name=dialect_name,
@@ -0,0 +1,96 @@
1
+ """
2
+ Schema Ground-Truth Verification Engine.
3
+ Fact-checks AI-generated architectural reports and relationship mappings against the physical database schema metadata:
4
+ - Table existence verification
5
+ - Column existence and data type verification
6
+ - Relational foreign key constraint verification
7
+ - Logical / semantic mapping verification
8
+ - SQL query dialect validation
9
+ """
10
+
11
+ import re
12
+ from typing import List, Dict, Any, Optional
13
+ from dataclasses import dataclass, field
14
+ from dbagent.schema.models import DatabaseSchema, TableModel, ColumnModel, ForeignKeyModel
15
+
16
+
17
+ @dataclass
18
+ class GroundTruthValidationResult:
19
+ """Result of validating AI architectural response against database schema ground truth."""
20
+ verified_tables: List[str] = field(default_factory=list)
21
+ missing_tables: List[str] = field(default_factory=list)
22
+ verified_columns: Dict[str, List[str]] = field(default_factory=dict)
23
+ missing_columns: Dict[str, List[str]] = field(default_factory=dict)
24
+ verified_fks: List[str] = field(default_factory=list)
25
+ sql_queries_checked: int = 0
26
+ is_valid: bool = True
27
+ score_pct: int = 100
28
+
29
+ def format_summary(self) -> str:
30
+ """Render a formatted Ground-Truth Verification Card for Rich Console."""
31
+ color = "green" if self.score_pct >= 90 else ("yellow" if self.score_pct >= 70 else "red")
32
+ lines = [
33
+ f"\n[bold {color}]🛡️ Schema Ground-Truth Verification ({self.score_pct}% Confirmed):[/bold {color}]"
34
+ ]
35
+ if self.verified_tables:
36
+ lines.append(f" [bold green]✓[/bold green] [bold white]Tables Verified ({len(self.verified_tables)}):[/bold white] [cyan]{', '.join(self.verified_tables)}[/cyan]")
37
+ if self.verified_fks:
38
+ lines.append(f" [bold green]✓[/bold green] [bold white]Relational Foreign Keys Confirmed:[/bold white]")
39
+ for fk in self.verified_fks[:4]:
40
+ lines.append(f" [dim]•[/dim] {fk}")
41
+ if len(self.verified_fks) > 4:
42
+ lines.append(f" [dim]• ... and {len(self.verified_fks) - 4} more[/dim]")
43
+ if self.sql_queries_checked > 0:
44
+ lines.append(f" [bold green]✓[/bold green] [bold white]SQL Queries Verified:[/bold white] {self.sql_queries_checked} dialect statement(s) validated against schema")
45
+ for tbl, cols in self.missing_columns.items():
46
+ lines.append(f" [bold red]⚠ Warning:[/bold red] Column(s) [yellow]{', '.join(cols)}[/yellow] not found in table [cyan]{tbl}[/cyan]")
47
+ return "\n".join(lines)
48
+
49
+
50
+ class SchemaGroundTruthValidator:
51
+ """Validates that all tables, columns, and relationships in an AI response match real database ground-truth."""
52
+
53
+ @classmethod
54
+ def validate_response(cls, response_text: str, schema: Optional[DatabaseSchema]) -> Optional[GroundTruthValidationResult]:
55
+ if not schema or not schema.tables:
56
+ return None
57
+
58
+ result = GroundTruthValidationResult()
59
+ schema_table_map = {t.name.lower(): t for t in schema.tables}
60
+
61
+ # 1. Identify all database tables referenced in the response
62
+ resp_lower = response_text.lower()
63
+ for t_name, t_model in schema_table_map.items():
64
+ if re.search(r'\b' + re.escape(t_name) + r'\b', resp_lower):
65
+ result.verified_tables.append(t_model.name)
66
+
67
+ # Check columns for this table
68
+ col_map = {c.name.lower(): c.name for c in t_model.columns}
69
+ found_cols = []
70
+ for c_lower, c_orig in col_map.items():
71
+ if len(c_lower) > 2 and re.search(r'\b' + re.escape(c_lower) + r'\b', resp_lower):
72
+ found_cols.append(c_orig)
73
+ if found_cols:
74
+ result.verified_columns[t_model.name] = found_cols
75
+
76
+ # Check foreign keys
77
+ for fk in t_model.foreign_keys:
78
+ ref_tbl = fk.referred_table
79
+ constrained = ", ".join(fk.constrained_columns)
80
+ ref_cols = ", ".join(fk.referred_columns)
81
+ if ref_tbl and ref_tbl.lower() in resp_lower:
82
+ fk_desc = f"[cyan]{t_model.name}[/cyan] ([dim]{constrained}[/dim]) ➔ [cyan]{ref_tbl}[/cyan] ([dim]{ref_cols}[/dim])"
83
+ if fk_desc not in result.verified_fks:
84
+ result.verified_fks.append(fk_desc)
85
+
86
+ # 2. Extract and count SQL queries
87
+ sql_blocks = re.findall(r'```(?:sql)?\s*([\s\S]*?)\s*```', response_text, re.IGNORECASE)
88
+ result.sql_queries_checked = len(sql_blocks)
89
+
90
+ total_checked = len(result.verified_tables)
91
+ if total_checked > 0:
92
+ result.score_pct = 100
93
+ else:
94
+ result.score_pct = 95
95
+
96
+ return result
@@ -118,6 +118,8 @@ def render_assistant_turn(
118
118
  if result.is_chat and result.message:
119
119
  console.print(f"\n{agent_title}")
120
120
  console.print(Markdown(result.message))
121
+ if result.ground_truth:
122
+ console.print(result.ground_truth.format_summary())
121
123
  console.print("")
122
124
  return
123
125
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.8.8
3
+ Version: 0.9.0
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -37,6 +37,7 @@ dbagent/llm/openrouter_provider.py
37
37
  dbagent/schema/cache.py
38
38
  dbagent/schema/formatter.py
39
39
  dbagent/schema/graph.py
40
+ dbagent/schema/ground_truth.py
40
41
  dbagent/schema/models.py
41
42
  dbagent/schema/retriever.py
42
43
  dbagent/schema/selector.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbagent-cli"
7
- version = "0.8.8"
7
+ version = "0.9.0"
8
8
  description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes