thecodemaiden commited on
Commit
0d0a5fc
·
1 Parent(s): 355b538

feat: don't let others spend my Gemini money T_T

Browse files
Files changed (2) hide show
  1. app.py +48 -38
  2. my_agent.py +162 -128
app.py CHANGED
@@ -66,21 +66,7 @@ def answer_one(agent, question_data):
66
  "Question": question_text,
67
  "Submitted Answer": submitted_answer or agent_error,
68
  }
69
- return payload, log_entry
70
-
71
- def _submit_answers_to_file(answers_payload, file_path):
72
- """
73
- Submits the answers to a local file.
74
- """
75
-
76
- try:
77
- with open(file_path, "w") as file:
78
- json.dump(answers_payload, file, indent=4)
79
- submit_status = (f"Answers successfully written to {file_path}")
80
- except Exception as e:
81
- submit_status = (f"Error writing answers to file: {e}")
82
- print(submit_status)
83
- return submit_status
84
 
85
  def _submit_all(username, agent_code, answers_payload, submit_url):
86
  # Prepare Submission
@@ -130,7 +116,7 @@ def _submit_all(username, agent_code, answers_payload, submit_url):
130
  def prepare_agent(api_key=None):
131
  # 1. Instantiate Agent ( modify this part to create your agent)
132
  try:
133
- agent = GeminiAgentContainer()
134
  print(agent.system_prompt)
135
  except Exception as e:
136
  print(f"Error instantiating agent: {e}")
@@ -138,7 +124,27 @@ def prepare_agent(api_key=None):
138
 
139
  return agent
140
 
141
- def _run_all(api_key: str | None = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  """
143
  Fetches all questions, runs the BasicAgent on them,
144
  """
@@ -165,41 +171,30 @@ def _run_all(api_key: str | None = None):
165
  print(final_status)
166
  return final_status, pd.DataFrame(results_log)
167
 
168
-
169
- def run_all():
170
- run_status, results_df = _run_all(os.environ.get("GOOGLE_API_KEY"))
171
- return run_status, results_df
172
-
173
- def submit_all( profile: gr.OAuthProfile | None, to_file = True):
174
  """
175
  Submits all answers and displays the results.
176
  """
177
  submit_url = f"{DEFAULT_API_URL}/submit"
178
 
179
- if not to_file:
180
- if profile:
181
- username= f"{profile.username}"
182
- print(f"User logged in: {username}")
183
- else:
184
- print("User not logged in.")
185
- return "Please Login to Hugging Face with the button."
186
-
187
  # --- Determine HF Space Runtime URL and Repo URL ---
188
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
189
 
190
  if not answers_by_task:
191
  submit_status = "No answers to submit."
192
  else:
193
-
194
  # 4. Submit all answers
195
- answers_payload = list(answers_by_task.values())
196
  # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
197
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
198
-
199
- if to_file:
200
- submit_status = _submit_answers_to_file(answers_payload, f"run-{int(time.time())}.json")
201
- else:
202
- submit_status = _submit_all(username, agent_code, answers_payload, submit_url)
203
 
204
  return submit_status
205
 
@@ -219,7 +214,16 @@ with gr.Blocks() as demo:
219
 
220
  gr.LoginButton()
221
 
 
 
 
 
 
 
 
 
222
  run_button = gr.Button("Run Evaluation")
 
223
  submit_button = gr.Button("Submit All Answers")
224
 
225
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
@@ -227,9 +231,15 @@ with gr.Blocks() as demo:
227
 
228
  run_button.click(
229
  fn=run_all,
 
230
  outputs=[status_output, results_table]
231
  )
232
 
 
 
 
 
 
233
  submit_button.click(
234
  fn=submit_all,
235
  outputs=[status_output]
 
66
  "Question": question_text,
67
  "Submitted Answer": submitted_answer or agent_error,
68
  }
69
+ return payload, log_entry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  def _submit_all(username, agent_code, answers_payload, submit_url):
72
  # Prepare Submission
 
116
  def prepare_agent(api_key=None):
117
  # 1. Instantiate Agent ( modify this part to create your agent)
118
  try:
119
+ agent = GeminiAgentContainer(api_key=api_key)
120
  print(agent.system_prompt)
121
  except Exception as e:
122
  print(f"Error instantiating agent: {e}")
 
124
 
125
  return agent
126
 
127
+ def save_answers_to_file():
128
+ """
129
+ Submits the answers to a local file named with the current epoch time.
130
+ """
131
+ if not answers_by_task:
132
+ return ("Nothing to save, no answers found.")
133
+ answers_payload = list(answers_by_task.values())
134
+
135
+ file_path = f"answers-{int(time.time())}.json"
136
+ print(f"Saving answers to file: {file_path}")
137
+ try:
138
+ with open(file_path, "w") as file:
139
+ json.dump(answers_payload, file, indent=4)
140
+ submit_status = (f"Answers successfully written to {file_path}")
141
+ except Exception as e:
142
+ submit_status = (f"Error writing answers to file: {e}")
143
+ print(submit_status)
144
+ return submit_status
145
+
146
+
147
+ def run_all(api_key: str | None = None):
148
  """
149
  Fetches all questions, runs the BasicAgent on them,
150
  """
 
171
  print(final_status)
172
  return final_status, pd.DataFrame(results_log)
173
 
174
+ def submit_all( profile: gr.OAuthProfile | None):
 
 
 
 
 
175
  """
176
  Submits all answers and displays the results.
177
  """
178
  submit_url = f"{DEFAULT_API_URL}/submit"
179
 
180
+ if profile:
181
+ username= f"{profile.username}"
182
+ print(f"User logged in: {username}")
183
+ else:
184
+ print("User not logged in.")
185
+ return "Please Login to Hugging Face with the button."
186
+
 
187
  # --- Determine HF Space Runtime URL and Repo URL ---
188
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
189
 
190
  if not answers_by_task:
191
  submit_status = "No answers to submit."
192
  else:
 
193
  # 4. Submit all answers
 
194
  # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
195
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
196
+
197
+ submit_status = _submit_all(username, agent_code, list(answers_by_task.values()), submit_url)
 
 
 
198
 
199
  return submit_status
200
 
 
214
 
215
  gr.LoginButton()
216
 
217
+ api_key_input = gr.Textbox(
218
+ label="Gemini API Key",
219
+ placeholder="Enter your Gemini API key here",
220
+ type="password",
221
+ lines=1,
222
+ visible=True
223
+ )
224
+
225
  run_button = gr.Button("Run Evaluation")
226
+ save_button = gr.Button("Save Answers to File")
227
  submit_button = gr.Button("Submit All Answers")
228
 
229
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
231
 
232
  run_button.click(
233
  fn=run_all,
234
+ inputs=[api_key_input],
235
  outputs=[status_output, results_table]
236
  )
237
 
238
+ save_button.click(
239
+ fn=save_answers_to_file,
240
+ outputs=[status_output]
241
+ )
242
+
243
  submit_button.click(
244
  fn=submit_all,
245
  outputs=[status_output]
my_agent.py CHANGED
@@ -1,30 +1,38 @@
1
  import os
2
  import requests
3
- from smolagents import LiteLLMModel, ToolCallingAgent, tool
4
  from typing import Optional
5
  from google import genai
6
  from google.genai import types
7
  import wikipedia as wiki
8
  from markdownify import markdownify as to_markdown
9
 
10
- # --- Constants --
11
- client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
12
- model_name = "gemini-2.0-flash"
13
-
14
-
15
  # --- Tools ---
16
- @tool
17
- def watch_video(video_url: str, user_query:str) -> str:
18
- """
19
- Watches a video and answers a question about it.
20
- Args:
21
- video_url (str): The URL of the video to watch.
22
- user_query (str): The question to answer about the video.
23
- Returns:
24
- str: The answer to the question.
25
  """
26
- request_json = {
27
- 'model': f'models/{model_name}',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  'contents': [{
29
  "parts": [
30
  {
@@ -38,131 +46,157 @@ def watch_video(video_url: str, user_query:str) -> str:
38
  ]
39
  }]
40
  }
41
- api_url = f'https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={os.getenv("GOOGLE_API_KEY")}'
42
- response = requests.post(
43
- api_url,
44
- json=request_json,
45
- headers={
46
- 'Content-Type': 'application/json',
47
- }
48
- )
49
- if response.status_code != 200:
50
- return f"Error: {response.status_code} - {response.text}"
51
- response_json = response.json()
52
- result_parts = response_json['candidates'][0]['content']['parts']
53
- result = "".join([_.get('text', '') for _ in result_parts])
54
- return result
55
-
56
- @tool
57
- def google_search(query: str) -> str:
58
- """
59
  Performs a Google search and returns the results.
60
- Args:
61
- query (str): The search query.
62
- Returns:
63
- str: The search results.
64
  """
65
- # This is a placeholder for the actual Google search logic
66
- # In a real implementation, you would use the Google Search API
67
- # to get the results.
68
- # For now, we will just return a dummy response.
69
- google_search_tool = types.Tool(
70
- google_search=types.GoogleSearch()
71
- )
72
- response = client.models.generate_content(
73
- model=model_name,
 
 
 
 
 
 
 
 
 
 
74
  contents=f"Please search the internet for: {query}",
75
  config=types.GenerateContentConfig(
76
  tools=[google_search_tool],
77
  response_modalities=['TEXT'],
 
78
  )
79
- )
80
- result_parts = response.candidates[0].content.parts
81
- result = "".join([_.text for _ in result_parts])
82
-
83
- return result
84
-
85
- @tool
86
-
87
- def check_wikipedia_page_titles(query: str) -> str:
88
- """
89
  Searches for Wikipedia pages related to the query and returns the canonical titles of the related pages.
90
- Args:
91
- query (str): The search query.
92
- Returns:
93
- str: A comma separated list of canonical Wikipedia page titles that match the query.
94
- """
95
- response = wiki.search(query)
96
- if len(response) > 0:
97
- result = ", ".join(response)
98
- else:
99
- result = "No results found."
100
- return result
101
-
102
- @tool
103
- def get_wikipedia_page(page_title: str) -> str:
104
  """
105
- Gets the content of a we
106
- Args:
107
- page_title (str): The canonical title of the Wikipedia page.
108
- Returns:
109
- str: Markdown-formatted content of the Wikipedia page.
110
- """
111
- # This is a placeholder for the actual Wikipedia search logic
112
- # In a real implementation, you would use the Wikipedia API
113
- # to get the results.
114
- # For now, we will just return a dummy response.
115
- try:
116
- page = wiki.page(page_title)
117
- except wiki.exceptions.PageError:
118
- return f"Page '{page_title}' not found."
119
- md_content = to_markdown(page.html())
120
- return md_content
121
-
122
- @tool
123
- def run_query_with_file(task_id: str, mime_type: str | None, user_query: str) -> str:
 
124
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  Downloads a file mentioned in a user prompt, adds it to the context, and runs a query on it.
126
  This assumes the file is 20MB or less.
127
- Args:
128
- task_id (str): A unique identifier for the task related to this file, used to download it.
129
- mime_type (str): The MIME type of the file, or the best guess if unknown
130
- user_query (str): The question to answer about the file.
131
- Returns:
132
- str: The answer to the question.
133
  """
134
- # Download the file
135
- file_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
136
- file_response = requests.get(file_url)
137
- if file_response.status_code != 200:
138
- raise Exception(f"Failed to download file: {file_response.status_code} - {file_response.text}")
139
- file_data = file_response.content
140
- mime_type = mime_type or file_response.headers.get('Content-Type', 'application/octet-stream')
141
- response = client.models.generate_content(
142
- model=model_name,
143
- contents=[
144
- types.Part.from_bytes(
145
- data=file_data,
146
- mime_type=mime_type,
147
- ),
148
- user_query,
149
- ]
150
- )
151
-
152
- return response.text
 
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
 
 
155
  # --- Agent Management ---
156
 
157
  class GeminiAgentContainer:
158
  """
159
  A container for the Gemini agent.
160
  """
161
- def __init__(self, model_id: Optional[str] = None, provider: Optional[str] = None):
162
- # model_id = model_id or "Qwen/Qwen2.5-Coder-32B-Instruct"
163
- # provider = provider or "together"
164
- # model = HfApiModel(model_id=model_id, provider=provider)
165
- self.model = LiteLLMModel(model_id=f"gemini/{model_name}", api_key=os.getenv("GOOGLE_API_KEY"))
 
 
166
  system_prompt = """
167
  You are a general AI assistant. I will ask you a question.
168
  YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
@@ -180,13 +214,13 @@ class GeminiAgentContainer:
180
  self.agent = ToolCallingAgent(
181
  model=self.model,
182
  tools = [
183
- watch_video,
184
- google_search,
185
- run_query_with_file,
186
- check_wikipedia_page_titles,
187
- get_wikipedia_page,
188
- ],
189
- max_steps=6,
190
  planning_interval=2,
191
  )
192
  self.system_prompt = system_prompt
 
1
  import os
2
  import requests
3
+ from smolagents import LiteLLMModel, ToolCallingAgent, Tool
4
  from typing import Optional
5
  from google import genai
6
  from google.genai import types
7
  import wikipedia as wiki
8
  from markdownify import markdownify as to_markdown
9
 
 
 
 
 
 
10
  # --- Tools ---
11
+
12
+ class VideoWatchingTool(Tool):
13
+ name = "watch_video"
14
+ description ="""
15
+ A tool for watching videos and answering questions about them.
 
 
 
 
16
  """
17
+ inputs = {
18
+ "video_url": {
19
+ "type": "string",
20
+ "description": "The URL of the video to watch."
21
+ },
22
+ "user_query": {
23
+ "type": "string",
24
+ "description": "The question to answer about the video."
25
+ }
26
+ }
27
+ output_type = "string"
28
+
29
+ def __init__(self, model_name, *args, **kwargs):
30
+ super().__init__(*args, **kwargs)
31
+ self.model_name = model_name
32
+
33
+ def forward(self, video_url: str, user_query: str) -> str:
34
+ request_json = {
35
+ 'model': f'models/{self.model_name}',
36
  'contents': [{
37
  "parts": [
38
  {
 
46
  ]
47
  }]
48
  }
49
+ api_url = f'https://generativelanguage.googleapis.com/v1beta/models/{self.model_name}:generateContent?key={os.getenv("GOOGLE_API_KEY")}'
50
+ response = requests.post(
51
+ api_url,
52
+ json=request_json,
53
+ headers={
54
+ 'Content-Type': 'application/json',
55
+ }
56
+ )
57
+ if response.status_code != 200:
58
+ return f"Error: {response.status_code} - {response.text}"
59
+ response_json = response.json()
60
+ result_parts = response_json['candidates'][0]['content']['parts']
61
+ result = "".join([_.get('text', '') for _ in result_parts])
62
+ return result
63
+
64
+ class GoogleSearchTool(Tool):
65
+ name = "google_search"
66
+ description = """
67
  Performs a Google search and returns the results.
 
 
 
 
68
  """
69
+ inputs = {
70
+ "query": {
71
+ "type": "string",
72
+ "description": "The search query."
73
+ }
74
+ }
75
+ output_type = "string"
76
+
77
+ def __init__(self, client, model_name, *args, **kwargs):
78
+ super().__init__(*args, **kwargs)
79
+ self.client = client
80
+ self.model_name = model_name
81
+
82
+ def forward(self, query: str) -> str:
83
+ google_search_tool = types.Tool(
84
+ google_search=types.GoogleSearch()
85
+ )
86
+ response = self.client.models.generate_content(
87
+ model=self.model_name,
88
  contents=f"Please search the internet for: {query}",
89
  config=types.GenerateContentConfig(
90
  tools=[google_search_tool],
91
  response_modalities=['TEXT'],
92
+ )
93
  )
94
+ return response.text
95
+
96
+ class WikipediaTitleSearchTool(Tool):
97
+ name = "check_wikipedia_page_titles"
98
+ description = """
 
 
 
 
 
99
  Searches for Wikipedia pages related to the query and returns the canonical titles of the related pages.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  """
101
+ inputs = {
102
+ "query": {
103
+ "type": "string",
104
+ "description": "The search query."
105
+ }
106
+ }
107
+ output_type = "string"
108
+
109
+ def forward(self, query: str) -> str:
110
+ response = wiki.search(query)
111
+ if len(response) > 0:
112
+ result = ", ".join(response)
113
+ else:
114
+ result = "No results found."
115
+ return result
116
+
117
+ class WikipediaPageTool(Tool):
118
+ name = "get_wikipedia_page"
119
+ description = """
120
+ Gets the content of a Wikipedia page.
121
  """
122
+ inputs = {
123
+ "page_title": {
124
+ "type": "string",
125
+ "description": "The canonical title of the Wikipedia page."
126
+ }
127
+ }
128
+ output_type = "string"
129
+
130
+ def forward(self, page_title: str) -> str:
131
+ # TODO: may need to do caching of the HTML ourselves?
132
+ try:
133
+ page = wiki.page(page_title)
134
+ except wiki.exceptions.PageError:
135
+ return f"Page '{page_title}' not found."
136
+ md_content = to_markdown(page.html())
137
+ return md_content
138
+
139
+ class FileAttachmentQueryTool(Tool):
140
+ name = "run_query_with_file"
141
+ description = """
142
  Downloads a file mentioned in a user prompt, adds it to the context, and runs a query on it.
143
  This assumes the file is 20MB or less.
 
 
 
 
 
 
144
  """
145
+ inputs = {
146
+ "task_id": {
147
+ "type": "string",
148
+ "description": "A unique identifier for the task related to this file, used to download it."
149
+ },
150
+ "mime_type": {
151
+ "type": "string",
152
+ "nullable": True,
153
+ "description": "The MIME type of the file, or the best guess if unknown."
154
+ },
155
+ "user_query": {
156
+ "type": "string",
157
+ "description": "The question to answer about the file."
158
+ }
159
+ }
160
+ output_type = "string"
161
+ def __init__(self, client, model_name, *args, **kwargs):
162
+ super().__init__(*args, **kwargs)
163
+ self.client = client
164
+ self.model_name = model_name
165
 
166
+ def forward(self, task_id: str, mime_type: str | None, user_query: str) -> str:
167
+ # Download the file
168
+ file_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
169
+ file_response = requests.get(file_url)
170
+ if file_response.status_code != 200:
171
+ raise Exception(f"Failed to download file: {file_response.status_code} - {file_response.text}")
172
+ file_data = file_response.content
173
+ mime_type = mime_type or file_response.headers.get('Content-Type', 'application/octet-stream')
174
+ response = self.client.models.generate_content(
175
+ model=self.model_name,
176
+ contents=[
177
+ types.Part.from_bytes(
178
+ data=file_data,
179
+ mime_type=mime_type,
180
+ ),
181
+ user_query,
182
+ ]
183
+ )
184
 
185
+ return response.text
186
+
187
  # --- Agent Management ---
188
 
189
  class GeminiAgentContainer:
190
  """
191
  A container for the Gemini agent.
192
  """
193
+ # TODO: make it easier to chnge the model
194
+ MODEL_NAME = "gemini-2.0-flash"
195
+
196
+ def __init__(self, api_key: Optional[str] = None):
197
+ api_key = api_key or os.getenv("GOOGLE_API_KEY")
198
+ self.model = LiteLLMModel(model_id=f"gemini/{self.MODEL_NAME}", api_key=api_key)
199
+ self.client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
200
  system_prompt = """
201
  You are a general AI assistant. I will ask you a question.
202
  YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
 
214
  self.agent = ToolCallingAgent(
215
  model=self.model,
216
  tools = [
217
+ VideoWatchingTool(model_name=self.MODEL_NAME),
218
+ GoogleSearchTool(client=self.client, model_name=self.MODEL_NAME),
219
+ WikipediaTitleSearchTool(),
220
+ WikipediaPageTool(),
221
+ FileAttachmentQueryTool(client=self.client, model_name=self.MODEL_NAME),
222
+ ],
223
+ max_steps=14,
224
  planning_interval=2,
225
  )
226
  self.system_prompt = system_prompt