Skip to content

Commit 763493e

Browse files
committed
fix: add defensive field width limits and improve explain_action docstrings
1 parent 8af9698 commit 763493e

2 files changed

Lines changed: 81 additions & 25 deletions

File tree

nexum_ai/optimizer.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,24 @@ def feed_metrics(self, query: str, latency_ms: float) -> None:
361361

362362
def explain_action(self, query: str, available_actions: List[str]) -> Dict[str, Any]:
363363
"""
364-
Explain what action would be taken without executing
365-
Returns Q-values and predicted action for EXPLAIN command
364+
Explain what action would be taken without executing.
365+
366+
Returns Q-values and predicted action for EXPLAIN command.
367+
This method provides a read-only analysis of the optimizer's decision-making
368+
process without actually executing any action or updating the Q-table.
369+
370+
Args:
371+
query: SQL query string
372+
available_actions: List of possible actions
373+
374+
Returns:
375+
Dict containing:
376+
- state: state key string
377+
- q_values: Q-values for all actions
378+
- best_action: action with highest Q-value
379+
- epsilon: current exploration rate
380+
- would_explore: whether exploration is possible
381+
- explanation: human-readable explanation of optimizer behavior
366382
"""
367383
state = f"query_type_{len(query) // 10}"
368384

@@ -374,13 +390,16 @@ def explain_action(self, query: str, available_actions: List[str]) -> Dict[str,
374390

375391
best_action = max(available_actions, key=lambda a: q_values.get(a, 0.0))
376392

393+
# Defensive truncation for display (limit to 20 chars)
394+
best_action_display = best_action[:20] if len(best_action) > 20 else best_action
395+
377396
return {
378397
'state': state,
379398
'q_values': q_values,
380-
'best_action': best_action,
399+
'best_action': best_action_display,
381400
'epsilon': self.epsilon,
382401
'would_explore': self.epsilon > 0,
383-
'exploration_note': f'Random action possible (ε={self.epsilon})' if self.epsilon > 0 else 'Would use best action'
402+
'explanation': f'With ε={self.epsilon:.4f}, agent would explore {self.epsilon*100:.1f}% of the time'
384403
}
385404

386405

@@ -477,65 +496,79 @@ def explain_query_plan(query: str, cache: Optional[SemanticCache] = None,
477496

478497

479498
def format_explain_output(explain_result: Dict[str, Any]) -> str:
480-
"""Format EXPLAIN result as a readable table"""
499+
"""Format EXPLAIN result as a readable table with defensive field width limits"""
500+
501+
def truncate(value: Any, max_len: int) -> str:
502+
"""Truncate value to max length for box alignment"""
503+
s = str(value)
504+
if len(s) > max_len:
505+
return s[:max_len - 3] + "..."
506+
return s
507+
481508
lines = []
482509
lines.append("=" * 70)
483510
lines.append("QUERY EXECUTION PLAN")
484511
lines.append("=" * 70)
485512

486513
# Smart query truncation
487514
query = explain_result['query']
488-
if len(query) > 60:
489-
display_query = query[:60] + "..."
490-
else:
491-
display_query = query
515+
display_query = truncate(query, 60)
492516

493517
lines.append(f"Query: {display_query}")
494518
lines.append("")
495519

496520
# Parsing section
497521
lines.append("┌─ PARSING ─────────────────────────────────────────────────────────┐")
498522
p = explain_result['parsing']
499-
lines.append(f"│ Type: {p['query_type']:<15} Complexity: {p['complexity_estimate']}/10 │")
523+
query_type = truncate(p['query_type'], 15)
524+
lines.append(f"│ Type: {query_type:<15} Complexity: {p['complexity_estimate']}/10 │")
500525
lines.append(f"│ WHERE: {str(p['has_where_clause']):<8} JOIN: {str(p['has_join']):<8} AGG: {str(p['has_aggregation']):<8} │")
501526
lines.append("└───────────────────────────────────────────────────────────────────┘")
502527
lines.append("")
503528

504529
# Cache section
505530
lines.append("┌─ CACHE LOOKUP ────────────────────────────────────────────────────┐")
506531
c = explain_result['cache_analysis']
507-
lines.append(f"│ Entries checked: {c['cache_entries_checked']:<5} Threshold: {c['similarity_threshold']:<6} │")
532+
# Defensive limits: cache_entries_checked capped at 99999 for display
533+
entries_checked = min(c['cache_entries_checked'], 99999)
534+
lines.append(f"│ Entries checked: {entries_checked:<5} Threshold: {c['similarity_threshold']:<6} │")
508535
lines.append(f"│ Best similarity: {c['best_similarity']:<6} Would hit: {str(c['would_hit_cache']):<6} │")
509536
if c['top_matches']:
510537
lines.append("│ Top matches: │")
511538
for match in c['top_matches'][:3]:
512539
sim = match['similarity']
513540
hit = "✓" if match['would_hit'] else "✗"
514-
# Smart truncation for cached queries
515-
cached_query = match['cached_query']
516-
if not cached_query.endswith('...') and len(cached_query) > 45:
517-
cached_query = cached_query[:42] + "..."
541+
# Smart truncation for cached queries (limit to 45 chars)
542+
cached_query = truncate(match['cached_query'], 45)
518543
lines.append(f"│ {hit} {sim:.4f} - {cached_query:<45} │")
519544
lines.append("└───────────────────────────────────────────────────────────────────┘")
520545
lines.append("")
521546

522547
# RL Agent section
523548
lines.append("┌─ RL AGENT ────────────────────────────────────────────────────────┐")
524549
r = explain_result['rl_agent']
525-
lines.append(f"│ State: {r['state']:<30} Epsilon: {r.get('epsilon', r.get('exploration_probability', 0)):<6} │")
526-
lines.append(f"│ Best action: {r['best_action']:<20} │")
550+
# Defensive truncation for state (30 chars) and best_action (20 chars)
551+
state_display = truncate(r['state'], 30)
552+
best_action_display = truncate(r['best_action'], 20)
553+
lines.append(f"│ State: {state_display:<30} Epsilon: {r.get('epsilon', 0):<6} │")
554+
lines.append(f"│ Best action: {best_action_display:<20} │")
527555
lines.append("│ Q-values: │")
528556
for action, qval in r['q_values'].items():
529-
lines.append(f"│ {action:<15}: {qval:>8.4f} │")
557+
# Truncate action names to 15 chars for alignment
558+
action_display = truncate(action, 15)
559+
lines.append(f"│ {action_display:<15}: {qval:>8.4f} │")
530560
lines.append("└───────────────────────────────────────────────────────────────────┘")
531561
lines.append("")
532562

533563
# Execution strategy
534564
lines.append("┌─ EXECUTION STRATEGY ──────────────────────────────────────────────┐")
535565
e = explain_result['execution_strategy']
536-
lines.append(f"│ Strategy: {e['strategy']:<20} Est. latency: {e['estimated_latency']:<10} │")
566+
# Defensive truncation for strategy (20 chars)
567+
strategy_display = truncate(e['strategy'], 20)
568+
recommendation_display = truncate(e['recommendation'], 40)
569+
lines.append(f"│ Strategy: {strategy_display:<20} Est. latency: {e['estimated_latency']:<10} │")
537570
lines.append(f"│ Will cache: {str(e['will_cache_result']):<8} │")
538-
lines.append(f"│ Recommendation: {e['recommendation']:<40} │")
571+
lines.append(f"│ Recommendation: {recommendation_display:<40} │")
539572
lines.append("└───────────────────────────────────────────────────────────────────┘")
540573

541574
return "\n".join(lines)

nexum_ai/rl_agent.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,28 @@ def get_stats(self) -> Dict[str, float]:
175175

176176
def explain_action(self, query_length: int, cache_hit: bool, complexity: int) -> Dict[str, Any]:
177177
"""
178-
Explain what action would be taken without executing
179-
Returns Q-values, state analysis, and predicted action for EXPLAIN command
178+
Explain what action would be taken without executing.
179+
180+
Returns Q-values, state analysis, and predicted action for EXPLAIN command.
181+
This method provides a read-only analysis of the RL agent's decision-making
182+
process without actually executing any action or updating the Q-table.
183+
184+
Args:
185+
query_length: Length of SQL query
186+
cache_hit: Whether query hit cache
187+
complexity: Complexity score (0-10)
188+
189+
Returns:
190+
Dict containing:
191+
- state: state key string
192+
- state_breakdown: dict with query_length_bucket, cache_hit, complexity
193+
- q_values: Q-values for all actions
194+
- best_action: action with highest Q-value
195+
- epsilon: current exploration rate
196+
- would_explore: whether exploration is possible
197+
- predicted_action: deterministic best action (ignores epsilon-greedy)
198+
- explanation: human-readable explanation of agent behavior
199+
- agent_stats: total_states_learned, total_updates, episodes
180200
"""
181201
state = self._get_state_key(query_length, cache_hit, complexity)
182202

@@ -189,6 +209,9 @@ def explain_action(self, query_length: int, cache_hit: bool, complexity: int) ->
189209
# Determine best action
190210
best_action = max(self.actions, key=lambda a: q_values.get(a, 0.0))
191211

212+
# Truncate best_action for display if needed (defensive limit)
213+
best_action_display = best_action[:20] if len(best_action) > 20 else best_action
214+
192215
return {
193216
'state': state,
194217
'state_breakdown': {
@@ -197,11 +220,11 @@ def explain_action(self, query_length: int, cache_hit: bool, complexity: int) ->
197220
'complexity': complexity
198221
},
199222
'q_values': q_values,
200-
'best_action': best_action,
223+
'best_action': best_action_display,
201224
'epsilon': round(self.epsilon, 4),
202225
'would_explore': self.epsilon > 0,
203-
'predicted_action': best_action, # Deterministic for explain
204-
'exploration_probability': round(self.epsilon, 4),
226+
'predicted_action': best_action_display, # Deterministic for explain
227+
'explanation': f'With ε={self.epsilon:.4f}, agent would explore {self.epsilon*100:.1f}% of the time',
205228
'agent_stats': {
206229
'total_states_learned': len(self.q_table),
207230
'total_updates': len(self.training_history),

0 commit comments

Comments
 (0)