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- .gitignore +197 -0
- agent.py +104 -0
- app.py +17 -15
- 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 |
-
|
12 |
-
|
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 =
|
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."
|