MariaFilipkowska commited on
Commit
dc27495
·
verified ·
1 Parent(s): e831570

Create app

Browse files
Files changed (1) hide show
  1. app.py +336 -0
app.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import pandas as pd
5
+ import base64
6
+ import ollama
7
+ from typing import TypedDict, Annotated
8
+ from langchain_core.messages import AnyMessage, SystemMessage
9
+ from langchain_community.tools import DuckDuckGoSearchRun
10
+ from langchain_ollama.chat_models import ChatOllama
11
+ from langchain.tools import Tool
12
+ from langgraph.graph import START, StateGraph
13
+ from langgraph.graph.message import add_messages
14
+ from langgraph.prebuilt import ToolNode, tools_condition
15
+
16
+ # (Keep Constants as is)
17
+ # --- Constants ---
18
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
19
+
20
+
21
+ def get_file_path(task_id: str, question) -> str:
22
+ """Retrieves reference file path."""
23
+ if question['task_id'] == task_id:
24
+ return question['file_path']
25
+
26
+
27
+ def get_ref_content(path: str) -> str | object:
28
+ """Retrieves content from the reference path provided."""
29
+ with open(path, "rb") as f:
30
+ file = f.read()
31
+ return file
32
+
33
+
34
+ def search_web(topic: str) -> str:
35
+ """Retrieves information about the topic."""
36
+ results = DuckDuckGoSearchRun().invoke(topic)
37
+ if results:
38
+ return "\n\n".join([doc.text for doc in results[:2]])
39
+ else:
40
+ return "No matching content found."
41
+
42
+
43
+ def extract_text_from_image(img_path: str) -> str:
44
+ """Extracts text from image"""
45
+ try:
46
+ # Read image and encode as base64
47
+ with open(img_path, "rb") as image_file:
48
+ image_bytes = image_file.read()
49
+
50
+ image_base64 = base64.b64encode(image_bytes).decode("utf-8")
51
+ return image_base64
52
+ except Exception as e:
53
+ # A butler should handle errors gracefully
54
+ error_msg = f"Error extracting text: {str(e)}"
55
+ print(error_msg)
56
+ return ""
57
+
58
+
59
+ # Initialize the tool
60
+ get_file_path_tool = Tool(
61
+ name="file_path_retriever",
62
+ func=get_file_path,
63
+ description="Retrieves path to the reference file."
64
+ )
65
+
66
+ get_content_tool = Tool(
67
+ name="ref_content_retriever",
68
+ func=get_ref_content,
69
+ description="Retrieves reference file content."
70
+ )
71
+
72
+ search_web_tool = Tool(
73
+ name="search_web_retriever",
74
+ func=search_web,
75
+ description="Retrieves online info about a specific topic."
76
+ )
77
+
78
+ extract_text_tool = Tool(
79
+ name="extract_text_retriever",
80
+ func=extract_text_from_image,
81
+ description="Retrieves text from an image."
82
+ )
83
+
84
+ # Generate the chat interface, including the tools
85
+ print("Loading LLM...")
86
+ ollama.pull('gemma3:4b')
87
+ chat = ChatOllama(model="gemma3:4b", verbose=True)
88
+ print(f"Model {chat.model} downloaded!")
89
+
90
+ tools = [get_file_path_tool, get_content_tool, extract_text_tool, search_web_tool]
91
+ chat_with_tools = chat.bind_tools(tools, parallel_tool_calls=False)
92
+
93
+
94
+ # Generate the AgentState and Agent graph
95
+ class AgentState(TypedDict):
96
+ messages: Annotated[list[AnyMessage], add_messages]
97
+
98
+
99
+ def assistant(state: AgentState):
100
+ return {
101
+ "messages": chat.invoke(state["messages"]),
102
+ }
103
+
104
+
105
+ # The graph
106
+ builder = StateGraph(AgentState)
107
+
108
+ # Define nodes: these do the work
109
+ builder.add_node("assistant", assistant)
110
+ builder.add_node("tools", ToolNode([get_file_path_tool, get_content_tool, extract_text_tool, search_web_tool]))
111
+
112
+ # Define edges: these determine how the control flow moves
113
+ builder.add_edge(START, "assistant")
114
+ builder.add_conditional_edges(
115
+ "assistant",
116
+ # If the latest message requires a tool, route to tools
117
+ # Otherwise, provide a direct response
118
+ tools_condition
119
+ )
120
+ builder.add_edge("tools", "assistant")
121
+ alfred = builder.compile()
122
+ system_prompt = SystemMessage(
123
+ content="You are a general AI assistant. \
124
+ I will ask you a question. Report your thoughts shortly, \
125
+ and finish your answer with the following template: \
126
+ FINAL ANSWER: [YOUR FINAL ANSWER]. \
127
+ YOUR FINAL ANSWER should be a number OR as few words as possible \
128
+ OR a comma separated list of numbers and/or strings. \
129
+ If you are asked for a number, use only digits in your final answer. \
130
+ Don't use comma nor brackets to write your number neither use units such as $ or percent sign \
131
+ unless specified otherwise. \
132
+ If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), \
133
+ and write the digits in plain text unless specified otherwise. \
134
+ If you are asked for a comma separated list, apply the above rules \
135
+ depending of whether the element to be put in the list is a number or a string. \
136
+ If the question refers to an external content and there is no reference file attached, \
137
+ perform a web search and retrieve relevant information from the internet. \
138
+ Make sure that each final answer is preceded with 'FINAL ANSWER:'. "
139
+ )
140
+
141
+
142
+ class BasicAgent:
143
+ def __init__(self):
144
+ print("BasicAgent initialized.")
145
+
146
+ def __call__(self, question: str) -> str:
147
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
148
+ fixed_answer = "This is a default answer."
149
+ print(f"Agent returning fixed answer: {fixed_answer}")
150
+ return fixed_answer
151
+
152
+
153
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
154
+ """
155
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
156
+ and displays the results.
157
+ """
158
+ # --- Determine HF Space Runtime URL and Repo URL ---
159
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
160
+
161
+ if profile:
162
+ username = f"{profile.username}"
163
+ print(f"User logged in: {username}")
164
+ else:
165
+ print("User not logged in.")
166
+ return "Please Login to Hugging Face with the button.", None
167
+
168
+ api_url = DEFAULT_API_URL
169
+ questions_url = f"{api_url}/questions"
170
+ submit_url = f"{api_url}/submit"
171
+
172
+ # 1. Instantiate Agent ( modify this part to create your agent)
173
+ try:
174
+ agent = BasicAgent()
175
+ except Exception as e:
176
+ print(f"Error instantiating agent: {e}")
177
+ return f"Error initializing agent: {e}", None
178
+ # In the case of an app running as a hugging Face space,
179
+ # this link points toward your codebase ( useful for others so please keep it public)
180
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
181
+ print(agent_code)
182
+
183
+ # 2. Fetch Questions
184
+ print(f"Fetching questions from: {questions_url}")
185
+ try:
186
+ response = requests.get(questions_url, timeout=15)
187
+ response.raise_for_status()
188
+ questions_data = response.json()
189
+ if not questions_data:
190
+ print("Fetched questions list is empty.")
191
+ return "Fetched questions list is empty or invalid format.", None
192
+ print(f"Fetched {len(questions_data)} questions.")
193
+ except requests.exceptions.RequestException as e:
194
+ print(f"Error fetching questions: {e}")
195
+ return f"Error fetching questions: {e}", None
196
+ except requests.exceptions.JSONDecodeError as e:
197
+ print(f"Error decoding JSON response from questions endpoint: {e}")
198
+ # print(f"Response text: {response.text[:500]}")
199
+ return f"Error decoding server response for questions: {e}", None
200
+ except Exception as e:
201
+ print(f"An unexpected error occurred fetching questions: {e}")
202
+ return f"An unexpected error occurred fetching questions: {e}", None
203
+
204
+ # 3. Run your Agent
205
+ results_log = []
206
+ answers_payload = []
207
+ print(f"Running agent on {len(questions_data)} questions...")
208
+ for item in questions_data:
209
+ task_id = item.get("task_id")
210
+ question_text = item.get("question")
211
+ if not task_id or question_text is None:
212
+ print(f"Skipping item with missing task_id or question: {item}")
213
+ continue
214
+ try:
215
+ submitted_answer = agent(question_text)
216
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
217
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
218
+ except Exception as e:
219
+ print(f"Error running agent on task {task_id}: {e}")
220
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
221
+
222
+ if not answers_payload:
223
+ print("Agent did not produce any answers to submit.")
224
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
225
+
226
+ # 4. Prepare Submission
227
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
228
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
229
+ print(status_update)
230
+
231
+ # 5. Submit
232
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
233
+ try:
234
+ response = requests.post(submit_url, json=submission_data, timeout=60)
235
+ response.raise_for_status()
236
+ result_data = response.json()
237
+ final_status = (
238
+ f"Submission Successful!\n"
239
+ f"User: {result_data.get('username')}\n"
240
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
241
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
242
+ f"Message: {result_data.get('message', 'No message received.')}"
243
+ )
244
+ print("Submission successful.")
245
+ results_df = pd.DataFrame(results_log)
246
+ return final_status, results_df
247
+ except requests.exceptions.HTTPError as e:
248
+ error_detail = f"Server responded with status {e.response.status_code}."
249
+ try:
250
+ error_json = e.response.json()
251
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
252
+ except requests.exceptions.JSONDecodeError:
253
+ error_detail += f" Response: {e.response.text[:500]}"
254
+ status_message = f"Submission Failed: {error_detail}"
255
+ print(status_message)
256
+ results_df = pd.DataFrame(results_log)
257
+ return status_message, results_df
258
+ except requests.exceptions.Timeout:
259
+ status_message = "Submission Failed: The request timed out."
260
+ print(status_message)
261
+ results_df = pd.DataFrame(results_log)
262
+ return status_message, results_df
263
+ except requests.exceptions.RequestException as e:
264
+ status_message = f"Submission Failed: Network error - {e}"
265
+ print(status_message)
266
+ results_df = pd.DataFrame(results_log)
267
+ return status_message, results_df
268
+ except Exception as e:
269
+ status_message = f"An unexpected error occurred during submission: {e}"
270
+ print(status_message)
271
+ results_df = pd.DataFrame(results_log)
272
+ return status_message, results_df
273
+
274
+
275
+ # --- Build Gradio Interface using Blocks ---
276
+ with gr.Blocks() as demo:
277
+ gr.Markdown("# Basic Agent Evaluation Runner")
278
+ gr.Markdown(
279
+ """
280
+ **Instructions:**
281
+
282
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, \
283
+ the necessary packages, etc ...
284
+ 2. Log in to your Hugging Face account using the button below. \
285
+ This uses your HF username for submission.
286
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, \
287
+ submit answers, and see the score.
288
+
289
+ ---
290
+ **Disclaimers:**
291
+ Once clicking on the "submit button, it can take quite some time \
292
+ ( this is the time for the agent to go through all the questions).
293
+ This space provides a basic setup and is intentionally sub-optimal to encourage you \
294
+ to develop your own, more robust solution. \
295
+ For instance for the delay process of the submit button, \
296
+ a solution could be to cache the answers and submit in a separate action \
297
+ or even to answer the questions in async.
298
+ """
299
+ )
300
+
301
+ gr.LoginButton()
302
+
303
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
304
+
305
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
306
+ # Removed max_rows=10 from DataFrame constructor
307
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
308
+
309
+ run_button.click(
310
+ fn=run_and_submit_all,
311
+ outputs=[status_output, results_table]
312
+ )
313
+
314
+ if __name__ == "__main__":
315
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
316
+ # Check for SPACE_HOST and SPACE_ID at startup for information
317
+ space_host_startup = os.getenv("SPACE_HOST")
318
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
319
+
320
+ if space_host_startup:
321
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
322
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
323
+ else:
324
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
325
+
326
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
327
+ print(f"✅ SPACE_ID found: {space_id_startup}")
328
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
329
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
330
+ else:
331
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
332
+
333
+ print("-" * (60 + len(" App Starting ")) + "\n")
334
+
335
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
336
+ demo.launch(debug=True, share=False)