Francesco Laiti commited on
Commit
a745ced
·
1 Parent(s): 2e3fe1a

Add .gitignore, implement AskMeAnythingAgent in agent.py, and update app.py to utilize the new agent. Include instruction prompts in YAML format.

Browse files
Files changed (4) hide show
  1. .gitignore +197 -0
  2. agent.py +104 -0
  3. app.py +17 -15
  4. instruction_prompts.yaml +12 -0
.gitignore ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
2
+ # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python
3
+ media/
4
+ old_agent.py
5
+
6
+ ### Python ###
7
+ # Byte-compiled / optimized / DLL files
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+
12
+ # C extensions
13
+ *.so
14
+
15
+ # Distribution / packaging
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ share/python-wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+ MANIFEST
34
+
35
+ # PyInstaller
36
+ # Usually these files are written by a python script from a template
37
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
38
+ *.manifest
39
+ *.spec
40
+
41
+ # Installer logs
42
+ pip-log.txt
43
+ pip-delete-this-directory.txt
44
+
45
+ # Unit test / coverage reports
46
+ htmlcov/
47
+ .tox/
48
+ .nox/
49
+ .coverage
50
+ .coverage.*
51
+ .cache
52
+ nosetests.xml
53
+ coverage.xml
54
+ *.cover
55
+ *.py,cover
56
+ .hypothesis/
57
+ .pytest_cache/
58
+ cover/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ .pybuilder/
82
+ target/
83
+
84
+ # Jupyter Notebook
85
+ .ipynb_checkpoints
86
+
87
+ # IPython
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # pyenv
92
+ # For a library or package, you might want to ignore these files since the code is
93
+ # intended to run in multiple environments; otherwise, check them in:
94
+ # .python-version
95
+
96
+ # pipenv
97
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
99
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
100
+ # install all needed dependencies.
101
+ #Pipfile.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/#use-with-ide
116
+ .pdm.toml
117
+
118
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
119
+ __pypackages__/
120
+
121
+ # Celery stuff
122
+ celerybeat-schedule
123
+ celerybeat.pid
124
+
125
+ # SageMath parsed files
126
+ *.sage.py
127
+
128
+ # Environments
129
+ .env
130
+ .venv
131
+ env/
132
+ venv/
133
+ ENV/
134
+ env.bak/
135
+ venv.bak/
136
+
137
+ # Spyder project settings
138
+ .spyderproject
139
+ .spyproject
140
+
141
+ # Rope project settings
142
+ .ropeproject
143
+
144
+ # mkdocs documentation
145
+ /site
146
+
147
+ # mypy
148
+ .mypy_cache/
149
+ .dmypy.json
150
+ dmypy.json
151
+
152
+ # Pyre type checker
153
+ .pyre/
154
+
155
+ # pytype static type analyzer
156
+ .pytype/
157
+
158
+ # Cython debug symbols
159
+ cython_debug/
160
+
161
+ # PyCharm
162
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
163
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
164
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
165
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
166
+ #.idea/
167
+
168
+ ### Python Patch ###
169
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
170
+ poetry.toml
171
+
172
+ # ruff
173
+ .ruff_cache/
174
+
175
+ # LSP config files
176
+ pyrightconfig.json
177
+
178
+ ### VisualStudioCode ###
179
+ .vscode/*
180
+ !.vscode/settings.json
181
+ !.vscode/tasks.json
182
+ !.vscode/launch.json
183
+ !.vscode/extensions.json
184
+ !.vscode/*.code-snippets
185
+
186
+ # Local History for Visual Studio Code
187
+ .history/
188
+
189
+ # Built Visual Studio Code Extensions
190
+ *.vsix
191
+
192
+ ### VisualStudioCode Patch ###
193
+ # Ignore all local history of files
194
+ .history
195
+ .ionide
196
+
197
+ # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
agent.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ import yaml
4
+ import re
5
+ import json
6
+ import pandas as pd
7
+ from pathlib import Path
8
+ import time
9
+
10
+ from agno.agent import Agent, RunResponse
11
+ from agno.models.google import Gemini
12
+ from agno.media import Image, Audio, Video, File
13
+
14
+ from agno.tools.googlesearch import GoogleSearchTools
15
+ from agno.tools.calculator import CalculatorTools
16
+
17
+
18
+ URL_BASE_DOWNLOAD = "https://agents-course-unit4-scoring.hf.space/files/"
19
+ load_dotenv()
20
+
21
+ class AskMeAnythingAgent:
22
+ def __init__(self, model_id: str = "gemini-2.0-flash", debug: bool = False):
23
+ with open("instruction_prompts.yaml", "r") as file:
24
+ self.instruction_prompts = yaml.safe_load(file)
25
+ self.main_agent = Agent(
26
+ name="Ask Me Anything",
27
+ model=Gemini(id=model_id),
28
+ role=self.instruction_prompts["role"],
29
+ instructions=self.instruction_prompts["instructions"],
30
+ goal=self.instruction_prompts["goal"],
31
+ tools=[GoogleSearchTools(), CalculatorTools()],
32
+ debug_mode=debug,
33
+ )
34
+
35
+ def __call__(self, question: str, file_name: str = "", debug: bool = False) -> str:
36
+ payload = self.manage_payload(file_name, question)
37
+ response: RunResponse = self.main_agent.run(message=question, **payload)
38
+ return self.parse_response(response.content)
39
+
40
+ def manage_payload(self, file_name: str, question: str):
41
+ if file_name != "":
42
+ if file_name.endswith(".png"):
43
+ return {"images": [Image(url=URL_BASE_DOWNLOAD + file_name.split(".")[0], format="png")]}
44
+ elif file_name.endswith(".mp3"):
45
+ return {"audio": [Audio(url=URL_BASE_DOWNLOAD + file_name.split(".")[0], format="mp3")]}
46
+ elif file_name.endswith(".xlsx"):
47
+ df = pd.read_excel(io=URL_BASE_DOWNLOAD + file_name.split(".")[0])
48
+ df.to_csv(f"media/{file_name.split('.')[0]}.csv", index=False)
49
+ return {"files": [File(filepath=f"media/{file_name.split('.')[0]}.csv", mime_type="text/csv")]}
50
+ else:
51
+ return {"files": [File(url=URL_BASE_DOWNLOAD + file_name.split(".")[0])]}
52
+ elif "https://www.youtube.com/" in question:
53
+ url = re.search(r'https://www\.youtube\.com/watch\?v=[\w-]+', question).group(0)
54
+ path_to_file = self.download_yt_video(url)
55
+ return {"videos": [Video(filepath=path_to_file)]}
56
+ else:
57
+ return {}
58
+
59
+ def download_yt_video(self, url: str):
60
+ import yt_dlp
61
+ import os
62
+
63
+ ydl_opts = {
64
+ 'format': 'best',
65
+ 'outtmpl': 'media/%(id)s.%(ext)s',
66
+ 'quiet': True,
67
+ }
68
+ os.makedirs("media", exist_ok=True)
69
+ print(f"Downloading video from {url}")
70
+ if not os.path.exists(f"media/{url.split('v=')[1]}.mp4"):
71
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
72
+ #info = ydl.extract_info(url, download=False)
73
+ #final_filename = ydl.prepare_filename(info)
74
+ ydl.download([url])
75
+ return Path(__file__).parent.joinpath(f"media/{url.split('v=')[1]}.mp4")
76
+
77
+ def download_file(self, file_name: str):
78
+ import requests
79
+ response = requests.get(URL_BASE_DOWNLOAD + file_name)
80
+ os.makedirs("media", exist_ok=True)
81
+ if not os.path.exists(f"media/{file_name}"):
82
+ with open(f"media/{file_name}", 'wb') as file:
83
+ file.write(response.content)
84
+ return f"media/{file_name}"
85
+
86
+ def parse_response(self, response: str):
87
+ field_header_pattern = re.compile(r"\[\[ ## ANSWER ## \]\]|\[\[## ANSWER ##\]\]")
88
+ for line in response.splitlines():
89
+ match = field_header_pattern.search(line.strip())
90
+ if match:
91
+ return line[match.end():].strip()
92
+ return "invalid"
93
+
94
+
95
+ if __name__ == "__main__":
96
+ with open("questions.json", "r") as file:
97
+ questions = json.load(file)
98
+ for i, question in enumerate(questions):
99
+ agent = AskMeAnythingAgent(debug=False)
100
+ print(f"{i+1}. QUESTION: ", question["question"], "\nFILE: ", question["file_name"])
101
+ print(f"{i+1}. ANSWER: ", agent(question["question"], question["file_name"]), "\n\n")
102
+ if (i+1) % 5 == 0:
103
+ print("Sleeping for 30 seconds to avoid RPM limit from API")
104
+ time.sleep(30)
app.py CHANGED
@@ -3,21 +3,16 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
 
 
 
6
 
7
- # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
-
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
- def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
 
22
  def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
@@ -40,7 +35,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
40
 
41
  # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
@@ -76,17 +71,24 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
76
  for item in questions_data:
77
  task_id = item.get("task_id")
78
  question_text = item.get("question")
 
79
  if not task_id or question_text is None:
80
  print(f"Skipping item with missing task_id or question: {item}")
81
  continue
 
82
  try:
83
- submitted_answer = agent(question_text)
84
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
86
  except Exception as e:
87
  print(f"Error running agent on task {task_id}: {e}")
88
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
89
-
 
 
 
 
 
90
  if not answers_payload:
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from agent import AskMeAnythingAgent
7
+ import time
8
+ from tqdm import tqdm
9
+
10
 
 
11
  # --- Constants ---
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
+ RPM_MODEL_LIMIT = 10
14
+ MARGIN_TIME = 15
15
+ MODEL_ID = "gemini-2.5-flash-preview-04-17" # "gemini-2.5-pro-exp-03-25"
 
 
 
 
 
 
 
 
16
 
17
  def run_and_submit_all( profile: gr.OAuthProfile | None):
18
  """
 
35
 
36
  # 1. Instantiate Agent ( modify this part to create your agent)
37
  try:
38
+ agent = AskMeAnythingAgent(model_id=MODEL_ID, debug=True)
39
  except Exception as e:
40
  print(f"Error instantiating agent: {e}")
41
  return f"Error initializing agent: {e}", None
 
71
  for item in questions_data:
72
  task_id = item.get("task_id")
73
  question_text = item.get("question")
74
+ file_name = item.get("file_name")
75
  if not task_id or question_text is None:
76
  print(f"Skipping item with missing task_id or question: {item}")
77
  continue
78
+ start_time = time.time()
79
  try:
80
+ submitted_answer = agent(question_text, file_name)
81
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
82
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
83
  except Exception as e:
84
  print(f"Error running agent on task {task_id}: {e}")
85
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
86
+ end_time = time.time()
87
+ time_taken = end_time - start_time
88
+ if time_taken < (60 / RPM_MODEL_LIMIT):
89
+ print(f"Waiting for {60 / RPM_MODEL_LIMIT - time_taken + MARGIN_TIME} seconds to avoid exceeding the free tier rate limit")
90
+ for _ in tqdm(range(int(60 / RPM_MODEL_LIMIT - time_taken + MARGIN_TIME)), desc="Waiting..."):
91
+ time.sleep(1)
92
  if not answers_payload:
93
  print("Agent did not produce any answers to submit.")
94
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
instruction_prompts.yaml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ role:
2
+ - "You will be given a question, and optionally an attached file (image, audio, video, spreadsheet, or code)"
3
+ goal:
4
+ - "Produce a correct, concise answer based on the question and file."
5
+ instructions:
6
+ - "Start your response with a brief explanation of your reasoning unless the question explicitly instructs you to provide only the answer."
7
+ - "End your response using the following template: [[ ## ANSWER ## ]] {final answer}"
8
+ - "For number, do not include commas (e.g., write 1042 not 1,042), do not include units (e.g., $, %, etc.) unless explicitly required, do not use scientific notation unless specified.."
9
+ - "For string, do not use articles (e.g., a, an, the), do not use abbreviations (e.g., use 'San Francisco' instead of 'SF'), write digits as plain text (e.g., 'three' instead of '3') unless numeric form is clearly expected (e.g., years, IDs)."
10
+ - "For list, return items as a comma-separated list, apply the number and string formatting rules to each element, alphabetize the list if the question asks for it, follow any specific structural requirements in the question (e.g., 'last names only')."
11
+ - "If a file is provided, incorporate relevant content from the file in your answer."
12
+ - "If the question requires a specific format (e.g., algebraic notation, page numbers), use it exactly as expected."