naman1102 commited on
Commit
9450587
Β·
1 Parent(s): 9017277
Files changed (3) hide show
  1. agent.py +14 -2
  2. app.py +117 -31
  3. tools.py +20 -0
agent.py CHANGED
@@ -7,6 +7,7 @@ from state import AgentState
7
  from typing import Any, Dict, List, Optional
8
  import json
9
  from langgraph.prebuilt import create_react_agent
 
10
 
11
  # ─────────────────────────── External tools ──────────────────────────────
12
  from tools import (
@@ -20,7 +21,13 @@ from tools import (
20
 
21
  # ─────────────────────────── Configuration ───────────────────────────────
22
  MAX_TOOL_CALLS = 5
 
23
 
 
 
 
 
 
24
 
25
  # ─────────────────────────── Helper utilities ────────────────────────────
26
 
@@ -34,8 +41,13 @@ MAX_TOOL_CALLS = 5
34
  # ─────────────────────────── Graph wiring ───────────────────────────────
35
 
36
  def build_graph():
37
- """Build and return a create_react_agent."""
38
- llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0.3)
 
 
 
 
 
39
 
40
  llm_tools = [
41
  wikipedia_search_tool,
 
7
  from typing import Any, Dict, List, Optional
8
  import json
9
  from langgraph.prebuilt import create_react_agent
10
+ import signal
11
 
12
  # ─────────────────────────── External tools ──────────────────────────────
13
  from tools import (
 
21
 
22
  # ─────────────────────────── Configuration ───────────────────────────────
23
  MAX_TOOL_CALLS = 5
24
+ AGENT_TIMEOUT = 300 # 5 minutes timeout for agent execution
25
 
26
+ class TimeoutError(Exception):
27
+ pass
28
+
29
+ def timeout_handler(signum, frame):
30
+ raise TimeoutError("Agent execution timed out")
31
 
32
  # ─────────────────────────── Helper utilities ────────────────────────────
33
 
 
41
  # ─────────────────────────── Graph wiring ───────────────────────────────
42
 
43
  def build_graph():
44
+ """Build and return a create_react_agent with improved configuration."""
45
+ llm = ChatOpenAI(
46
+ model_name="gpt-4o-mini",
47
+ temperature=0.1, # Lower temperature for more consistent responses
48
+ max_tokens=2000, # Ensure reasonable response length
49
+ timeout=60 # 1 minute timeout for LLM calls
50
+ )
51
 
52
  llm_tools = [
53
  wikipedia_search_tool,
app.py CHANGED
@@ -5,6 +5,7 @@ import requests
5
  import pandas as pd
6
  from langchain.schema import HumanMessage, SystemMessage
7
  from typing import Optional
 
8
 
9
  from agent import build_graph
10
  from state import AgentState
@@ -15,6 +16,14 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
  SYSTEM_PROMPT = """
16
  You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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.
17
 
 
 
 
 
 
 
 
 
18
  IMPORTANT: When using tools that require file access (such as audio_transcriber_tool, excel_tool, analyze_code_tool, or image_tool), ALWAYS use the task_id parameter only. Do NOT use any file names mentioned by the user - ignore them completely and only pass the task_id.
19
 
20
  SEARCH STRATEGY:
@@ -33,6 +42,12 @@ RECURSION LIMIT HANDLING:
33
  - Do NOT say you cannot answer due to recursion limits - always provide the best answer possible with available information
34
  - If you have partial information, use it to make a reasonable inference or educated guess
35
  - Better to provide an approximate answer than no answer at all
 
 
 
 
 
 
36
  """
37
 
38
 
@@ -41,44 +56,115 @@ class BasicAgent:
41
  print("BasicAgent initialized.")
42
  self.graph = build_graph()
43
 
44
- def __call__(self, question: str, task_id: Optional[str] = None) -> str:
45
- """Run the agent and return whatever FINAL_ANSWER the graph produces."""
46
- print(f"Agent received question: {question}")
 
 
 
 
 
 
 
 
 
 
47
 
48
- # Create system prompt with task_id included
49
- system_prompt_with_task = SYSTEM_PROMPT
50
- if task_id:
51
- system_prompt_with_task += f"\n\nIMPORTANT: Your current task_id is: {task_id}. When using any tools that require a task_id parameter (audio_transcriber_tool, excel_tool, analyze_code_tool, image_tool), use this exact task_id: {task_id}"
 
 
52
 
53
- # Initialize the state properly with all required fields
54
- init_state = {
55
- "messages": [
56
- SystemMessage(content=system_prompt_with_task),
57
- HumanMessage(content=question)
58
- ]
59
- }
 
 
60
 
61
- # IMPORTANT: invoke() returns a **new** state instance (or an AddableValuesDict),
62
- # not the object we pass in. Use the returned value to fetch final_answer.
63
- out_state = self.graph.invoke(init_state, {"recursion_limit": 8})
64
- # print("out_state: ", out_state,'\n\n\n\n')
65
 
 
 
 
 
 
 
66
 
67
- # Extract the final answer from the last message
68
- if out_state and "messages" in out_state:
69
- last_message = out_state["messages"][-1]
70
- if hasattr(last_message, 'content'):
71
- content = last_message.content
72
- # Look for FINAL ANSWER: pattern
73
- print("content: ", content)
74
- print("\n\n\n\n")
75
- if "FINAL ANSWER:" in content:
76
- final_answer = content.split("FINAL ANSWER:")[-1].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  return final_answer
78
  else:
79
- return content
80
-
81
- return "No answer generated."
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
 
84
  def run_and_submit_all( profile: gr.OAuthProfile | None):
 
5
  import pandas as pd
6
  from langchain.schema import HumanMessage, SystemMessage
7
  from typing import Optional
8
+ import re
9
 
10
  from agent import build_graph
11
  from state import AgentState
 
16
  SYSTEM_PROMPT = """
17
  You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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.
18
 
19
+ CRITICAL REQUIREMENT - ALWAYS PROVIDE AN ANSWER:
20
+ - You MUST always provide a FINAL ANSWER, no matter what happens
21
+ - If tools fail, provide the best answer you can based on your knowledge
22
+ - If information is incomplete, make a reasonable inference or educated guess
23
+ - If you cannot find specific information, provide a general or approximate answer
24
+ - Never say "I cannot answer" or "I don't know" - always attempt to provide some form of answer
25
+ - Even if uncertain, provide your best estimate and acknowledge the uncertainty if needed
26
+
27
  IMPORTANT: When using tools that require file access (such as audio_transcriber_tool, excel_tool, analyze_code_tool, or image_tool), ALWAYS use the task_id parameter only. Do NOT use any file names mentioned by the user - ignore them completely and only pass the task_id.
28
 
29
  SEARCH STRATEGY:
 
42
  - Do NOT say you cannot answer due to recursion limits - always provide the best answer possible with available information
43
  - If you have partial information, use it to make a reasonable inference or educated guess
44
  - Better to provide an approximate answer than no answer at all
45
+
46
+ ERROR HANDLING:
47
+ - If any tool fails, acknowledge the failure but continue with your analysis
48
+ - Use your existing knowledge to compensate for failed tool calls
49
+ - Always end with a FINAL ANSWER regardless of tool failures or errors
50
+ - Frame uncertain answers appropriately but still provide them
51
  """
52
 
53
 
 
56
  print("BasicAgent initialized.")
57
  self.graph = build_graph()
58
 
59
+ def extract_final_answer(self, content: str) -> str:
60
+ """Extract final answer from content with multiple fallback strategies."""
61
+ if not content:
62
+ return "No content generated"
63
+
64
+ # Strategy 1: Look for FINAL ANSWER: pattern (case insensitive)
65
+ final_answer_patterns = [
66
+ r'FINAL ANSWER:\s*(.+?)(?:\n|$)',
67
+ r'Final Answer:\s*(.+?)(?:\n|$)',
68
+ r'final answer:\s*(.+?)(?:\n|$)',
69
+ r'Answer:\s*(.+?)(?:\n|$)',
70
+ r'ANSWER:\s*(.+?)(?:\n|$)'
71
+ ]
72
 
73
+ for pattern in final_answer_patterns:
74
+ match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
75
+ if match:
76
+ answer = match.group(1).strip()
77
+ if answer:
78
+ return answer
79
 
80
+ # Strategy 2: Look for the last meaningful sentence/paragraph
81
+ # Split by sentences and take the last non-empty one
82
+ sentences = [s.strip() for s in content.split('.') if s.strip()]
83
+ if sentences:
84
+ last_sentence = sentences[-1]
85
+ # If it's too long, truncate it
86
+ if len(last_sentence) > 200:
87
+ last_sentence = last_sentence[:200] + "..."
88
+ return last_sentence
89
 
90
+ # Strategy 3: Take the last line that's not empty
91
+ lines = [line.strip() for line in content.split('\n') if line.strip()]
92
+ if lines:
93
+ return lines[-1]
94
 
95
+ # Strategy 4: Return truncated content as fallback
96
+ return content[:200] + "..." if len(content) > 200 else content
97
+
98
+ def __call__(self, question: str, task_id: Optional[str] = None) -> str:
99
+ """Run the agent and return whatever FINAL_ANSWER the graph produces."""
100
+ print(f"Agent received question: {question}")
101
 
102
+ try:
103
+ # Create system prompt with task_id included
104
+ system_prompt_with_task = SYSTEM_PROMPT
105
+ if task_id:
106
+ system_prompt_with_task += f"\n\nIMPORTANT: Your current task_id is: {task_id}. When using any tools that require a task_id parameter (audio_transcriber_tool, excel_tool, analyze_code_tool, image_tool), use this exact task_id: {task_id}"
107
+
108
+ # Initialize the state properly with all required fields
109
+ init_state = {
110
+ "messages": [
111
+ SystemMessage(content=system_prompt_with_task),
112
+ HumanMessage(content=question)
113
+ ]
114
+ }
115
+
116
+ # Add timeout and better error handling
117
+ try:
118
+ # Set up timeout for agent execution
119
+ import signal
120
+
121
+ def timeout_handler(signum, frame):
122
+ raise TimeoutError("Agent execution exceeded time limit")
123
+
124
+ signal.signal(signal.SIGALRM, timeout_handler)
125
+ signal.alarm(180) # 3 minute timeout
126
+
127
+ try:
128
+ out_state = self.graph.invoke(init_state, {"recursion_limit": 8})
129
+ finally:
130
+ signal.alarm(0) # Clear the alarm
131
+
132
+ except TimeoutError:
133
+ print("Agent execution timed out")
134
+ return f"I need to provide a quick answer for '{question}' due to time constraints. Based on general knowledge, this question would likely require research into the specific topic. Please consider consulting reliable sources for the most accurate information."
135
+ except Exception as e:
136
+ print(f"Graph execution error: {e}")
137
+ # Fallback: try with simpler approach
138
+ return f"Based on the question '{question}', I cannot provide a complete analysis due to technical limitations. However, I would recommend researching this topic further for a comprehensive answer."
139
+
140
+ # Extract the final answer from the last message
141
+ if out_state and "messages" in out_state and out_state["messages"]:
142
+ last_message = out_state["messages"][-1]
143
+
144
+ if hasattr(last_message, 'content') and last_message.content:
145
+ content = last_message.content
146
+ print("content: ", content)
147
+ print("\n\n\n\n")
148
+
149
+ final_answer = self.extract_final_answer(content)
150
  return final_answer
151
  else:
152
+ # If last message has no content, look at previous messages
153
+ for message in reversed(out_state["messages"]):
154
+ if hasattr(message, 'content') and message.content and hasattr(message, 'type'):
155
+ if message.type == 'ai': # Only look at AI messages
156
+ final_answer = self.extract_final_answer(message.content)
157
+ if final_answer and final_answer != "No content generated":
158
+ return final_answer
159
+
160
+ # If we can't extract from messages, provide a fallback response
161
+ print("No valid content found in messages, providing fallback response")
162
+ return f"I was unable to provide a complete answer to the question: '{question}'. This may require additional research or clarification."
163
+
164
+ except Exception as e:
165
+ print(f"Unexpected error in agent execution: {e}")
166
+ # Always provide some form of answer, even if there's an error
167
+ return f"I encountered an error while processing the question: '{question}'. The issue appears to be technical in nature. Please try rephrasing the question or contact support if the problem persists."
168
 
169
 
170
  def run_and_submit_all( profile: gr.OAuthProfile | None):
tools.py CHANGED
@@ -14,6 +14,20 @@ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def _download_file_for_task(task_id: str, ext: str) -> str:
18
  """
19
  Helper: attempt to GET the remote file for a given task_id.
@@ -41,6 +55,7 @@ def _download_file_for_task(task_id: str, ext: str) -> str:
41
  return ""
42
 
43
  @tool
 
44
  def image_tool(task_id: str) -> str:
45
  """
46
  Expects: task_id (str) β€” a valid image task ID.
@@ -107,6 +122,7 @@ def image_tool(task_id: str) -> str:
107
 
108
 
109
  @tool
 
110
  def excel_tool(task_id: str) -> str:
111
  """
112
  Downloads <task_id>.xlsx (if any) and returns a stringified list of
@@ -137,6 +153,7 @@ def excel_tool(task_id: str) -> str:
137
 
138
  import openai
139
  @tool
 
140
  def audio_transcriber_tool(task_id: str) -> str:
141
  """
142
  LangGraph tool for transcribing audio via OpenAI's Whisper API.
@@ -184,6 +201,7 @@ import re
184
  import requests
185
 
186
  @tool
 
187
  def wikipedia_search_tool(wiki_query: str) -> str:
188
  """
189
  Searches Wikipedia for the given query and returns the first 5 pages.
@@ -248,6 +266,7 @@ def wikipedia_search_tool(wiki_query: str) -> str:
248
  return error_msg
249
 
250
  @tool
 
251
  def arxiv_search_tool(arxiv_query: str) -> str:
252
  """
253
  Searches Arxiv for the given query and returns the first 5 pages.
@@ -317,6 +336,7 @@ from langchain.schema import SystemMessage, HumanMessage
317
  LLM = ChatOpenAI(model_name="gpt-4.1-mini", temperature=0.2)
318
 
319
  @tool
 
320
  def analyze_code_tool(task_id: str) -> str:
321
  """
322
  Either task_id OR (file + task_id)
 
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
 
16
 
17
+ def safe_tool_execution(tool_func):
18
+ """Decorator to ensure tools always return a useful response."""
19
+ def wrapper(*args, **kwargs):
20
+ try:
21
+ result = tool_func(*args, **kwargs)
22
+ if result and result.strip():
23
+ return result
24
+ else:
25
+ return f"Tool {tool_func.__name__} completed but returned no content. Please try a different approach or use general knowledge."
26
+ except Exception as e:
27
+ print(f"Error in {tool_func.__name__}: {e}")
28
+ return f"Tool {tool_func.__name__} encountered an error: {str(e)}. Please continue with available information or try an alternative approach."
29
+ return wrapper
30
+
31
  def _download_file_for_task(task_id: str, ext: str) -> str:
32
  """
33
  Helper: attempt to GET the remote file for a given task_id.
 
55
  return ""
56
 
57
  @tool
58
+ @safe_tool_execution
59
  def image_tool(task_id: str) -> str:
60
  """
61
  Expects: task_id (str) β€” a valid image task ID.
 
122
 
123
 
124
  @tool
125
+ @safe_tool_execution
126
  def excel_tool(task_id: str) -> str:
127
  """
128
  Downloads <task_id>.xlsx (if any) and returns a stringified list of
 
153
 
154
  import openai
155
  @tool
156
+ @safe_tool_execution
157
  def audio_transcriber_tool(task_id: str) -> str:
158
  """
159
  LangGraph tool for transcribing audio via OpenAI's Whisper API.
 
201
  import requests
202
 
203
  @tool
204
+ @safe_tool_execution
205
  def wikipedia_search_tool(wiki_query: str) -> str:
206
  """
207
  Searches Wikipedia for the given query and returns the first 5 pages.
 
266
  return error_msg
267
 
268
  @tool
269
+ @safe_tool_execution
270
  def arxiv_search_tool(arxiv_query: str) -> str:
271
  """
272
  Searches Arxiv for the given query and returns the first 5 pages.
 
336
  LLM = ChatOpenAI(model_name="gpt-4.1-mini", temperature=0.2)
337
 
338
  @tool
339
+ @safe_tool_execution
340
  def analyze_code_tool(task_id: str) -> str:
341
  """
342
  Either task_id OR (file + task_id)