harini-012 commited on
Commit
122389a
Β·
verified Β·
1 Parent(s): 4be00c5

Update tools.py

Browse files
Files changed (1) hide show
  1. tools.py +68 -56
tools.py CHANGED
@@ -1,4 +1,3 @@
1
- cat > /home/claude/tools.py << 'ENDOFFILE'
2
  # tools.py
3
  import os
4
  import re
@@ -37,17 +36,15 @@ def _save_cache(cache: dict) -> None:
37
  # Helpers used by agent.py (no LLM, no @tool)
38
  # ──────────────────────────────────────────────────────────────────────────────
39
 
40
- # Question-type classifier ─────────────────────────────────────────────────────
41
  def classify_question(question: str) -> str:
42
  """
43
- Return one of: 'reasoning', 'youtube', 'image', 'wikipedia_log', 'web'
44
  Checked in order; first match wins.
45
  """
46
  q = question.lower()
47
 
48
- # Pure reasoning β€” no external lookup needed
49
  reasoning_patterns = [
50
- r"\btable\b.*\bset\b.*\{", # math/operation table
51
  r"\boperation\b.*\bset\b",
52
  r"grocery list",
53
  r"\bbotany\b",
@@ -56,7 +53,7 @@ def classify_question(question: str) -> str:
56
  r"\bcommutativ",
57
  r"\bassociativ",
58
  r"making a pie",
59
- r"shopping list.*recipe",
60
  r"recipe.*ingredient",
61
  r"\bconvert\b.*\bunits?\b",
62
  r"\bcalculat",
@@ -65,65 +62,84 @@ def classify_question(question: str) -> str:
65
  if re.search(pat, q):
66
  return "reasoning"
67
 
68
- # YouTube video
69
  if "youtube.com/watch" in q or "youtu.be/" in q:
70
  return "youtube"
71
 
72
- # Image / chess
73
- if re.search(r"\bimage\b|\bchess\b|\bboard\b.*\bposition\b|\bpicture\b|\bphoto\b|\bscreenshot\b", q):
 
 
74
  return "image"
75
 
76
- # Wikipedia featured article log β€” needs the exact archive URL
77
- if re.search(r"featured article.*wikipedia.*nominated|nominated.*featured article.*wikipedia", q):
78
- return "wikipedia_log"
79
- if re.search(r"featured log|featured article.*promoted.*\d{4}|promoted.*featured article.*\d{4}", q):
 
80
  return "wikipedia_log"
81
 
82
  return "web"
83
 
84
 
85
- # Search-query builder ─────────────────────────────────────────────────────────
86
  def build_search_query(question: str) -> str:
87
  """
88
- Condense a verbose GAIA question into a tight DDG query (4-7 words).
89
- Always appends 'wikipedia' so DDG surfaces the right article first.
90
  """
91
  q = question.strip()
92
- q = re.sub(r"\(.*?\)", "", q).strip() # drop parenthetical hints
 
93
 
94
- # Drop question starters
95
  q = re.sub(
96
  r"^(how many|what (is|was|are|were)|who (is|was)|when (did|was|is)|"
97
  r"which|where (is|was)|why|tell me|find|give me|list)\s+",
98
  "", q, flags=re.I,
99
  )
100
- # Drop filler
101
- for filler in [
102
- r"studio albums? were published by",
103
- r"albums? were released by",
104
- r"were published by",
105
- r"was born in",
106
- r"between \d{4} and \d{4}.*",
107
- r"you can use .*",
108
- r"the latest \d{4} version.*",
109
- r"surname of the",
110
- r"mentioned in .*libretexts.*",
111
- ]:
112
- q = re.sub(filler, "", q, flags=re.I).strip()
 
 
 
 
 
 
 
113
 
114
  q = re.sub(r"\s+", " ", q).strip().rstrip("?.,;:")
115
 
 
 
 
 
 
116
  if "wikipedia" not in q.lower():
117
  q += " wikipedia"
118
  return q
119
 
120
 
121
- # URL scorer ──────────────���────────────────────────────────────────────────────
122
  def extract_best_url(search_output: str, question: str = "") -> str | None:
123
  """
124
- Pick the most relevant URL from web_search output without an LLM call.
125
- Scores by keyword overlap between the URL+snippet and the question.
126
  """
 
 
 
 
 
 
127
  blocks = re.split(r"\n\n+", search_output)
128
  candidates: list[tuple[str, str]] = []
129
  for block in blocks:
@@ -140,6 +156,7 @@ def extract_best_url(search_output: str, question: str = "") -> str | None:
140
  "by","to","and","or","you","can","use","between","included","latest",
141
  "version","english","wikipedia","published","released","studio","albums",
142
  "surname","mentioned","exercises","chemistry","licensed","compiled",
 
143
  }
144
  keywords = [
145
  w.lower() for w in re.findall(r"[A-Za-z]{3,}", question)
@@ -153,9 +170,7 @@ def extract_best_url(search_output: str, question: str = "") -> str | None:
153
  s += 3
154
  if "disambiguation" in ul or "disambiguation" in ctx:
155
  s -= 2
156
- # penalise obvious mismatches (YouTube, Facebook, Reddit, Chegg…)
157
- for bad in ["youtube.com", "reddit.com", "facebook.com",
158
- "chegg.com", "studyx.ai", "lespac.com", "fandom.com"]:
159
  if bad in ul:
160
  s -= 5
161
  for kw in keywords:
@@ -166,14 +181,12 @@ def extract_best_url(search_output: str, question: str = "") -> str | None:
166
  return s
167
 
168
  ranked = sorted(candidates, key=lambda x: score(x[0], x[1]), reverse=True)
169
- best = ranked[0][0]
170
- # Never return a score-negative URL (all bad sources) β€” return None instead
171
- if score(best, ranked[0][1]) < 0:
172
  return None
173
- return best
174
 
175
 
176
- # YouTube transcript helper ────────────────────────────────────────────────────
177
  def _extract_youtube_id(text: str) -> str | None:
178
  m = re.search(r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})", text)
179
  return m.group(1) if m else None
@@ -184,7 +197,7 @@ def _extract_youtube_id(text: str) -> str | None:
184
  # ──────────────────────────────────────────────────────────────────────────────
185
  @tool
186
  def web_search(query: str) -> str:
187
- """Search the web. Pass a SHORT query (4-7 words), never the full question.
188
 
189
  Args:
190
  query: Short search query, e.g. 'Mercedes Sosa discography wikipedia'
@@ -251,7 +264,7 @@ def visit_webpage(url: str) -> str:
251
  @tool
252
  def get_youtube_transcript(video_url: str) -> str:
253
  """Fetch the auto-generated transcript of a YouTube video.
254
- Use this for any question that asks about audio/dialogue in a YouTube video.
255
 
256
  Args:
257
  video_url: Full YouTube URL, e.g. 'https://www.youtube.com/watch?v=1htKBjuUWec'
@@ -260,25 +273,25 @@ def get_youtube_transcript(video_url: str) -> str:
260
  if not vid_id:
261
  return f"Could not extract video ID from: {video_url}"
262
 
263
- # Try youtube-transcript-api (pip install youtube-transcript-api)
264
  try:
265
  from youtube_transcript_api import YouTubeTranscriptApi
266
- transcript_list = YouTubeTranscriptApi.get_transcript(vid_id)
267
- text = " ".join(entry["text"] for entry in transcript_list)
268
  return text[:8000]
269
- except Exception as e:
270
- pass # fall through to scrape fallback
271
 
272
- # Fallback: scrape the timedtext API
273
  try:
274
- url = f"https://www.youtube.com/watch?v={vid_id}"
275
- resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=12)
276
- # Extract caption track URL from page source
 
277
  cap_match = re.search(r'"captionTracks":\[.*?"baseUrl":"(.*?)"', resp.text)
278
  if cap_match:
279
  cap_url = cap_match.group(1).replace("\\u0026", "&")
280
  cap_resp = requests.get(cap_url, timeout=10)
281
- # Strip XML tags from caption XML
282
  text = re.sub(r"<[^>]+>", " ", cap_resp.text)
283
  text = re.sub(r"\s+", " ", text).strip()
284
  return text[:8000]
@@ -306,5 +319,4 @@ def read_pdf(filepath: str) -> str:
306
  return "PDF appears to be empty or image-only (no extractable text)."
307
  return text[:15000]
308
  except Exception as e:
309
- return f"PDF error: {e}"
310
- ENDOFFILE
 
 
1
  # tools.py
2
  import os
3
  import re
 
36
  # Helpers used by agent.py (no LLM, no @tool)
37
  # ──────────────────────────────────────────────────────────────────────────────
38
 
 
39
  def classify_question(question: str) -> str:
40
  """
41
+ Route to one of: 'reasoning', 'youtube', 'image', 'wikipedia_log', 'web'.
42
  Checked in order; first match wins.
43
  """
44
  q = question.lower()
45
 
 
46
  reasoning_patterns = [
47
+ r"\btable\b.*\bset\b.*\{",
48
  r"\boperation\b.*\bset\b",
49
  r"grocery list",
50
  r"\bbotany\b",
 
53
  r"\bcommutativ",
54
  r"\bassociativ",
55
  r"making a pie",
56
+ r"shopping list.*(?:recipe|ingredient|pie)",
57
  r"recipe.*ingredient",
58
  r"\bconvert\b.*\bunits?\b",
59
  r"\bcalculat",
 
62
  if re.search(pat, q):
63
  return "reasoning"
64
 
 
65
  if "youtube.com/watch" in q or "youtu.be/" in q:
66
  return "youtube"
67
 
68
+ if re.search(
69
+ r"\bimage\b|\bchess\b|\bboard\b.*\bposition\b|\bpicture\b|\bphoto\b|\bscreenshot\b",
70
+ q
71
+ ):
72
  return "image"
73
 
74
+ if re.search(
75
+ r"featured article.*wikipedia.*nominated|nominated.*featured article.*wikipedia"
76
+ r"|featured log|featured article.*promoted.*\d{4}|promoted.*featured article.*\d{4}",
77
+ q
78
+ ):
79
  return "wikipedia_log"
80
 
81
  return "web"
82
 
83
 
 
84
  def build_search_query(question: str) -> str:
85
  """
86
+ Turn a verbose GAIA question into a tight 4-8 word DDG query.
87
+ Always appends 'wikipedia' to surface the right article first.
88
  """
89
  q = question.strip()
90
+ # Remove parenthetical hints
91
+ q = re.sub(r"\(.*?\)", "", q).strip()
92
 
93
+ # Drop question-word starters
94
  q = re.sub(
95
  r"^(how many|what (is|was|are|were)|who (is|was)|when (did|was|is)|"
96
  r"which|where (is|was)|why|tell me|find|give me|list)\s+",
97
  "", q, flags=re.I,
98
  )
99
+
100
+ # Drop known filler β€” order matters (longer patterns first)
101
+ fillers = [
102
+ r"studio albums? (?:were )?published by\s*",
103
+ r"albums? (?:were )?released by\s*",
104
+ r"were published by\s*",
105
+ r"was born in\s*",
106
+ r"between \d{4} and \d{4}[^.]*",
107
+ r"you can use [^.]*",
108
+ r"the latest \d{4} version[^.]*",
109
+ r"surname of (?:the)?\s*",
110
+ r"(?:licensed|compiled) by .*", # drop "licensed by X …"
111
+ r"from the chemistry materials?.*",
112
+ r"in \d+\.\w+ exercises?.*",
113
+ r"under the ck-12 .*",
114
+ r"libretexts.*",
115
+ r"mentioned in .*(?:exercises?|materials?)",
116
+ ]
117
+ for filler in fillers:
118
+ q = re.sub(filler, " ", q, flags=re.I).strip()
119
 
120
  q = re.sub(r"\s+", " ", q).strip().rstrip("?.,;:")
121
 
122
+ # Cap at 8 words so DDG returns precise results
123
+ words = q.split()
124
+ if len(words) > 8:
125
+ q = " ".join(words[:8])
126
+
127
  if "wikipedia" not in q.lower():
128
  q += " wikipedia"
129
  return q
130
 
131
 
 
132
  def extract_best_url(search_output: str, question: str = "") -> str | None:
133
  """
134
+ Score URLs by keyword overlap with the question.
135
+ Avoids known useless domains; returns None if nothing looks good.
136
  """
137
+ BAD_DOMAINS = {
138
+ "youtube.com", "reddit.com", "facebook.com", "chegg.com",
139
+ "studyx.ai", "lespac.com", "fandom.com", "quora.com",
140
+ "answers.com", "yahoo.com",
141
+ }
142
+
143
  blocks = re.split(r"\n\n+", search_output)
144
  candidates: list[tuple[str, str]] = []
145
  for block in blocks:
 
156
  "by","to","and","or","you","can","use","between","included","latest",
157
  "version","english","wikipedia","published","released","studio","albums",
158
  "surname","mentioned","exercises","chemistry","licensed","compiled",
159
+ "materials","introductory","ck12","libretexts",
160
  }
161
  keywords = [
162
  w.lower() for w in re.findall(r"[A-Za-z]{3,}", question)
 
170
  s += 3
171
  if "disambiguation" in ul or "disambiguation" in ctx:
172
  s -= 2
173
+ for bad in BAD_DOMAINS:
 
 
174
  if bad in ul:
175
  s -= 5
176
  for kw in keywords:
 
181
  return s
182
 
183
  ranked = sorted(candidates, key=lambda x: score(x[0], x[1]), reverse=True)
184
+ best_url, best_ctx = ranked[0]
185
+ if score(best_url, best_ctx) < 0:
 
186
  return None
187
+ return best_url
188
 
189
 
 
190
  def _extract_youtube_id(text: str) -> str | None:
191
  m = re.search(r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})", text)
192
  return m.group(1) if m else None
 
197
  # ──────────────────────────────────────────────────────────────────────────────
198
  @tool
199
  def web_search(query: str) -> str:
200
+ """Search the web. Pass a SHORT query (4-8 words), never the full question.
201
 
202
  Args:
203
  query: Short search query, e.g. 'Mercedes Sosa discography wikipedia'
 
264
  @tool
265
  def get_youtube_transcript(video_url: str) -> str:
266
  """Fetch the auto-generated transcript of a YouTube video.
267
+ Use this for any question that asks about spoken dialogue or audio in a video.
268
 
269
  Args:
270
  video_url: Full YouTube URL, e.g. 'https://www.youtube.com/watch?v=1htKBjuUWec'
 
273
  if not vid_id:
274
  return f"Could not extract video ID from: {video_url}"
275
 
276
+ # Primary: youtube-transcript-api (pip install youtube-transcript-api)
277
  try:
278
  from youtube_transcript_api import YouTubeTranscriptApi
279
+ entries = YouTubeTranscriptApi.get_transcript(vid_id)
280
+ text = " ".join(e["text"] for e in entries)
281
  return text[:8000]
282
+ except Exception:
283
+ pass
284
 
285
+ # Fallback: scrape caption track from page source
286
  try:
287
+ resp = requests.get(
288
+ f"https://www.youtube.com/watch?v={vid_id}",
289
+ headers={"User-Agent": "Mozilla/5.0"}, timeout=12
290
+ )
291
  cap_match = re.search(r'"captionTracks":\[.*?"baseUrl":"(.*?)"', resp.text)
292
  if cap_match:
293
  cap_url = cap_match.group(1).replace("\\u0026", "&")
294
  cap_resp = requests.get(cap_url, timeout=10)
 
295
  text = re.sub(r"<[^>]+>", " ", cap_resp.text)
296
  text = re.sub(r"\s+", " ", text).strip()
297
  return text[:8000]
 
319
  return "PDF appears to be empty or image-only (no extractable text)."
320
  return text[:15000]
321
  except Exception as e:
322
+ return f"PDF error: {e}"