wt002 commited on
Commit
20c067b
·
verified ·
1 Parent(s): 43257f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -624
app.py CHANGED
@@ -1,642 +1,78 @@
1
-
2
  import os
 
 
3
  import gradio as gr
4
- import requests
5
- import inspect
6
  import pandas as pd
7
- from smolagents import tool, Tool, CodeAgent, DuckDuckGoSearchTool, HfApiModel, VisitWebpageTool, SpeechToTextTool, FinalAnswerTool
8
- from dotenv import load_dotenv
9
- import heapq
10
- from collections import Counter
11
- import re
12
- from io import BytesIO
13
- from youtube_transcript_api import YouTubeTranscriptApi
14
- from langchain_community.tools.tavily_search import TavilySearchResults
15
- from langchain_community.document_loaders import WikipediaLoader
16
- from langchain_community.utilities import WikipediaAPIWrapper
17
- from langchain_community.document_loaders import ArxivLoader
18
-
 
 
 
 
19
 
20
- # (Keep Constants as is)
21
  # --- Constants ---
22
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
23
 
24
- #Load environment variables
25
- load_dotenv()
26
-
27
-
28
- import os
29
- import time
30
- import json
31
- from typing import TypedDict, List, Union, Any, Dict, Optional
32
-
33
- # LangChain and LangGraph imports
34
- from langchain.schema import HumanMessage, AIMessage, SystemMessage
35
- from langchain.prompts import ChatPromptTemplate
36
- from langgraph.graph import StateGraph, END
37
- from langchain_community.llms import HuggingFacePipeline
38
- from typing import List, Union, Dict, Any, TypedDict # Ensure all types are imported
39
-
40
- import torch
41
- from langchain_core.messages import AIMessage, HumanMessage # Corrected import for message types
42
- from langchain_core.tools import BaseTool
43
- from langchain_community.embeddings import HuggingFaceEmbeddings
44
- from langchain_community.vectorstores import FAISS
45
- from langchain.text_splitter import RecursiveCharacterTextSplitter
46
- from langchain_core.documents import Document
47
- from langchain_community.llms import HuggingFacePipeline
48
- from langchain.prompts import ChatPromptTemplate # SystemMessage moved to langchain_core.messages
49
- from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
50
- from langgraph.graph import END, StateGraph
51
-
52
- # Corrected Tool import: Use 'tool' (lowercase)
53
- from langchain_core.tools import BaseTool, tool
54
-
55
- # Hugging Face local model imports
56
- from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
57
- import torch
58
-
59
- # Tool-specific imports
60
- from duckduckgo_search import DDGS
61
- import wikipedia
62
- import arxiv
63
- from transformers import pipeline as hf_pipeline # Renamed to avoid clash with main pipeline
64
- from youtube_transcript_api import YouTubeTranscriptApi
65
-
66
- # --- Helper function for python_execution tool ---
67
- def indent_code(code: str, indent: str = " ") -> str:
68
- """Indents multi-line code for execution within a function."""
69
- return "\n".join(indent + line for line in code.splitlines())
70
-
71
- # --- Tool Definitions ---
72
- @tool
73
- def duckduckgo_search(query: str) -> str:
74
- """Search web using DuckDuckGo. Returns top 3 results."""
75
- print(f"DEBUG: duckduckgo_search called with: {query}")
76
- try:
77
- with DDGS() as ddgs:
78
- return "\n\n".join(
79
- f"Title: {res['title']}\nURL: {res['href']}\nSnippet: {res['body']}"
80
- for res in ddgs.text(query, max_results=3)
81
- )
82
- except Exception as e:
83
- return f"Error performing DuckDuckGo search: {str(e)}"
84
-
85
- @tool
86
- def wikipedia_search(query: str) -> str:
87
- """Get Wikipedia summaries. Returns first 3 sentences."""
88
- print(f"DEBUG: wikipedia_search called with: {query}")
89
- try:
90
- return wikipedia.summary(query, sentences=3)
91
- except wikipedia.DisambiguationError as e:
92
- return f"Disambiguation options: {', '.join(e.options[:3])}"
93
- except wikipedia.PageError:
94
- return "Wikipedia page not found."
95
- except Exception as e:
96
- return f"Error performing Wikipedia search: {str(e)}"
97
-
98
- @tool
99
- def arxiv_search(query: str) -> str:
100
- """Search academic papers on arXiv. Returns top 3 results."""
101
- print(f"DEBUG: arxiv_search called with: {query}")
102
- try:
103
- results = arxiv.Search(
104
- query=query,
105
- max_results=3,
106
- sort_by=arxiv.SortCriterion.Relevance
107
- ).results()
108
-
109
- return "\n\n".join(
110
- f"Title: {r.title}\nAuthors: {', '.join(a.name for a in r.authors)}\n"
111
- f"Published: {r.published.strftime('%Y-%m-%d')}\nSummary: {r.summary[:250]}..."
112
- for r in results
113
- )
114
- except Exception as e:
115
- return f"Error performing ArXiv search: {str(e)}"
116
-
117
- @tool
118
- def document_qa(input_str: str) -> str:
119
- """Answer questions from documents. Input format: 'document_text||question'"""
120
- print(f"DEBUG: document_qa called with: {input_str}")
121
- try:
122
- if '||' not in input_str:
123
- return "Invalid format. Input must be: 'document_text||question'"
124
-
125
- context, question = input_str.split('||', 1)
126
- # Load QA model on first call or ensure it's loaded once globally.
127
- # It's better to load once in __init__ for BasicAgent if possible,
128
- # but this lazy loading prevents initial heavy load if tool is not used.
129
- qa_model = hf_pipeline('question-answering', model='deepset/roberta-base-squad2')
130
- return qa_model(question=question, context=context)['answer']
131
- except Exception as e:
132
- return f"Error answering question from document: {str(e)}"
133
-
134
- @tool
135
- def python_execution(code: str) -> str:
136
- """Execute Python code and return output.
137
- The code should assign its final result to a variable named '_result_value'.
138
- Example: '_result_value = 1 + 1'
139
- """
140
- print(f"DEBUG: python_execution called with: {code}")
141
- try:
142
- # Create isolated environment
143
- env = {}
144
- # Wrap code in a function to isolate scope and capture '_result_value'
145
- # The exec function is used carefully here. In a production environment,
146
- # consider a more robust and secure sandbox (e.g., Docker, dedicated service).
147
- exec(f"def __exec_fn__():\n{indent_code(code)}\n_result_value = __exec_fn__()", globals(), env)
148
- return str(env.get('_result_value', 'No explicit result assigned to "_result_value" variable.'))
149
- except Exception as e:
150
- return f"Python execution error: {str(e)}"
151
-
152
- class VideoTranscriptionTool(Tool):
153
- name: str = "transcript_video"
154
- # CORRECTED LINE BELOW: Added '=' for assignment
155
- description: str = "Fetch text transcript from YouTube videos using URL or ID. Use for any question involving video or audio. Input is the YouTube URL or ID."
156
-
157
- def _run(self, url_or_id: str) -> str:
158
- print(f"DEBUG: transcript_video called with: {url_or_id}")
159
- video_id = None
160
- # Basic parsing for common YouTube URL formats
161
- if "youtube.com/watch?v=" in url_or_id:
162
- video_id = url_or_id.split("v=")[1].split("&")[0]
163
- elif "youtu.be/" in url_or_id:
164
- video_id = url_or_id.split("youtu.be/")[1].split("?")[0]
165
- elif len(url_or_id.strip()) == 11 and not ("http://" in url_or_id or "https://" in url_or_id):
166
- video_id = url_or_id.strip() # Assume it's just the ID
167
-
168
- if not video_id:
169
- return f"Invalid or unsupported YouTube URL/ID: {url_or_id}. Please provide a valid YouTube URL or 11-character ID."
170
-
171
- try:
172
- transcription = YouTubeTranscriptApi.get_transcript(video_id)
173
- return " ".join([part['text'] for part in transcription])
174
-
175
- except Exception as e:
176
- return f"Error fetching transcript for video ID '{video_id}': {str(e)}. It might not have an English transcript, or the video is unavailable."
177
-
178
- def _arun(self, *args, **kwargs):
179
- raise NotImplementedError("Async not supported for this tool.")
180
-
181
-
182
- # ====== IMPORTS ======
183
- import json
184
- import time
185
- from typing import Dict, List, Tuple, Any, Optional
186
- from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
187
- from langchain_core.prompts import ChatPromptTemplate
188
- from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
189
- import torch
190
- from langchain_community.embeddings import HuggingFaceEmbeddings
191
- from langchain_community.vectorstores import FAISS
192
- from langchain.text_splitter import RecursiveCharacterTextSplitter
193
- from langchain_core.documents import Document
194
-
195
- # ====== TYPE DEFINITIONS ======
196
- AgentState = Dict[str, Any]
197
-
198
- # ====== DOCUMENT PROCESSING ======
199
- def create_vector_store() -> Optional[FAISS]:
200
- """Create vector store with predefined documents using FAISS"""
201
- try:
202
- # Define the documents
203
- documents = [
204
- Document(page_content="The capital of France is Paris.", metadata={"source": "geography"}),
205
- Document(page_content="Python is a popular programming language created by Guido van Rossum.", metadata={"source": "tech"}),
206
- Document(page_content="The Eiffel Tower is located in Paris, France.", metadata={"source": "landmarks"}),
207
- ]
208
-
209
- # Initialize embedding model
210
- embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
211
-
212
- # Split documents into chunks
213
- text_splitter = RecursiveCharacterTextSplitter(
214
- chunk_size=500,
215
- chunk_overlap=100
216
- )
217
- chunks = text_splitter.split_documents(documents)
218
-
219
- # Create FAISS vector store
220
- return FAISS.from_documents(
221
- documents=chunks,
222
- embedding=embeddings
223
- )
224
- except Exception as e:
225
- print(f"ERROR creating vector store: {str(e)}")
226
- return None
227
-
228
- # ====== AGENT HELPER FUNCTIONS ======
229
- def parse_agent_response(content: str) -> Tuple[str, str, str]:
230
- """Parse the agent's JSON response"""
231
- try:
232
- # Extract JSON from content
233
- json_start = content.find('{')
234
- json_end = content.rfind('}') + 1
235
- json_str = content[json_start:json_end]
236
-
237
- # Parse JSON
238
- response_dict = json.loads(json_str)
239
-
240
- reasoning = response_dict.get("Reasoning", "No reasoning provided")
241
- action = response_dict.get("Action", "No action specified")
242
- action_input = response_dict.get("Action Input", "No input provided")
243
-
244
- return reasoning, action, action_input
245
- except json.JSONDecodeError:
246
- print(f"WARNING: Failed to parse JSON from response: {content[:200]}...")
247
- return "Failed to parse response", "Final Answer", "Error: Could not parse agent response"
248
- except Exception as e:
249
- print(f"ERROR parsing agent response: {str(e)}")
250
- return "Error in parsing", "Final Answer", f"Internal error: {str(e)}"
251
-
252
- def should_continue(state: AgentState) -> str:
253
- """Determine if we should continue processing or end"""
254
- if state.get("final_answer"):
255
- return "end"
256
- if state.get("context", {}).get("pending_action"):
257
- return "action"
258
- return "reason"
259
-
260
- # ====== REASONING NODE ======
261
- def reasoning_node(state: AgentState) -> AgentState:
262
- """Node for analyzing questions and determining next steps"""
263
- print(f"DEBUG: Entering reasoning_node. Iteration: {state.get('iterations', 0)}")
264
-
265
- # Safely initialize state components
266
- state.setdefault("context", {})
267
- state.setdefault("reasoning", "")
268
- state.setdefault("iterations", 0)
269
- state.setdefault("current_task", "Understand the question and plan the next step.")
270
- state.setdefault("current_thoughts", "")
271
- state.setdefault("history", [])
272
-
273
- # Safely remove pending_action
274
- state["context"].pop("pending_action", None)
275
-
276
- # Initialize local HuggingFacePipeline
277
- model_name = "mistralai/Mistral-7B-Instruct-v0.2"
278
-
279
- try:
280
- print(f"DEBUG: Loading local model: {model_name}...")
281
- tokenizer = AutoTokenizer.from_pretrained(model_name)
282
-
283
- # Determine torch dtype based on available hardware
284
- if torch.cuda.is_available():
285
- torch_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
286
- else:
287
- torch_dtype = torch.float32
288
-
289
- model = AutoModelForCausalLM.from_pretrained(
290
- model_name,
291
- torch_dtype=torch_dtype,
292
- device_map="auto"
293
- )
294
-
295
- # Create transformers pipeline
296
- pipe = pipeline(
297
- "text-generation",
298
- model=model,
299
- tokenizer=tokenizer,
300
- max_new_tokens=1024,
301
- temperature=0.1,
302
- do_sample=True,
303
- top_p=0.9,
304
- repetition_penalty=1.1,
305
  )
306
 
307
- llm = HuggingFacePipeline(pipeline=pipe)
308
- except Exception as e:
309
- print(f"ERROR loading model: {str(e)}")
310
- state["history"].append(AIMessage(content=f"[ERROR] Failed to load model: {str(e)}"))
311
- state["final_answer"] = "Error: Failed to initialize language model"
312
- return state
313
-
314
- # Prepare tool descriptions
315
- tool_descriptions = "\n".join([
316
- f"- **{t.name}**: {t.description}" for t in state.get("tools", [])
317
- ])
318
-
319
- # RAG Retrieval
320
- rag_context = ""
321
- vector_store = state["context"].get("vector_store")
322
-
323
- if vector_store:
324
- try:
325
- # Perform retrieval
326
- relevant_docs = vector_store.similarity_search(
327
- state.get("question", ""),
328
- k=3
329
- )
330
 
331
- # Format context for LLM
332
- rag_context = "\n\n[Relevant Knowledge]\n"
333
- rag_context += "\n---\n".join([doc.page_content for doc in relevant_docs])
334
- except Exception as e:
335
- print(f"WARNING: RAG retrieval failed: {str(e)}")
336
- rag_context = "\n\n[Relevant Knowledge] Retrieval failed. Proceeding without additional context."
337
- else:
338
- print("WARNING: No vector store available for RAG")
339
- rag_context = "\n\n[Relevant Knowledge] No knowledge base available."
340
-
341
- # Renamed the variable to emphasize it's a template string, not a SystemMessage object
342
- system_prompt_template_str = (
343
- "You are an expert problem solver, designed to provide concise and accurate answers. "
344
- "Your process involves analyzing the question, intelligently selecting and using tools, "
345
- "and synthesizing information.\n\n"
346
- "**Available Tools:**\n"
347
- f"{tool_descriptions}\n\n"
348
- "**Tool Usage Guidelines:**\n"
349
- "- Use **duckduckgo_search** for current events, general facts, or quick lookups. Provide a concise search query. Example: `What is the population of New York?`\n"
350
- "- Use **wikipedia_search** for encyclopedic information, historical context, or detailed topics. Provide a concise search term. Example: `Eiffel Tower history`\n"
351
- "- Use **arxiv_search** for scientific papers, research, or cutting-edge technical information. Provide a concise search query. Example: `Large Language Models recent advances`\n"
352
- "- Use **document_qa** when the question explicitly refers to a specific document or when you have content to query. Input format: 'document_text||question'. Example: `The capital of France is Paris.||What is the capital of France?`\n"
353
- "- Use **python_execution** for complex calculations, data manipulation, or logical operations that cannot be done with simple reasoning. Always provide the full Python code, ensuring it's valid and executable, and assign the final result to a variable named '_result_value'. Example: `_result_value = 1 + 1`\n"
354
- "- Use **transcript_video** for any question involving video or audio content (e.g., YouTube). Provide the full YouTube URL or video ID. Example: `youtube.com`\n\n"
355
- "**Crucial Instructions:**\n"
356
- "1. **Always aim to provide a definitive answer.** If you have enough information, use the 'final answer' action.\n"
357
- "2. **To provide a final answer, use the Action 'final answer' with the complete answer in 'Action Input'.** This is how you tell me you're done. Example:\n"
358
- " ```json\n"
359
- " {\n"
360
- " \"Reasoning\": \"I have found the capital of France.\",\n"
361
- " \"Action\": \"final answer\",\n"
362
- " \"Action Input\": \"The capital of France is Paris.\"\n"
363
- " }\n"
364
- " ```\n"
365
- "3. **If you need more information or cannot answer yet, select an appropriate tool and provide a clear, concise query.**\n"
366
- "4. **Think step-by-step.** Reflect on previous tool outputs and the question.\n"
367
- "5. **Do NOT repeat actions or search queries unless the previous attempt yielded an error.**\n\n"
368
- "**Retrieved Context:**\n{rag_context}\n\n"
369
- "**Current Context (Tool Outputs/Intermediate Info):**\n{context}\n\n"
370
- "**Previous Reasoning Steps:**\n{reasoning}\n\n"
371
- "**Current Task:** {current_task}\n"
372
- "**Current Thoughts:** {current_thoughts}\n\n"
373
- "**Question:** {question}\n\n"
374
- "**Expected JSON Output Format:**\n"
375
- "```json\n"
376
- "{\n"
377
- " \"Reasoning\": \"Your reasoning process to decide the next step, including why a tool is chosen or how an answer is derived.\",\n"
378
- " \"Action\": \"The name of the tool to use (e.g., duckduckgo_search, final answer, No Action), if no tool is needed yet, use 'No Action'.\",\n"
379
- " \"Action Input\": \"The input for the tool (e.g., 'What is the capital of France?', 'The final answer is Paris.').\"\n"
380
- "}\n"
381
- "```\n"
382
- "Ensure your response is ONLY valid JSON and strictly follows this format. Begin your response with ````json`."
383
- )
384
-
385
- prompt = ChatPromptTemplate.from_messages([
386
- # --- CHANGE THIS LINE ---
387
- # Pass the system message as a tuple (role, content_template_string)
388
- ("system", system_prompt_template_str),
389
- *state["history"]
390
- ])
391
-
392
- # Format messages safely
393
- formatted_messages = prompt.format_messages(
394
- rag_context=rag_context,
395
- context=state.get("context", {}),
396
- reasoning=state.get("reasoning", ""),
397
- question=state.get("question", ""),
398
- current_task=state.get("current_task", ""),
399
- current_thoughts=state.get("current_thoughts", "")
400
- )
401
-
402
- # Format full input string
403
- try:
404
- full_input_string = tokenizer.apply_chat_template(
405
- formatted_messages,
406
- tokenize=False,
407
- add_generation_prompt=True
408
- )
409
- except Exception as e:
410
- print(f"WARNING: Failed to apply chat template: {e}. Using simple join.")
411
- full_input_string = "\n".join([msg.content for msg in formatted_messages])
412
-
413
- # Call LLM with retry
414
- def call_with_retry_local(inputs: str, retries: int = 3) -> AIMessage:
415
- for attempt in range(retries):
416
- try:
417
- response_text = llm.invoke(inputs)
418
-
419
- # Strip the prompt from the generated text
420
- if response_text.startswith(inputs):
421
- content = response_text[len(inputs):].strip()
422
- else:
423
- content = response_text.strip()
424
-
425
- print(f"DEBUG: RAW LOCAL LLM Response (Attempt {attempt+1}):\n---\n{content}\n---")
426
-
427
- # Attempt to parse to validate structure
428
- json.loads(content)
429
- return AIMessage(content=content)
430
- except json.JSONDecodeError as e:
431
- print(f"[Retry {attempt+1}/{retries}] Invalid JSON. Error: {e}.")
432
- print(f"Invalid content: {content[:200]}...")
433
- state["history"].append(AIMessage(content=f"[Parsing Error] The previous LLM output was not valid JSON. Expected format: ```json{{\"Reasoning\": \"...\", \"Action\": \"...\", \"Action Input\": \"...\"}}```. Please ensure your response is ONLY valid JSON and strictly follows the format. Error: {e}"))
434
- time.sleep(3)
435
- except Exception as e:
436
- print(f"[Retry {attempt+1}/{retries}] Error: {e}.")
437
- state["history"].append(AIMessage(content=f"[LLM Error] Failed to get response: {e}. Trying again."))
438
- time.sleep(5)
439
- return AIMessage(content='{"Reasoning": "Max retries exceeded", "Action": "Final Answer", "Action Input": "Error: Failed after multiple retries"}')
440
-
441
- response = call_with_retry_local(full_input_string)
442
- content = response.content
443
-
444
- # Parse response
445
- reasoning, action, action_input = parse_agent_response(content)
446
-
447
- print(f"DEBUG: Parsed Action: '{action}', Action Input: '{action_input[:100]}...'")
448
-
449
- # Update state
450
- state["history"].append(AIMessage(content=content))
451
- state["reasoning"] += f"\nStep {state['iterations'] + 1}: {reasoning}"
452
- state["iterations"] += 1
453
- state["current_thoughts"] = reasoning
454
-
455
- if "final answer" in action.lower():
456
- state["final_answer"] = action_input
457
- else:
458
- state["context"]["pending_action"] = {
459
- "tool": action,
460
- "input": action_input
461
- }
462
- state["history"].append(AIMessage(content=f"Agent decided to use tool: {action} with input: {action_input}"))
463
-
464
- print(f"DEBUG: Exiting reasoning_node. New history length: {len(state['history'])}")
465
- return state
466
 
467
- # ====== TOOL NODE ======
468
- def tool_node(state: AgentState) -> AgentState:
469
- """Node for executing the chosen tool"""
470
- print(f"DEBUG: Entering tool_node. Iteration: {state.get('iterations', 0)}")
471
-
472
- # Safely get pending action
473
- tool_call_dict = state.get("context", {}).pop("pending_action", None)
474
 
475
- if not tool_call_dict:
476
- error_message = "[Tool Error] No pending_action found in context."
477
- print(f"ERROR: {error_message}")
478
- state.setdefault("history", []).append(AIMessage(content=error_message))
479
- return state
480
 
481
- tool_name = tool_call_dict.get("tool", "")
482
- tool_input = tool_call_dict.get("input", "")
483
 
484
- if not tool_name or not tool_input:
485
- error_message = f"[Tool Error] Invalid action: Tool name '{tool_name}' or input '{tool_input}' was empty."
486
- print(f"ERROR: {error_message}")
487
- state["history"].append(AIMessage(content=error_message))
488
- return state
489
-
490
- # Find and execute tool
491
- available_tools = state.get("tools", [])
492
- tool_fn = next((t for t in available_tools if t.name == tool_name), None)
493
-
494
- if tool_fn is None:
495
- tool_output = f"[Tool Error] Tool '{tool_name}' not found. Available: {', '.join([t.name for t in available_tools])}"
496
- print(f"ERROR: {tool_output}")
497
- else:
498
- try:
499
- print(f"DEBUG: Invoking tool '{tool_name}' with input: '{tool_input[:100]}...'")
500
- tool_output = tool_fn.run(tool_input)
501
- if tool_output is None:
502
- tool_output = f"[{tool_name} output] No result returned for '{tool_input}'."
503
- except Exception as e:
504
- tool_output = f"[Tool Error] Error running '{tool_name}': {str(e)}"
505
- print(f"ERROR: {tool_output}")
506
-
507
- state["history"].append(AIMessage(content=f"[{tool_name} output]\n{tool_output}"))
508
-
509
- print(f"DEBUG: Exiting tool_node. Tool output added to history.")
510
- return state
511
-
512
- # ====== AGENT GRAPH ======
513
- class StateGraph:
514
- """Simple state graph implementation"""
515
- def __init__(self, state: AgentState):
516
- self.nodes = {}
517
- self.entry_point = None
518
- self.edges = {}
519
- self.conditional_edges = {}
520
-
521
- def add_node(self, name: str, func: callable):
522
- self.nodes[name] = func
523
 
524
- def set_entry_point(self, name: str):
525
- self.entry_point = name
526
-
527
- def add_conditional_edges(self, source: str, condition: callable, path_map: Dict[str, str]):
528
- self.conditional_edges[source] = (condition, path_map)
529
-
530
- def add_edge(self, source: str, dest: str):
531
- self.edges[source] = dest
532
-
533
- def compile(self):
534
- def app(state: AgentState) -> AgentState:
535
- current_node = self.entry_point
536
-
537
- while current_node != END:
538
- if current_node in self.nodes:
539
- state = self.nodes[current_node](state)
540
-
541
- if current_node in self.conditional_edges:
542
- condition, path_map = self.conditional_edges[current_node]
543
- next_node_key = condition(state)
544
- current_node = path_map.get(next_node_key, END)
545
- elif current_node in self.edges:
546
- current_node = self.edges[current_node]
547
- else:
548
- current_node = END
549
-
550
- return state
551
-
552
- return app
553
-
554
- END = "__END__"
555
-
556
- # ====== AGENT INTERFACE ======
557
- class BasicAgent:
558
- def __init__(self):
559
- # Instantiate tools (implementation not shown - should be defined elsewhere)
560
- self.tools = [
561
- duckduckgo_search,
562
- wikipedia_search,
563
- arxiv_search,
564
- document_qa,
565
- python_execution,
566
- VideoTranscriptionTool()
567
- ]
568
-
569
- # Pre-initialize RAG vector store
570
- try:
571
- self.vector_store = create_vector_store()
572
- if not self.vector_store:
573
- print("WARNING: Vector store creation failed. Proceeding without RAG.")
574
- except Exception as e:
575
- print(f"ERROR creating vector store: {str(e)}")
576
- self.vector_store = None
577
-
578
- self.workflow = create_agent_workflow(self.tools)
579
-
580
- def __call__(self, question: str) -> str:
581
- print(f"\n--- Agent received question: {question[:80]}{'...' if len(question) > 80 else ''} ---")
582
-
583
- state = {
584
- "question": question,
585
- "context": {
586
- "vector_store": self.vector_store
587
- },
588
- "reasoning": "",
589
- "iterations": 0,
590
- "history": [HumanMessage(content=question)],
591
- "final_answer": None,
592
- "current_task": "Understand the question and plan the next step.",
593
- "current_thoughts": "",
594
- "tools": self.tools
595
- }
596
-
597
- try:
598
- final_state = self.workflow.invoke(state)
599
- if final_state.get("final_answer") is not None:
600
- answer = final_state["final_answer"]
601
- print(f"--- Agent returning FINAL ANSWER: {answer} ---")
602
- return answer
603
- else:
604
- print("--- ERROR: Agent finished without setting 'final_answer' ---")
605
- if final_state.get("history"):
606
- last_message = final_state["history"][-1].content
607
- print(f"Last message: {last_message}")
608
- return f"Agent could not answer. Last message: {last_message}"
609
- return "Error: Agent failed to provide an answer"
610
- except Exception as e:
611
- print(f"FATAL ERROR during agent execution: {str(e)}")
612
- return f"Agent encountered a fatal error: {str(e)}"
613
-
614
- def create_agent_workflow(tools: List[Any]):
615
- workflow = StateGraph(AgentState)
616
-
617
- workflow.add_node("reason", reasoning_node)
618
- workflow.add_node("action", tool_node)
619
-
620
- workflow.set_entry_point("reason")
621
-
622
- workflow.add_conditional_edges(
623
- "reason",
624
- should_continue,
625
- {
626
- "action": "action",
627
- "reason": "reason",
628
- "end": END
629
- }
630
- )
631
-
632
- workflow.add_edge("action", "reason")
633
-
634
- app = workflow.compile()
635
- return app
636
-
637
-
638
-
639
-
640
  def run_and_submit_all( profile: gr.OAuthProfile | None):
641
  """
642
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
 
1
  import os
2
+ import time
3
+
4
  import gradio as gr
 
 
5
  import pandas as pd
6
+ import requests
7
+ from smolagents import (
8
+ CodeAgent,
9
+ DuckDuckGoSearchTool,
10
+ LiteLLMModel,
11
+ WikipediaSearchTool,
12
+ FinalAnswerTool
13
+ )
14
+
15
+ from agent import (
16
+ analyze_audio_file,
17
+ analyze_image_file,
18
+ analyze_xlsx_file,
19
+ analyze_youtube_video,
20
+ download_file_of_task_id,
21
+ )
22
 
 
23
  # --- Constants ---
24
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
25
 
26
+ # --- Basic Agent Definition ---
27
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
28
+ class BasicAgent:
29
+ def __init__(self):
30
+ print("BasicAgent initialized.")
31
+
32
+ # Initialize the model
33
+ model = LiteLLMModel(model_id=os.getenv("MODEL_ID"),
34
+ api_key=os.getenv("API_KEY"))
35
+
36
+ # Initialize the searchs tool
37
+ duck_duck_go_search_tool = DuckDuckGoSearchTool()
38
+ wikipedia_search_tool = WikipediaSearchTool()
39
+
40
+ # Initialize Agent
41
+ self.agent = CodeAgent(
42
+ model = model,
43
+ tools=[download_file_of_task_id, analyze_audio_file, analyze_image_file,
44
+ analyze_xlsx_file, duck_duck_go_search_tool, wikipedia_search_tool,
45
+ analyze_youtube_video, FinalAnswerTool()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  )
47
 
48
+ def __call__(self, question: str, task_id: str) -> str:
49
+ task = f"""
50
+ You are a general AI assistant.
51
+ I will ask you a question and you can use 8 steps to answer it.
52
+ You can use the tools I provided to you to answer the question.
53
+ Every time you use a tool, the number of steps will decrease by one.
54
+ If you have a list of possible pages to visit, prefer the wikipedia ones.
55
+ If a page does not allow visit, skip it.
56
+ Report your thoughts, and finish your answer with the following template:
57
+ FINAL ANSWER: [YOUR FINAL ANSWER].
58
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
59
+ If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
60
+ If the answer is a number, represent it with digits.
61
+ If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
62
+ If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
 
 
 
 
 
 
 
 
63
 
64
+ The taskid is {task_id} in case you need to get extra files, use taskid and not name of the file
65
+ and the question is {question}
66
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ fixed_answer = self.agent.run(task)
69
+ print(f"Agent returning fixed answer: {fixed_answer}")
70
+ time.sleep(50)
 
 
 
 
71
 
72
+ return fixed_answer
 
 
 
 
73
 
 
 
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def run_and_submit_all( profile: gr.OAuthProfile | None):
77
  """
78
  Fetches all questions, runs the BasicAgent on them, submits all answers,