rohitdeshmukh318 commited on
Commit
c960c82
Β·
1 Parent(s): 1f93fec

Add dynamic AI suggestions, fix insight router bug, and clean up repository for production

Browse files
agent/graph.py CHANGED
@@ -171,7 +171,7 @@ def _output_pipeline(state: AgentState) -> AgentState:
171
  """
172
  result = state.get("execution_result")
173
 
174
- if not result:
175
  return {
176
  **state,
177
  "insight_text": "No results were returned for this query.",
 
171
  """
172
  result = state.get("execution_result")
173
 
174
+ if not result and state.get("intent") != "insight_only":
175
  return {
176
  **state,
177
  "insight_text": "No results were returned for this query.",
agent/nodes/insight_synthesizer.py CHANGED
@@ -13,15 +13,37 @@ Focus on the most important numbers, trends, and business implications.
13
  Do not describe how the query works β€” just state what the data shows."""
14
 
15
 
 
 
 
 
 
 
 
 
 
 
 
16
  def insight_synthesizer(state: AgentState) -> AgentState:
17
  result = state.get("execution_result")
 
 
18
  if not result:
 
 
 
 
 
 
 
 
 
19
  return {**state, "insight_text": "No results were returned for this query."}
20
 
21
  # Truncate result preview for prompt (first 20 rows)
22
  preview = json.dumps(result[:20], default=str)
23
 
24
- client = get_groq_client()
25
  insight = client.complete_system(
26
  system=SYSTEM,
27
  user=f"Question: {state['user_query']}\n\nResults (first 20 rows):\n{preview}",
 
13
  Do not describe how the query works β€” just state what the data shows."""
14
 
15
 
16
+ def _build_conversation_context(history: list) -> str:
17
+ if not history:
18
+ return "No prior conversation."
19
+ lines = []
20
+ for i, turn in enumerate(history[-3:], 1):
21
+ lines.append(f"Q: {turn.get('query', '')}")
22
+ if turn.get('insight'):
23
+ lines.append(f"A: {turn['insight']}")
24
+ return "\n".join(lines)
25
+
26
+
27
  def insight_synthesizer(state: AgentState) -> AgentState:
28
  result = state.get("execution_result")
29
+ client = get_groq_client()
30
+
31
  if not result:
32
+ if state.get("intent") == "insight_only":
33
+ history_text = _build_conversation_context(state.get("conversation_history", []))
34
+ insight = client.complete_system(
35
+ system="You are a helpful data analyst. Answer the user's conversational question based on the previous context. Do not describe your thought process.",
36
+ user=f"Context:\n{history_text}\n\nQuestion: {state['user_query']}",
37
+ model=client.reason_model,
38
+ max_tokens=256,
39
+ )
40
+ return {**state, "insight_text": insight}
41
  return {**state, "insight_text": "No results were returned for this query."}
42
 
43
  # Truncate result preview for prompt (first 20 rows)
44
  preview = json.dumps(result[:20], default=str)
45
 
46
+
47
  insight = client.complete_system(
48
  system=SYSTEM,
49
  user=f"Question: {state['user_query']}\n\nResults (first 20 rows):\n{preview}",
agent/nodes/intent_router.py CHANGED
@@ -44,5 +44,10 @@ def intent_router(state: AgentState) -> AgentState:
44
  # Post-processing safeguard: cannot do insight without prior context
45
  if intent == "insight" and not has_history:
46
  intent = "sql"
 
 
 
 
 
47
 
48
  return {**state, "intent": intent}
 
44
  # Post-processing safeguard: cannot do insight without prior context
45
  if intent == "insight" and not has_history:
46
  intent = "sql"
47
+
48
+ # Post-processing safeguard: charting requests must not be insight
49
+ query_lower = state["user_query"].lower()
50
+ if intent == "insight" and any(w in query_lower for w in ["chart", "plot", "graph", "visualize", "visualization"]):
51
+ intent = "sql"
52
 
53
  return {**state, "intent": intent}
api/routers/query.py CHANGED
@@ -225,3 +225,51 @@ async def stream_query(req: QueryRequest):
225
  "X-Accel-Buffering": "no",
226
  },
227
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  "X-Accel-Buffering": "no",
226
  },
227
  )
228
+
229
+
230
+ class SuggestRequest(BaseModel):
231
+ connector_id: str
232
+
233
+
234
+ class SuggestResponse(BaseModel):
235
+ suggestions: List[str]
236
+
237
+
238
+ @router.post("/suggest", response_model=SuggestResponse)
239
+ async def get_suggestions(req: SuggestRequest):
240
+ try:
241
+ from connectors.base import get_connector
242
+ connector = get_connector(req.connector_id)
243
+ schema = connector.get_schema()
244
+ except Exception as exc:
245
+ return SuggestResponse(suggestions=["What is the total number of rows in this dataset?"])
246
+
247
+ # Format schema for prompt
248
+ schema_lines = []
249
+ for t in schema[:3]: # Limit to 3 tables
250
+ cols = ", ".join(f"{c['name']} ({c['type']})" for c in t.get("columns", [])[:15])
251
+ schema_lines.append(f"Table: {t['table']}\nColumns: {cols}")
252
+ schema_context = "\n\n".join(schema_lines)
253
+
254
+ SYSTEM = """You are a senior data analyst.
255
+ Based on the provided database schema, generate 3 highly relevant, interesting analytical questions that a user might want to ask.
256
+ Return ONLY a JSON list of 3 strings. Example: ["question 1?", "question 2?", "question 3?"]
257
+ Focus on business metrics, trends, and aggregations."""
258
+
259
+ from llm import get_groq_client
260
+ client = get_groq_client()
261
+ try:
262
+ raw = client.complete_system(
263
+ system=SYSTEM,
264
+ user=f"Schema:\n{schema_context}",
265
+ model=client.reason_model,
266
+ max_tokens=200,
267
+ )
268
+ import json
269
+ suggestions = json.loads(raw)
270
+ if isinstance(suggestions, list) and len(suggestions) > 0:
271
+ return SuggestResponse(suggestions=suggestions[:4])
272
+ except Exception:
273
+ pass
274
+
275
+ return SuggestResponse(suggestions=["What are the top trends in this dataset?"])
frontend/src/api.ts CHANGED
@@ -141,6 +141,17 @@ export async function* streamQuery(req: QueryRequest): AsyncGenerator<StreamEven
141
  }
142
  }
143
 
 
 
 
 
 
 
 
 
 
 
 
144
  // ── History ────────────────────────────────────────────────────────────────────
145
 
146
  export async function getHistory(sessionId: string): Promise<HistoryRecord[]> {
 
141
  }
142
  }
143
 
144
+ export async function fetchSuggestions(connectorId: string): Promise<string[]> {
145
+ const res = await fetch(`${BASE}/api/query/suggest`, {
146
+ method: 'POST',
147
+ headers: { 'Content-Type': 'application/json' },
148
+ body: JSON.stringify({ connector_id: connectorId })
149
+ })
150
+ if (!res.ok) throw new Error('Failed to fetch suggestions')
151
+ const data = await res.json()
152
+ return data.suggestions || []
153
+ }
154
+
155
  // ── History ────────────────────────────────────────────────────────────────────
156
 
157
  export async function getHistory(sessionId: string): Promise<HistoryRecord[]> {
frontend/src/pages/ChatPage.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useState, useRef, useEffect, useCallback } from 'react'
2
  import { Send, RefreshCw, FileDown } from 'lucide-react'
3
  import { v4 as uuidv4 } from 'uuid'
4
- import { streamQuery, savePanel, downloadReport } from '@/api'
5
  import type { QueryResponse, TraceEvent } from '@/api'
6
  import { getSessionId, newSession, getUserId, getConnectorId } from '@/session'
7
  import ChatMessage from '@/components/dashboard/ChatMessage'
@@ -15,21 +15,28 @@ interface Message {
15
  traceEvents: TraceEvent[]
16
  }
17
 
18
- const SUGGESTIONS = [
19
- 'What are the top 5 products by total revenue?',
20
- 'Show monthly order volume for the past year',
21
- 'Which region has the highest average order value?',
22
- 'List customers with more than 5 orders',
23
- ]
24
 
25
  export default function ChatPage() {
26
  const [messages, setMessages] = useState<Message[]>([])
 
 
 
27
  const [input, setInput] = useState('')
28
  const [loading, setLoading] = useState(false)
29
  const [streamText, setStreamText] = useState('')
30
  const [liveTrace, setLiveTrace] = useState<TraceEvent[]>([])
31
  const [sessionId, setSessionId] = useState(getSessionId)
32
  const [connector, setConnector] = useState(getConnectorId)
 
 
 
 
 
 
 
 
 
33
  const bottomRef = useRef<HTMLDivElement>(null)
34
  const textareaRef = useRef<HTMLTextAreaElement>(null)
35
 
@@ -140,15 +147,19 @@ export default function ChatPage() {
140
  <h2 className="text-xl font-semibold text-neutral-200 text-center mb-2">Ask anything about your data</h2>
141
  <p className="text-xs text-neutral-500 text-center mb-8">Connected to: <span className="text-neutral-300 font-mono">{connector}</span></p>
142
  <div className="grid grid-cols-1 gap-2">
143
- {SUGGESTIONS.map((s) => (
144
- <button
145
- key={s}
146
- onClick={() => submit(s)}
147
- className="text-left px-4 py-3 card hover:border-neutral-700 hover:bg-neutral-900/50 text-sm text-neutral-400 hover:text-neutral-200 transition-all"
148
- >
149
- {s}
150
- </button>
151
- ))}
 
 
 
 
152
  </div>
153
  </div>
154
  )}
 
1
  import { useState, useRef, useEffect, useCallback } from 'react'
2
  import { Send, RefreshCw, FileDown } from 'lucide-react'
3
  import { v4 as uuidv4 } from 'uuid'
4
+ import { streamQuery, savePanel, downloadReport, fetchSuggestions } from '@/api'
5
  import type { QueryResponse, TraceEvent } from '@/api'
6
  import { getSessionId, newSession, getUserId, getConnectorId } from '@/session'
7
  import ChatMessage from '@/components/dashboard/ChatMessage'
 
15
  traceEvents: TraceEvent[]
16
  }
17
 
18
+ // Dynamic suggestions fetched from API
 
 
 
 
 
19
 
20
  export default function ChatPage() {
21
  const [messages, setMessages] = useState<Message[]>([])
22
+ const [suggestions, setSuggestions] = useState<string[]>([])
23
+ const [loadingSuggestions, setLoadingSuggestions] = useState(false)
24
+
25
  const [input, setInput] = useState('')
26
  const [loading, setLoading] = useState(false)
27
  const [streamText, setStreamText] = useState('')
28
  const [liveTrace, setLiveTrace] = useState<TraceEvent[]>([])
29
  const [sessionId, setSessionId] = useState(getSessionId)
30
  const [connector, setConnector] = useState(getConnectorId)
31
+
32
+ useEffect(() => {
33
+ if (!connector) return
34
+ setLoadingSuggestions(true)
35
+ fetchSuggestions(connector)
36
+ .then(setSuggestions)
37
+ .catch(() => setSuggestions(['What insights can you find in this dataset?']))
38
+ .finally(() => setLoadingSuggestions(false))
39
+ }, [connector])
40
  const bottomRef = useRef<HTMLDivElement>(null)
41
  const textareaRef = useRef<HTMLTextAreaElement>(null)
42
 
 
147
  <h2 className="text-xl font-semibold text-neutral-200 text-center mb-2">Ask anything about your data</h2>
148
  <p className="text-xs text-neutral-500 text-center mb-8">Connected to: <span className="text-neutral-300 font-mono">{connector}</span></p>
149
  <div className="grid grid-cols-1 gap-2">
150
+ {loadingSuggestions ? (
151
+ <div className="text-center text-xs text-neutral-500 py-4 animate-pulse">Generating tailored suggestions...</div>
152
+ ) : (
153
+ suggestions.map((s) => (
154
+ <button
155
+ key={s}
156
+ onClick={() => submit(s)}
157
+ className="text-left px-4 py-3 card hover:border-neutral-700 hover:bg-neutral-900/50 text-sm text-neutral-400 hover:text-neutral-200 transition-all"
158
+ >
159
+ {s}
160
+ </button>
161
+ ))
162
+ )}
163
  </div>
164
  </div>
165
  )}