Subh775 commited on
Commit
3f7ecbf
Β·
1 Parent(s): baa77c3

Improved search..

Browse files
Files changed (4) hide show
  1. app.py +14 -0
  2. graph.py +10 -0
  3. static/app.js +4 -6
  4. tools.py +5 -17
app.py CHANGED
@@ -342,6 +342,18 @@ def chat(request: ChatRequest, req: Request):
342
 
343
  # Step 6: Stream LLM response via LangGraph
344
  has_image = bool(request.image)
 
 
 
 
 
 
 
 
 
 
 
 
345
  config = {
346
  "configurable": {
347
  "thread_id": request.thread_id,
@@ -352,6 +364,8 @@ def chat(request: ChatRequest, req: Request):
352
  "student_profile": student_profile,
353
  "user_api_key": user_api_key,
354
  "has_image": has_image,
 
 
355
  }
356
  }
357
 
 
342
 
343
  # Step 6: Stream LLM response via LangGraph
344
  has_image = bool(request.image)
345
+
346
+ # Resolve target model dynamically to print/trace and pass to LangGraph config
347
+ try:
348
+ from graph import _classify, _pick
349
+ category = _classify(clean_message)
350
+ target_model, _ = _pick(category, has_image=has_image)
351
+ except Exception as e:
352
+ print(f"[MODEL PICK ERROR] {e}", flush=True)
353
+ target_model = ""
354
+
355
+ print(f"[ROUTER CHAT] Routing query to model: {target_model}", flush=True)
356
+
357
  config = {
358
  "configurable": {
359
  "thread_id": request.thread_id,
 
364
  "student_profile": student_profile,
365
  "user_api_key": user_api_key,
366
  "has_image": has_image,
367
+ "search_enabled": (web_search_enabled or yt_search_enabled),
368
+ "model": target_model,
369
  }
370
  }
371
 
graph.py CHANGED
@@ -305,6 +305,7 @@ def chat_node(state: ChatState, config: RunnableConfig):
305
  profile = cfg.get("student_profile", "")
306
  api_key = cfg.get("user_api_key", "")
307
  override = cfg.get("model", "")
 
308
  has_image = cfg.get("has_image", False)
309
 
310
  user_text = _extract_text(state["messages"])
@@ -313,6 +314,15 @@ def chat_node(state: ChatState, config: RunnableConfig):
313
  messages = state["messages"]
314
  base_prompt = prompts.build(persona, context, language, username, profile)
315
 
 
 
 
 
 
 
 
 
 
316
  if has_image:
317
  base_prompt += (
318
  "\n\nIMAGE HANDLING:\n"
 
305
  profile = cfg.get("student_profile", "")
306
  api_key = cfg.get("user_api_key", "")
307
  override = cfg.get("model", "")
308
+ search_enabled = cfg.get("search_enabled", False)
309
  has_image = cfg.get("has_image", False)
310
 
311
  user_text = _extract_text(state["messages"])
 
314
  messages = state["messages"]
315
  base_prompt = prompts.build(persona, context, language, username, profile)
316
 
317
+ if search_enabled:
318
+ base_prompt += (
319
+ "\n\nSCOPE OVERRIDE (ACTIVE SEARCH/YT DETECTED):\n"
320
+ "The user has enabled Web Search / YouTube Video Search. You are acting as a general information assistant. "
321
+ "The strict Physics, Chemistry, and Mathematics scope restriction is temporarily suspended. "
322
+ "Answer the user's query comprehensively using the web search or YouTube transcript context provided, "
323
+ "regardless of the academic or non-academic nature of the query. Do NOT output a scope refusal message.\n"
324
+ )
325
+
326
  if has_image:
327
  base_prompt += (
328
  "\n\nIMAGE HANDLING:\n"
static/app.js CHANGED
@@ -1401,7 +1401,7 @@ function _updateInputPlaceholder() {
1401
  if (isWebSearchEnabled) {
1402
  placeholder = 'Search the web and ask...';
1403
  } else if (isYtSearchEnabled) {
1404
- placeholder = 'Paste YouTube URL or ask about video...';
1405
  }
1406
 
1407
  const inputs = [
@@ -1463,11 +1463,9 @@ function setupAttachMenu(plusBtn, menuEl, webSearchToggleEl, ytSearchToggleEl, a
1463
  }
1464
 
1465
  if (docInputEl) {
1466
- const newDocInputEl = docInputEl.cloneNode(true);
1467
- docInputEl.parentNode.replaceChild(newDocInputEl, docInputEl);
1468
- newDocInputEl.addEventListener('change', () => {
1469
- if (newDocInputEl.files[0]) {
1470
- handleAttachedFile(newDocInputEl.files[0], previewEl);
1471
  }
1472
  });
1473
  }
 
1401
  if (isWebSearchEnabled) {
1402
  placeholder = 'Search the web and ask...';
1403
  } else if (isYtSearchEnabled) {
1404
+ placeholder = 'Paste YouTube link...';
1405
  }
1406
 
1407
  const inputs = [
 
1463
  }
1464
 
1465
  if (docInputEl) {
1466
+ docInputEl.addEventListener('change', () => {
1467
+ if (docInputEl.files[0]) {
1468
+ handleAttachedFile(docInputEl.files[0], previewEl);
 
 
1469
  }
1470
  });
1471
  }
tools.py CHANGED
@@ -2,10 +2,10 @@ from __future__ import annotations
2
  import re
3
  from langchain_community.tools import DuckDuckGoSearchResults
4
  from langchain_core.tools import tool
 
 
5
 
6
  # ── Web Search ────────────────────────────────────────────────
7
- # Using DuckDuckGoSearchResults (structured) over DuckDuckGoSearchRun (plain string).
8
- # num_results=4 keeps context tight for free-tier models.
9
  web_search = DuckDuckGoSearchResults(
10
  name="web_search",
11
  num_results=4,
@@ -20,11 +20,9 @@ web_search = DuckDuckGoSearchResults(
20
 
21
  def run_web_search(query: str) -> str:
22
  """
23
- Run web search programmatically using DDGS with a LangChain fallback.
24
- Catches all exceptions to ensure search failures never crash the chat.
25
  """
26
  try:
27
- from duckduckgo_search import DDGS
28
  with DDGS(timeout=10) as ddgs:
29
  results = list(ddgs.text(query, max_results=4))
30
  if not results:
@@ -38,9 +36,8 @@ def run_web_search(query: str) -> str:
38
  formatted.append(f"Title: {title}\nURL: {href}\nSnippet: {body}\n")
39
  return "\n".join(formatted)
40
  except Exception as e:
41
- print(f"[SEARCH DDGS ERROR] {e}. Trying LangChain fallback...")
42
  try:
43
- # Fallback to the LangChain wrapper tool
44
  return web_search.invoke(query)
45
  except Exception as fallback_err:
46
  return f"Web search is temporarily unavailable. (Error: {fallback_err})"
@@ -64,13 +61,6 @@ def _extract_video_id(url_or_id: str) -> str | None:
64
  def yt_transcript(youtube_url: str) -> str:
65
  """
66
  Fetch the full transcript of a YouTube video.
67
-
68
- Args:
69
- youtube_url: A YouTube video URL (e.g. https://www.youtube.com/watch?v=XYZ)
70
- or a bare 11-character video ID.
71
-
72
- Returns:
73
- The full transcript text, or an error message if unavailable.
74
  """
75
  return fetch_yt_transcript(youtube_url)
76
 
@@ -78,21 +68,19 @@ def yt_transcript(youtube_url: str) -> str:
78
  def fetch_yt_transcript(youtube_url: str) -> str:
79
  """
80
  Programmatic helper to fetch the transcript of a YouTube video URL or ID.
81
- Handles exceptions gracefully to prevent crashes.
82
  """
83
  video_id = _extract_video_id(youtube_url)
84
  if not video_id:
85
  return f"Could not extract a valid YouTube video ID from: {youtube_url}"
86
 
87
  try:
88
- from youtube_transcript_api import YouTubeTranscriptApi
89
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
90
  transcript = " ".join(seg["text"] for seg in transcript_list)
91
  if not transcript.strip():
92
  return "Transcript is empty for this video."
93
  return transcript
94
-
95
  except Exception as exc:
 
96
  err = str(exc).lower()
97
  if "disabled" in err or "no transcript" in err:
98
  return (
 
2
  import re
3
  from langchain_community.tools import DuckDuckGoSearchResults
4
  from langchain_core.tools import tool
5
+ from duckduckgo_search import DDGS
6
+ from youtube_transcript_api import YouTubeTranscriptApi
7
 
8
  # ── Web Search ────────────────────────────────────────────────
 
 
9
  web_search = DuckDuckGoSearchResults(
10
  name="web_search",
11
  num_results=4,
 
20
 
21
  def run_web_search(query: str) -> str:
22
  """
23
+ Run web search programmatically using DDGS.
 
24
  """
25
  try:
 
26
  with DDGS(timeout=10) as ddgs:
27
  results = list(ddgs.text(query, max_results=4))
28
  if not results:
 
36
  formatted.append(f"Title: {title}\nURL: {href}\nSnippet: {body}\n")
37
  return "\n".join(formatted)
38
  except Exception as e:
39
+ print(f"[SEARCH DDGS ERROR] {e}. Trying LangChain fallback...", flush=True)
40
  try:
 
41
  return web_search.invoke(query)
42
  except Exception as fallback_err:
43
  return f"Web search is temporarily unavailable. (Error: {fallback_err})"
 
61
  def yt_transcript(youtube_url: str) -> str:
62
  """
63
  Fetch the full transcript of a YouTube video.
 
 
 
 
 
 
 
64
  """
65
  return fetch_yt_transcript(youtube_url)
66
 
 
68
  def fetch_yt_transcript(youtube_url: str) -> str:
69
  """
70
  Programmatic helper to fetch the transcript of a YouTube video URL or ID.
 
71
  """
72
  video_id = _extract_video_id(youtube_url)
73
  if not video_id:
74
  return f"Could not extract a valid YouTube video ID from: {youtube_url}"
75
 
76
  try:
 
77
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
78
  transcript = " ".join(seg["text"] for seg in transcript_list)
79
  if not transcript.strip():
80
  return "Transcript is empty for this video."
81
  return transcript
 
82
  except Exception as exc:
83
+ print(f"[YT TRANSCRIPT EXCEPTION] Video ID: {video_id}, Error: {exc}", flush=True)
84
  err = str(exc).lower()
85
  if "disabled" in err or "no transcript" in err:
86
  return (