wt002 commited on
Commit
8777f65
·
verified ·
1 Parent(s): 1f27438

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +303 -47
app.py CHANGED
@@ -22,6 +22,93 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
22
  #Load environment variables
23
  load_dotenv()
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  from duckduckgo_search import DDGS
26
  import wikipedia
27
  import arxiv
@@ -138,73 +225,242 @@ class FinalAnswerTool:
138
  def run(self, answer: str) -> str:
139
  return f"FINAL ANSWER: {answer}"
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  class BasicAgent:
142
  def __init__(self):
143
  token = os.environ.get("HF_API_TOKEN")
144
  model = HfApiModel(
145
- temperature=0.0, # Reduced for deterministic output
146
  token=token
147
  )
148
 
149
- # Curated toolset - remove redundant/conflicting tools
150
  search_tool = DuckDuckGoSearchTool()
151
  wiki_search_tool = WikiSearchTool()
 
 
 
 
 
 
 
 
 
152
  arxiv_search_tool = ArxivSearchTool()
153
  doc_qa_tool = HuggingFaceDocumentQATool()
 
 
154
  python_tool = PythonCodeExecutionTool()
155
- final_answer_tool = FinalAnswerTool()
156
 
157
- # Strategic tool selection
158
- tools = [
159
- search_tool,
160
- wiki_search_tool,
161
- arxiv_search_tool,
162
- doc_qa_tool,
163
- python_tool,
164
- final_answer_tool
165
- ]
166
-
167
- # Enhanced system prompt
168
- system_prompt = """
169
- You are a precision question-answering AI. Follow this protocol:
170
- 1. Analyze the question type: factual, computational, or multi-step
171
- 2. Select the optimal tool:
172
- - Use Search/Wiki/Arxiv for factual queries
173
- - Use Python tool for calculations
174
- - Use DocQA for document-based questions
175
- 3. Execute necessary actions
176
- 4. Verify answer matches question requirements
177
- 5. Output FINAL ANSWER using this format:
178
- "FINAL ANSWER: [EXACT_RESULT]"
179
-
180
- Answer rules:
181
- - Numbers: Plain format (e.g., 1000000)
182
- - Strings: No articles/abbreviations (e.g., "Paris" not "city of Paris")
183
- - Lists: Comma-separated (e.g., "red,blue,green")
184
- - Never include units ($, kg, etc.) unless explicitly required
185
- - For true/false: Use "true" or "false" lowercase
 
 
186
  """
 
 
187
 
188
  self.agent = CodeAgent(
189
  model=model,
190
- tools=tools,
191
- add_base_tools=False # Prevent tool conflicts
 
 
 
 
 
 
 
 
 
192
  )
193
- # Force strict prompt template
194
- self.agent.prompt_templates["system_prompt"] = system_prompt
195
 
196
  def __call__(self, question: str) -> str:
197
- print(f"Processing: {question[:50]}...")
198
- try:
199
- result = self.agent.run(question)
200
- # Extract final answer using regex
201
- import re
202
- match = re.search(r"FINAL ANSWER:\s*(.+)", result, re.IGNORECASE)
203
- return match.group(1).strip() if match else result
204
- except Exception as e:
205
- print(f"Error: {str(e)}")
206
- return "Unable to determine answer"
207
-
208
 
209
 
210
  def run_and_submit_all( profile: gr.OAuthProfile | None):
 
22
  #Load environment variables
23
  load_dotenv()
24
 
25
+ import io
26
+ import contextlib
27
+ import traceback
28
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
29
+ from smolagents import Tool, CodeAgent, DuckDuckGoSearchTool, FinalAnswerTool, HfApiModel
30
+
31
+
32
+ class CodeLlamaTool(Tool):
33
+ name = "code_llama_tool"
34
+ description = "Solves reasoning/code questions using Meta Code Llama 7B Instruct"
35
+
36
+ inputs = {
37
+ "question": {
38
+ "type": "string",
39
+ "description": "The question requiring code-based or reasoning-based solution"
40
+ }
41
+ }
42
+ output_type = "string"
43
+
44
+ def __init__(self):
45
+ self.model_id = "codellama/CodeLlama-7b-Instruct-hf"
46
+ token = os.getenv("HF_TOKEN")
47
+
48
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_id, token=token)
49
+ self.model = AutoModelForCausalLM.from_pretrained(
50
+ self.model_id,
51
+ device_map="auto",
52
+ torch_dtype="auto",
53
+ token=token
54
+ )
55
+ self.pipeline = pipeline(
56
+ "text-generation",
57
+ model=self.model,
58
+ tokenizer=self.tokenizer,
59
+ max_new_tokens=512,
60
+ temperature=0.2,
61
+ truncation=True
62
+ )
63
+
64
+ def forward(self, question: str) -> str:
65
+ prompt = f"""You are an AI that uses Python code to answer questions.
66
+ Question: {question}
67
+ Instructions:
68
+ - If solving requires code, use a block like <tool>code</tool>.
69
+ - Always end with <final>FINAL ANSWER</final> containing the final number or string.
70
+ Example:
71
+ Question: What is 5 * sqrt(36)?
72
+ Answer:
73
+ <tool>
74
+ import math
75
+ print(5 * math.sqrt(36))
76
+ </tool>
77
+ <final>30.0</final>
78
+ Answer:"""
79
+
80
+ response = self.pipeline(prompt)[0]["generated_text"]
81
+ return self.parse_and_execute(response)
82
+
83
+ def parse_and_execute(self, response: str) -> str:
84
+ try:
85
+ # Extract and run code if exists
86
+ if "<tool>" in response and "</tool>" in response:
87
+ code = response.split("<tool>")[1].split("</tool>")[0].strip()
88
+ result = self._run_code(code)
89
+ return f"FINAL ANSWER (code output): {result}"
90
+
91
+ # Extract final result directly
92
+ elif "<final>" in response and "</final>" in response:
93
+ final = response.split("<final>")[1].split("</final>")[0].strip()
94
+ return f"FINAL ANSWER: {final}"
95
+
96
+ return f"Could not extract final answer.\n\n{response}"
97
+
98
+ except Exception as e:
99
+ return f"Error in parse_and_execute: {str(e)}\n\nFull response:\n{response}"
100
+
101
+ def _run_code(self, code: str) -> str:
102
+ buffer = io.StringIO()
103
+ try:
104
+ with contextlib.redirect_stdout(buffer):
105
+ exec(code, {})
106
+ return buffer.getvalue().strip()
107
+ except Exception:
108
+ return f"Error executing code:\n{traceback.format_exc()}"
109
+
110
+
111
+
112
  from duckduckgo_search import DDGS
113
  import wikipedia
114
  import arxiv
 
225
  def run(self, answer: str) -> str:
226
  return f"FINAL ANSWER: {answer}"
227
 
228
+ #from smolagents import Tool
229
+ #from langchain_community.document_loaders import WikipediaLoader
230
+
231
+ class WikiSearchTool(Tool):
232
+ name = "wiki_search"
233
+ description = "Search Wikipedia for a query and return up to 2 results."
234
+ inputs = {
235
+ "query": {"type": "string", "description": "The search term for Wikipedia."}
236
+ }
237
+ output_type = "string"
238
+
239
+ def forward(self, query: str) -> str:
240
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
241
+
242
+ formatted_search_docs = "\n\n---\n\n".join(
243
+ [
244
+ f'<Document source="{doc.metadata.get("source", "Wikipedia")}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
245
+ for doc in search_docs
246
+ ]
247
+ )
248
+ return formatted_search_docs
249
+
250
+
251
+
252
+
253
+ class StringReverseTool(Tool):
254
+ name = "reverse_message"
255
+ description = "When you received a strange text, try to reverse it and perform action described in reversed message."
256
+ inputs = {
257
+ "message": {
258
+ "type": "string",
259
+ "description": "A message, which looks like strange and can be reversed to get actions to execute."
260
+ }
261
+ }
262
+ output_type = "string"
263
+
264
+ def __init__(self):
265
+ return
266
+
267
+ def forward(self, message: str):
268
+ return message[::-1]
269
+
270
+ class KeywordsExtractorTool(Tool):
271
+ """Extracts top 5 keywords from a given text based on frequency."""
272
+
273
+ name = "keywords_extractor"
274
+ description = "This tool returns the 5 most frequent keywords occur in provided block of text."
275
+
276
+ inputs = {
277
+ "text": {
278
+ "type": "string",
279
+ "description": "Text to analyze for keywords.",
280
+ }
281
+ }
282
+ output_type = "string"
283
+
284
+ def forward(self, text: str) -> str:
285
+ try:
286
+ all_words = re.findall(r'\b\w+\b', text.lower())
287
+ conjunctions = {'a', 'and', 'of', 'is', 'in', 'to', 'the'}
288
+ filtered_words = []
289
+ for w in all_words:
290
+ if w not in conjunctions:
291
+ filtered_words.push(w)
292
+ word_counts = Counter(filtered_words)
293
+ k = 5
294
+ return heapq.nlargest(k, word_counts.items(), key=lambda x: x[1])
295
+ except Exception as e:
296
+ return f"Error during extracting most common words: {e}"
297
+
298
+ @tool
299
+ def parse_excel_to_json(task_id: str) -> dict:
300
+ """
301
+ For a given task_id fetch and parse an Excel file and save parsed data in structured JSON file.
302
+ Args:
303
+ task_id: An task ID to fetch.
304
+
305
+ Returns:
306
+ {
307
+ "task_id": str,
308
+ "sheets": {
309
+ "SheetName1": [ {col1: val1, col2: val2, ...}, ... ],
310
+ ...
311
+ },
312
+ "status": "Success" | "Error"
313
+ }
314
+ """
315
+ url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
316
+
317
+ try:
318
+ response = requests.get(url, timeout=100)
319
+ if response.status_code != 200:
320
+ return {"task_id": task_id, "sheets": {}, "status": f"{response.status_code} - Failed"}
321
+
322
+ xls_content = pd.ExcelFile(BytesIO(response.content))
323
+ json_sheets = {}
324
+
325
+ for sheet in xls_content.sheet_names:
326
+ df = xls_content.parse(sheet)
327
+ df = df.dropna(how="all")
328
+ rows = df.head(20).to_dict(orient="records")
329
+ json_sheets[sheet] = rows
330
+
331
+ return {
332
+ "task_id": task_id,
333
+ "sheets": json_sheets,
334
+ "status": "Success"
335
+ }
336
+
337
+ except Exception as e:
338
+ return {
339
+ "task_id": task_id,
340
+ "sheets": {},
341
+ "status": f"Error in parsing Excel file: {str(e)}"
342
+ }
343
+
344
+
345
+
346
+ class VideoTranscriptionTool(Tool):
347
+ """Fetch transcripts from YouTube videos"""
348
+ name = "transcript_video"
349
+ description = "Fetch text transcript from YouTube movies with optional timestamps"
350
+ inputs = {
351
+ "url": {"type": "string", "description": "YouTube video URL or ID"},
352
+ "include_timestamps": {"type": "boolean", "description": "If timestamps should be included in output", "nullable": True}
353
+ }
354
+ output_type = "string"
355
+
356
+ def forward(self, url: str, include_timestamps: bool = False) -> str:
357
+
358
+ if "youtube.com/watch" in url:
359
+ video_id = url.split("v=")[1].split("&")[0]
360
+ elif "youtu.be/" in url:
361
+ video_id = url.split("youtu.be/")[1].split("?")[0]
362
+ elif len(url.strip()) == 11: # Direct ID
363
+ video_id = url.strip()
364
+ else:
365
+ return f"YouTube URL or ID: {url} is invalid!"
366
+
367
+ try:
368
+ transcription = YouTubeTranscriptApi.get_transcript(video_id)
369
+
370
+ if include_timestamps:
371
+ formatted_transcription = []
372
+ for part in transcription:
373
+ timestamp = f"{int(part['start']//60)}:{int(part['start']%60):02d}"
374
+ formatted_transcription.append(f"[{timestamp}] {part['text']}")
375
+ return "\n".join(formatted_transcription)
376
+ else:
377
+ return " ".join([part['text'] for part in transcription])
378
+
379
+ except Exception as e:
380
+ return f"Error in extracting YouTube transcript: {str(e)}"
381
+
382
  class BasicAgent:
383
  def __init__(self):
384
  token = os.environ.get("HF_API_TOKEN")
385
  model = HfApiModel(
386
+ temperature=0.1,
387
  token=token
388
  )
389
 
390
+ # Existing tools
391
  search_tool = DuckDuckGoSearchTool()
392
  wiki_search_tool = WikiSearchTool()
393
+ str_reverse_tool = StringReverseTool()
394
+ keywords_extract_tool = KeywordsExtractorTool()
395
+ speech_to_text_tool = SpeechToTextTool()
396
+ visit_webpage_tool = VisitWebpageTool()
397
+ final_answer_tool = FinalAnswerTool()
398
+ video_transcription_tool = VideoTranscriptionTool()
399
+
400
+ # ✅ New Llama Tool
401
+ code_llama_tool = CodeLlamaTool()
402
  arxiv_search_tool = ArxivSearchTool()
403
  doc_qa_tool = HuggingFaceDocumentQATool()
404
+ image_qa_tool = HuggingFaceImageQATool()
405
+ translation_tool = HuggingFaceTranslationTool()
406
  python_tool = PythonCodeExecutionTool()
 
407
 
408
+ system_prompt = f"""
409
+ You are my general AI assistant. Your primary goal is to answer the user's question accurately and concisely.
410
+
411
+ Here's a detailed plan for answering:
412
+ 1. **Understand the Question:** Carefully parse the question to identify key entities, relationships, and the type of information requested.
413
+ 2. **Reasoning Steps (Chain-of-Thought):** Before attempting to answer, outline a step-by-step reasoning process. This helps in breaking down complex questions.
414
+ 3. **Tool Selection and Usage:** Based on your reasoning, select the most appropriate tool(s) to gather information or perform operations.
415
+ - Use `search_tool` (DuckDuckGoSearchTool) for general web searches.
416
+ - Use `wiki_search_tool` for encyclopedic knowledge.
417
+ - Use `arxiv_search_tool` for scientific papers.
418
+ - Use `visit_webpage_tool` to read content from URLs found via search.
419
+ - Use `doc_qa_tool` for answering questions about specific documents (if provided).
420
+ - Use `image_qa_tool` for questions about images.
421
+ - Use `translation_tool` for language translation.
422
+ - Use `python_tool` or `code_llama_tool` for code generation, execution, or complex calculations/data manipulation.
423
+ - Use `keywords_extract_tool` to identify important terms from text.
424
+ - Use `str_reverse_tool` for string manipulation if needed (less common for Q&A).
425
+ - Use `speech_to_text_tool` or `video_transcription_tool` if audio/video input is part of the question.
426
+ - Use `parse_excel_to_json` if the question involves data from Excel.
427
+ 4. **Information Synthesis:** Combine and process the information obtained from tools. Cross-reference if necessary to ensure accuracy.
428
+ 5. **Formulate Final Answer:** Construct the final answer according to the specified format.
429
+
430
+ **Final Answer Format:**
431
+ Return your final answer in a single line, formatted as follows: "FINAL ANSWER: [YOUR FINAL ANSWER]".
432
+ [YOUR FINAL ANSWER] should be a number, a string, or a comma-separated list of numbers and/or strings, depending on the question.
433
+ - If the answer is a number, do not use commas or units (e.g., $, %) unless explicitly specified in the question.
434
+ - If the answer is a string, do not use articles (a, an, the) or common abbreviations (e.g., "NY" for "New York") unless specified. Write digits in plain text unless specified.
435
+ - If the answer is a comma-separated list, apply the above rules for each element based on whether it is a number or a string.
436
+ - If you cannot find a definitive answer, state "FINAL ANSWER: I don't know."
437
+
438
+ Let's think step by step.
439
  """
440
+ self.agent.prompt_templates["system_prompt"] = self.agent.prompt_templates["system_prompt"] + system_prompt
441
+
442
 
443
  self.agent = CodeAgent(
444
  model=model,
445
+ tools=[
446
+ search_tool, wiki_search_tool, str_reverse_tool,
447
+ keywords_extract_tool, speech_to_text_tool,
448
+ visit_webpage_tool, final_answer_tool,
449
+ parse_excel_to_json, video_transcription_tool,
450
+ arxiv_search_tool,
451
+ doc_qa_tool, image_qa_tool,
452
+ translation_tool, python_tool,
453
+ code_llama_tool # 🔧 Add here
454
+ ],
455
+ add_base_tools=True
456
  )
457
+ self.agent.prompt_templates["system_prompt"] = self.agent.prompt_templates["system_prompt"] + system_prompt
 
458
 
459
  def __call__(self, question: str) -> str:
460
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
461
+ answer = self.agent.run(question)
462
+ print(f"Agent returning answer: {answer}")
463
+ return answer
 
 
 
 
 
 
 
464
 
465
 
466
  def run_and_submit_all( profile: gr.OAuthProfile | None):