ana-35 commited on
Commit
5c082e6
·
1 Parent(s): be105f4
Files changed (4) hide show
  1. app.py +41 -40
  2. tools/calculator.py +40 -5
  3. tools/file_reader.py +27 -21
  4. tools/web_searcher.py +18 -22
app.py CHANGED
@@ -14,6 +14,7 @@ load_dotenv()
14
 
15
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
 
 
17
  class ToolUsingAgent:
18
  def __init__(self):
19
  self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@@ -25,47 +26,46 @@ class ToolUsingAgent:
25
  self.file_reader = read_file_tool
26
 
27
  def answer(self, question: str, task_id: str = None) -> str:
28
- question_lower = question.lower()
29
-
30
  try:
31
- tool_result = None
32
- if "file" in question_lower or "attached" in question_lower:
33
- if task_id:
34
- tool_result = self.file_reader.run(task_id)
35
- else:
36
- return "[Error: task_id missing for file handling]"
37
-
38
- elif any(k in question_lower for k in ["calculate", "math", "sum", "total", "add", "subtract", "divide", "multiply"]):
39
- tool_result = self.calculator.run(question)
40
-
41
- elif any(k in question_lower for k in ["search", "find", "lookup", "look up", "google", "wikipedia", "ioc"]):
42
- tool_result = self.web_search.run(question)
43
-
44
- if tool_result:
45
- prompt = f"""
46
- You are an intelligent agent answering questions based on external tools or reasoning. Follow these strict guidelines:
47
- 1. If the question mentions web sources (e.g., Wikipedia, IOC, search), always use the web search tool to find the answer.
48
- 2. If the question refers to a file or attachment, always use the file reading tool to extract data.
49
- 3. If the question is a logic puzzle (e.g., reverse words, decode), reason internally—no tools required.
50
- 4. If the question asks for a specific format (e.g., a single name, city, number, or IOC code), return only that value—no extra text, no explanations.
51
- 5. If you cannot find the answer, say: 'No answer found.'
52
- 6. Do not guess or assume. Always verify using tools if available.
53
-
54
- Tool Output: {tool_result}
 
 
 
 
 
 
55
 
56
  Question: {question}
57
- Answer:"""
58
- else:
59
- prompt = f"""
60
- You are an intelligent agent answering questions based on external tools or reasoning. Follow these strict guidelines:
61
- (Same instructions as above)
62
 
63
- Question: {question}
64
  Answer:"""
65
 
66
- final_answer = self.query_llm(prompt)
67
- expected_format = self.expected_format_detected_from_question(question)
68
- return self.clean_output(final_answer, expected_format)
69
 
70
  except Exception as e:
71
  return f"[AGENT ERROR: {e}]"
@@ -87,7 +87,7 @@ Answer:"""
87
  return "city"
88
  elif "name" in q or "first name" in q or "last name" in q:
89
  return "name"
90
- elif any(w in q for w in ["number", "amount", "how many", "total", "sum", "price", "usd"]):
91
  return "number"
92
  else:
93
  return "text"
@@ -96,19 +96,20 @@ Answer:"""
96
  output = output.strip().strip(' "\'')
97
  if expected_format == "ioc":
98
  match = re.search(r'\b[A-Z]{3}\b', output)
99
- return match.group(0) if match else "No valid IOC code found"
100
  elif expected_format == "city":
101
  match = re.search(r'\b[A-Z][a-z]+(?: [A-Z][a-z]+)*\b', output)
102
- return match.group(0) if match else "No valid city found"
103
  elif expected_format == "name":
104
  match = re.search(r'\b[A-Z][a-z]+\b', output)
105
- return match.group(0) if match else "No valid name found"
106
  elif expected_format == "number":
107
  match = re.search(r'\d+(\.\d+)?', output)
108
- return match.group(0) if match else "No number found"
109
  else:
110
  return output
111
 
 
112
  def run_and_submit_all(profile: gr.OAuthProfile | None):
113
  space_id = os.getenv("SPACE_ID")
114
  if profile:
 
14
 
15
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
 
17
+
18
  class ToolUsingAgent:
19
  def __init__(self):
20
  self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 
26
  self.file_reader = read_file_tool
27
 
28
  def answer(self, question: str, task_id: str = None) -> str:
 
 
29
  try:
30
+ # Step 1: Collect tool results if applicable
31
+ file_result = None
32
+ if task_id:
33
+ file_result = self.file_reader.run(task_id)
34
+
35
+ web_result = None
36
+ if any(kw in question.lower() for kw in ["wikipedia", "search", "google", "find", "ioc", "lookup"]):
37
+ web_result = self.web_search.run(question)
38
+
39
+ calc_result = None
40
+ if any(kw in question.lower() for kw in ["calculate", "math", "sum", "total", "add", "subtract", "multiply", "divide"]):
41
+ calc_result = self.calculator.run(question)
42
+
43
+ # Step 2: Build tool context dynamically
44
+ tool_context = ""
45
+ if file_result:
46
+ tool_context += f"\nFile content: {file_result}"
47
+ if web_result:
48
+ tool_context += f"\nWeb search result: {web_result}"
49
+ if calc_result:
50
+ tool_context += f"\nCalculation result: {calc_result}"
51
+
52
+ # Step 3: Generate the prompt for LLM
53
+ prompt = f"""
54
+ You are an expert AI agent solving complex questions. Follow these strict rules:
55
+ 1. Use the tools when necessary: web_search, file_reader, calculator.
56
+ 2. Combine tools if required for multi-step questions.
57
+ 3. Return only the requested format: single name, number, city, or code. No explanations, no extra text.
58
+ 4. If you cannot answer, reply exactly: 'No answer found.'
59
+ 5. Do not hallucinate or guess.
60
 
61
  Question: {question}
62
+ {tool_context}
 
 
 
 
63
 
 
64
  Answer:"""
65
 
66
+ response = self.query_llm(prompt)
67
+ format_type = self.expected_format_detected_from_question(question)
68
+ return self.clean_output(response, format_type)
69
 
70
  except Exception as e:
71
  return f"[AGENT ERROR: {e}]"
 
87
  return "city"
88
  elif "name" in q or "first name" in q or "last name" in q:
89
  return "name"
90
+ elif any(word in q for word in ["number", "amount", "how many", "total", "sum", "price", "usd"]):
91
  return "number"
92
  else:
93
  return "text"
 
96
  output = output.strip().strip(' "\'')
97
  if expected_format == "ioc":
98
  match = re.search(r'\b[A-Z]{3}\b', output)
99
+ return match.group(0) if match else "No answer found."
100
  elif expected_format == "city":
101
  match = re.search(r'\b[A-Z][a-z]+(?: [A-Z][a-z]+)*\b', output)
102
+ return match.group(0) if match else "No answer found."
103
  elif expected_format == "name":
104
  match = re.search(r'\b[A-Z][a-z]+\b', output)
105
+ return match.group(0) if match else "No answer found."
106
  elif expected_format == "number":
107
  match = re.search(r'\d+(\.\d+)?', output)
108
+ return match.group(0) if match else "No answer found."
109
  else:
110
  return output
111
 
112
+
113
  def run_and_submit_all(profile: gr.OAuthProfile | None):
114
  space_id = os.getenv("SPACE_ID")
115
  if profile:
tools/calculator.py CHANGED
@@ -1,12 +1,47 @@
 
 
1
  from langchain.tools import Tool
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  def calculator(query: str) -> str:
4
- """Calculate the result of a mathematical expression"""
5
- return eval(query, {"__builtins__": {}})
 
 
 
6
 
7
  calculator_tool = Tool.from_function(
8
  name="calculator",
9
- description="Calculate the result of a mathematical expression",
10
  func=calculator
11
- )
12
-
 
1
+ import ast
2
+ import operator as op
3
  from langchain.tools import Tool
4
 
5
+ operators = {
6
+ ast.Add: op.add,
7
+ ast.Sub: op.sub,
8
+ ast.Mult: op.mul,
9
+ ast.Div: op.truediv,
10
+ ast.Pow: op.pow,
11
+ ast.Mod: op.mod,
12
+ ast.USub: op.neg,
13
+ }
14
+
15
+ def safe_eval(expr: str) -> float:
16
+ """
17
+ Safely evaluate a mathematical expression string.
18
+ Supports +, -, *, /, **, %, and negative numbers.
19
+ """
20
+ def eval_node(node):
21
+ if isinstance(node, ast.Num): # <number>
22
+ return node.n
23
+ elif isinstance(node, ast.BinOp): # <left> <operator> <right>
24
+ return operators[type(node.op)](eval_node(node.left), eval_node(node.right))
25
+ elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
26
+ return operators[type(node.op)](eval_node(node.operand))
27
+ else:
28
+ raise TypeError(f"Unsupported type: {node}")
29
+
30
+ try:
31
+ node = ast.parse(expr, mode='eval').body
32
+ return eval_node(node)
33
+ except Exception as e:
34
+ return f"[Calculator error: {e}]"
35
+
36
  def calculator(query: str) -> str:
37
+ try:
38
+ result = safe_eval(query)
39
+ return str(result)
40
+ except Exception as e:
41
+ return f"[Calculator error: {e}]"
42
 
43
  calculator_tool = Tool.from_function(
44
  name="calculator",
45
+ description="Performs safe mathematical calculations from a text query. Supports +, -, *, /, %, and powers.",
46
  func=calculator
47
+ )
 
tools/file_reader.py CHANGED
@@ -1,8 +1,8 @@
1
  import requests
2
  import pandas as pd
3
- import io
4
- from io import BytesIO
5
  import PyPDF2
 
 
6
  from langchain.tools import Tool
7
 
8
  def read_file(task_id: str) -> str:
@@ -12,38 +12,44 @@ def read_file(task_id: str) -> str:
12
  response.raise_for_status()
13
 
14
  content_type = response.headers.get("Content-Type", "").lower()
 
 
 
 
 
15
 
16
- if "excel" in content_type or task_id.endswith(".xlsx"):
17
- df = pd.read_excel(BytesIO(response.content))
18
- numeric_cols = df.select_dtypes(include='number').columns
19
- if numeric_cols.empty:
20
- return df.to_string(index=False, header=True)
21
- total = df[numeric_cols].sum().sum()
22
- return f"{total:.2f}"
23
  elif "csv" in content_type or task_id.endswith(".csv"):
24
- df = pd.read_csv(BytesIO(response.content))
25
- numeric_cols = df.select_dtypes(include='number').columns
26
- if numeric_cols.empty:
27
- return df.to_string(index=False, header=True)
28
- total = df[numeric_cols].sum().sum()
29
- return f"{total:.2f}"
30
 
31
  elif "pdf" in content_type or task_id.endswith(".pdf"):
32
- pdf = PyPDF2.PdfReader(BytesIO(response.content))
33
  text = "\n".join(page.extract_text() for page in pdf.pages if page.extract_text())
34
- return text.strip() if text else "[Error: No text extracted from PDF]"
35
 
36
- elif any(task_id.endswith(ext) for ext in [".txt", ".py", ".md", ".json"]):
37
- return response.content.decode('utf-8', errors='ignore')
 
 
 
 
38
 
39
  else:
40
- return response.content.decode('utf-8', errors='ignore')
41
 
42
  except Exception as e:
43
  return f"[File reader error: {e}]"
44
 
 
 
 
 
 
 
 
 
45
  read_file_tool = Tool.from_function(
46
  name="read_file",
47
  description="Reads the content of a file based on the task_id",
48
  func=read_file
49
- )
 
1
  import requests
2
  import pandas as pd
 
 
3
  import PyPDF2
4
+ import json
5
+ from io import BytesIO
6
  from langchain.tools import Tool
7
 
8
  def read_file(task_id: str) -> str:
 
12
  response.raise_for_status()
13
 
14
  content_type = response.headers.get("Content-Type", "").lower()
15
+ file_bytes = BytesIO(response.content)
16
+
17
+ if "excel" in content_type or task_id.endswith((".xls", ".xlsx")):
18
+ df = pd.read_excel(file_bytes)
19
+ return summarize_dataframe(df)
20
 
 
 
 
 
 
 
 
21
  elif "csv" in content_type or task_id.endswith(".csv"):
22
+ df = pd.read_csv(file_bytes)
23
+ return summarize_dataframe(df)
 
 
 
 
24
 
25
  elif "pdf" in content_type or task_id.endswith(".pdf"):
26
+ pdf = PyPDF2.PdfReader(file_bytes)
27
  text = "\n".join(page.extract_text() for page in pdf.pages if page.extract_text())
28
+ return text.strip() if text else "No text extracted from PDF."
29
 
30
+ elif "json" in content_type or task_id.endswith(".json"):
31
+ data = json.load(file_bytes)
32
+ return json.dumps(data, indent=2)
33
+
34
+ elif any(task_id.endswith(ext) for ext in [".txt", ".py", ".md"]):
35
+ return response.content.decode("utf-8", errors="ignore")
36
 
37
  else:
38
+ return response.content.decode("utf-8", errors="ignore")
39
 
40
  except Exception as e:
41
  return f"[File reader error: {e}]"
42
 
43
+ def summarize_dataframe(df: pd.DataFrame) -> str:
44
+ numeric_cols = df.select_dtypes(include='number').columns
45
+ if not numeric_cols.empty:
46
+ total = df[numeric_cols].sum().sum()
47
+ return f"Total of numeric columns: {total:.2f}"
48
+ else:
49
+ return df.head().to_string(index=False)
50
+
51
  read_file_tool = Tool.from_function(
52
  name="read_file",
53
  description="Reads the content of a file based on the task_id",
54
  func=read_file
55
+ )
tools/web_searcher.py CHANGED
@@ -1,52 +1,48 @@
1
  import requests
2
  import os
3
- import re
4
- from langchain.tools import Tool
5
  from dotenv import load_dotenv
 
6
 
7
  load_dotenv()
8
-
9
  serper_api_key = os.getenv("SERPER_API_KEY")
10
  serper_api_url = "https://google.serper.dev/search"
11
 
12
  def web_search(query: str) -> str:
13
  if not serper_api_key:
14
- return "Error: SERPER_API_KEY is not set"
15
 
16
  headers = {
17
  "X-API-KEY": serper_api_key,
18
  "Content-Type": "application/json"
19
  }
20
- payload = { "q": query }
21
 
 
22
  try:
23
- response = requests.post(serper_api_url, headers=headers, json=payload, timeout=10)
24
  response.raise_for_status()
25
  data = response.json()
26
 
27
- # Extract concise answers
28
- if data.get("answerBox"):
29
- answer = data["answerBox"].get("answer") or data["answerBox"].get("snippet")
30
- elif data.get("organic"):
31
- answer = data["organic"][0].get("snippet") or data["organic"][0].get("title")
 
 
 
 
 
 
 
32
  else:
33
- return "No results found"
34
-
35
- # Clean output: remove extra text and punctuation
36
- answer = answer.strip()
37
- answer = re.sub(r'^(The answer is|Answer:)\s*', '', answer, flags=re.IGNORECASE)
38
- answer = answer.strip(' "\'')
39
-
40
- # Optional: Extract only the first sentence or number
41
- match = re.match(r'^[^.,;:!?]+', answer)
42
- return match.group(0).strip() if match else answer
43
 
44
  except Exception as e:
45
  return f"[Web search error: {e}]"
46
 
47
  web_search_tool = Tool.from_function(
48
  name="web_search",
49
- description="Search the web for concise factual information, like a name, city, or number. Return only the final answer, no explanations.",
50
  func=web_search
51
  )
52
 
 
1
  import requests
2
  import os
 
 
3
  from dotenv import load_dotenv
4
+ from langchain.tools import Tool
5
 
6
  load_dotenv()
 
7
  serper_api_key = os.getenv("SERPER_API_KEY")
8
  serper_api_url = "https://google.serper.dev/search"
9
 
10
  def web_search(query: str) -> str:
11
  if not serper_api_key:
12
+ return "Error: SERPER_API_KEY is not set."
13
 
14
  headers = {
15
  "X-API-KEY": serper_api_key,
16
  "Content-Type": "application/json"
17
  }
 
18
 
19
+ payload = { "q": query }
20
  try:
21
+ response = requests.post(serper_api_url, headers=headers, json=payload, timeout=15)
22
  response.raise_for_status()
23
  data = response.json()
24
 
25
+ results = []
26
+ if data.get("organic"):
27
+ for entry in data["organic"]:
28
+ snippet = entry.get("snippet")
29
+ link = entry.get("link")
30
+ if snippet and link:
31
+ results.append(f"{snippet} (Source: {link})")
32
+ elif snippet:
33
+ results.append(snippet)
34
+
35
+ if results:
36
+ return "\n".join(results[:5])
37
  else:
38
+ return "No search results found."
 
 
 
 
 
 
 
 
 
39
 
40
  except Exception as e:
41
  return f"[Web search error: {e}]"
42
 
43
  web_search_tool = Tool.from_function(
44
  name="web_search",
45
+ description="Searches the web using Serper API and provides relevant information.",
46
  func=web_search
47
  )
48