Walidb commited on
Commit
d847548
·
1 Parent(s): c5a89e9
Files changed (3) hide show
  1. app.py +103 -14
  2. myagent_langraph.py +253 -0
  3. requirements.txt +6 -1
app.py CHANGED
@@ -3,21 +3,39 @@ 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 +58,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
@@ -73,7 +91,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
73
  results_log = []
74
  answers_payload = []
75
  print(f"Running agent on {len(questions_data)} questions...")
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:
@@ -141,6 +159,70 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
141
  return status_message, results_df
142
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  # --- Build Gradio Interface using Blocks ---
146
  with gr.Blocks() as demo:
@@ -151,7 +233,8 @@ with gr.Blocks() as demo:
151
 
152
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
153
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
154
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
155
 
156
  ---
157
  **Disclaimers:**
@@ -162,10 +245,11 @@ with gr.Blocks() as demo:
162
 
163
  gr.LoginButton()
164
 
165
- run_button = gr.Button("Run Evaluation (& Submit All Answers)")
 
 
166
 
167
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
168
- # Removed max_rows=10 from DataFrame constructor
169
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
170
 
171
  run_button.click(
@@ -173,6 +257,11 @@ with gr.Blocks() as demo:
173
  outputs=[status_output, results_table]
174
  )
175
 
 
 
 
 
 
176
  if __name__ == "__main__":
177
  print("\n" + "-"*30 + " App Starting " + "-"*30)
178
  # Check for SPACE_HOST and SPACE_ID at startup for information
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from myagent_langraph import build_graph
7
+ from dotenv import load_dotenv
8
+ from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
9
 
10
+ # Charger les variables d'environnement depuis le fichier .env
11
+ load_dotenv()
 
12
 
13
+ class WalidzAgent:
14
+ """A langgraph agent."""
 
15
  def __init__(self):
16
  print("BasicAgent initialized.")
17
+ self.graph = build_graph()
18
+
19
  def __call__(self, question: str) -> str:
20
  print(f"Agent received question (first 50 chars): {question[:50]}...")
21
+ # Wrap the question in a HumanMessage from langchain_core
22
+ messages = [HumanMessage(content=question)]
23
+ messages = self.graph.invoke({"messages": messages})
24
+ answer = messages['messages'][-1].content
25
+ # Find the last occurrence of "FINAL ANSWER:" in the response
26
+ if "FINAL ANSWER:" in answer:
27
+ # Split by "FINAL ANSWER:" and take the last part
28
+ final_answer_parts = answer.split("FINAL ANSWER:")
29
+ answer = final_answer_parts[-1].strip()
30
+ else:
31
+ # If "FINAL ANSWER:" is not found, return the original answer
32
+ print("Warning: 'FINAL ANSWER:' not found in response")
33
+ return answer
34
+
35
+ # (Keep Constants as is)
36
+ # --- Constants ---
37
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
38
+
39
 
40
  def run_and_submit_all( profile: gr.OAuthProfile | None):
41
  """
 
58
 
59
  # 1. Instantiate Agent ( modify this part to create your agent)
60
  try:
61
+ agent = WalidzAgent()
62
  except Exception as e:
63
  print(f"Error instantiating agent: {e}")
64
  return f"Error initializing agent: {e}", None
 
91
  results_log = []
92
  answers_payload = []
93
  print(f"Running agent on {len(questions_data)} questions...")
94
+ for item in questions_data[0:1]:
95
  task_id = item.get("task_id")
96
  question_text = item.get("question")
97
  if not task_id or question_text is None:
 
159
  return status_message, results_df
160
 
161
 
162
+ def run_evaluation_only(profile: gr.OAuthProfile | None):
163
+ """
164
+ Fetches all questions, runs the BasicAgent on them, but does not submit answers.
165
+ Only displays the results.
166
+ """
167
+ # --- Determine HF Space Runtime URL and Repo URL ---
168
+ space_id = os.getenv("SPACE_ID")
169
+
170
+ if profile:
171
+ username = f"{profile.username}"
172
+ print(f"User logged in: {username}")
173
+ else:
174
+ print("User not logged in.")
175
+ return "Please Login to Hugging Face with the button.", None
176
+
177
+ api_url = DEFAULT_API_URL
178
+ questions_url = f"{api_url}/questions"
179
+
180
+ # 1. Instantiate Agent
181
+ try:
182
+ agent = WalidzAgent()
183
+ except Exception as e:
184
+ print(f"Error instantiating agent: {e}")
185
+ return f"Error initializing agent: {e}", None
186
+
187
+ # 2. Fetch Questions
188
+ print(f"Fetching questions from: {questions_url}")
189
+ try:
190
+ response = requests.get(questions_url, timeout=15)
191
+ response.raise_for_status()
192
+ questions_data = response.json()
193
+ if not questions_data:
194
+ print("Fetched questions list is empty.")
195
+ return "Fetched questions list is empty or invalid format.", None
196
+ print(f"Fetched {len(questions_data)} questions.")
197
+ except requests.exceptions.RequestException as e:
198
+ print(f"Error fetching questions: {e}")
199
+ return f"Error fetching questions: {e}", None
200
+ except requests.exceptions.JSONDecodeError as e:
201
+ print(f"Error decoding JSON response from questions endpoint: {e}")
202
+ print(f"Response text: {response.text[:500]}")
203
+ return f"Error decoding server response for questions: {e}", None
204
+ except Exception as e:
205
+ print(f"An unexpected error occurred fetching questions: {e}")
206
+ return f"An unexpected error occurred fetching questions: {e}", None
207
+
208
+ # 3. Run your Agent
209
+ results_log = []
210
+ print(f"Running agent on {len(questions_data)} questions...")
211
+ for item in questions_data[0:1]:
212
+ task_id = item.get("task_id")
213
+ question_text = item.get("question")
214
+ if not task_id or question_text is None:
215
+ print(f"Skipping item with missing task_id or question: {item}")
216
+ continue
217
+ try:
218
+ submitted_answer = agent(question_text)
219
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
220
+ except Exception as e:
221
+ print(f"Error running agent on task {task_id}: {e}")
222
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
223
+
224
+ results_df = pd.DataFrame(results_log)
225
+ return "Evaluation completed. Answers not submitted.", results_df
226
 
227
  # --- Build Gradio Interface using Blocks ---
228
  with gr.Blocks() as demo:
 
233
 
234
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
235
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
236
+ 3. Click 'Run Evaluation (& Submit All Answers)' to fetch questions, run your agent, submit answers, and see the score.
237
+ 4. Click 'Run Evaluation Only' to test your agent without submitting answers.
238
 
239
  ---
240
  **Disclaimers:**
 
245
 
246
  gr.LoginButton()
247
 
248
+ with gr.Row():
249
+ run_button = gr.Button("Run Evaluation (& Submit All Answers)")
250
+ run_eval_only_button = gr.Button("Run Evaluation Only")
251
 
252
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
253
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
254
 
255
  run_button.click(
 
257
  outputs=[status_output, results_table]
258
  )
259
 
260
+ run_eval_only_button.click(
261
+ fn=run_evaluation_only,
262
+ outputs=[status_output, results_table]
263
+ )
264
+
265
  if __name__ == "__main__":
266
  print("\n" + "-"*30 + " App Starting " + "-"*30)
267
  # Check for SPACE_HOST and SPACE_ID at startup for information
myagent_langraph.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ from typing import List, TypedDict, Annotated, Optional
3
+ from langchain_openai import ChatOpenAI
4
+ from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
5
+ from langgraph.graph.message import add_messages
6
+ from langgraph.graph import START, StateGraph
7
+ from langgraph.prebuilt import ToolNode, tools_condition
8
+ from IPython.display import Image, display
9
+ from langchain_community.document_loaders import WikipediaLoader
10
+ from langchain_community.tools.tavily_search import TavilySearchResults
11
+
12
+ from dotenv import load_dotenv
13
+ import os
14
+ import sys
15
+
16
+ # Do not modify stdout/stdin as it can cause issues
17
+ import codecs
18
+
19
+ # Load environment variables
20
+ load_dotenv()
21
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
22
+
23
+ # Validate API key
24
+ if not OPENAI_API_KEY:
25
+ print("Warning: OPENAI_API_KEY environment variable not found or empty!")
26
+
27
+ class AgentState(TypedDict):
28
+ # The document provided
29
+ question_text: Optional[str]
30
+ messages: Annotated[list[AnyMessage], add_messages]
31
+
32
+ ### TOOLS ###
33
+ def search_wikipedia(query: str) -> str:
34
+ """Searches Wikipedia for a given query and returns maximum 1 results."""
35
+ try:
36
+ # Ensure query is properly encoded
37
+ if isinstance(query, bytes):
38
+ query = query.decode('utf-8')
39
+
40
+ # Use WikipediaLoader with the encoded query
41
+ search_docs = WikipediaLoader(
42
+ query=query,
43
+ load_max_docs=1,
44
+ doc_content_chars_max=10000
45
+ ).load()
46
+
47
+ # Format the results with proper encoding
48
+ formatted_docs = []
49
+ for doc in search_docs:
50
+ source = doc.metadata.get("source", "").encode('utf-8', errors='replace').decode('utf-8')
51
+ page = doc.metadata.get("page", "").encode('utf-8', errors='replace').decode('utf-8')
52
+ content = doc.page_content.encode('utf-8', errors='replace').decode('utf-8')
53
+ formatted_docs.append(
54
+ f'<Document source="{source}" page="{page}"/>\n{content}\n</Document>'
55
+ )
56
+
57
+ return "\n\n---\n\n".join(formatted_docs)
58
+ except Exception as e:
59
+ print(f"Error in search_wikipedia: {e}")
60
+ return f"Error searching Wikipedia: {str(e)}"
61
+
62
+ def multiply(a: int, b: int) -> int:
63
+ """Multiply two numbers.
64
+ Args:
65
+ a: first int
66
+ b: second int
67
+ """
68
+ return a * b
69
+
70
+ def add(a: int, b: int) -> int:
71
+ """Add two numbers.
72
+
73
+ Args:
74
+ a: first int
75
+ b: second int
76
+ """
77
+ return a + b
78
+
79
+ def subtract(a: int, b: int) -> int:
80
+ """Subtract two numbers.
81
+
82
+ Args:
83
+ a: first int
84
+ b: second int
85
+ """
86
+ return a - b
87
+
88
+ def divide(a: int, b: int) -> int:
89
+ """Divide two numbers.
90
+
91
+ Args:
92
+ a: first int
93
+ b: second int
94
+ """
95
+ if b == 0:
96
+ raise ValueError("Cannot divide by zero.")
97
+ return a / b
98
+
99
+ def modulus(a: int, b: int) -> int:
100
+ """Get the modulus of two numbers.
101
+
102
+ Args:
103
+ a: first int
104
+ b: second int
105
+ """
106
+ return a % b
107
+
108
+
109
+ def web_search(query: str) -> str:
110
+ """Search Tavily for a query and return maximum 3 results.
111
+
112
+ Args:
113
+ query: The search query."""
114
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
115
+ formatted_search_docs = "\n\n---\n\n".join(
116
+ [
117
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
118
+ for doc in search_docs
119
+ ])
120
+ return {"web_results": formatted_search_docs}
121
+
122
+ # Equip the butler with tools
123
+ tools = [
124
+ multiply,
125
+ subtract,
126
+ add,
127
+ divide,
128
+ modulus,
129
+ search_wikipedia,
130
+ web_search
131
+ ]
132
+
133
+ # Initialize the OpenAI model with explicit encoding parameters
134
+ try:
135
+ # Use encoding='utf-8' to ensure proper handling of special characters
136
+ llm = ChatOpenAI(
137
+ model="gpt-4o-mini",
138
+ openai_api_key=OPENAI_API_KEY,
139
+ default_headers={"Content-Type": "application/json; charset=utf-8"}
140
+ )
141
+ llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)
142
+ except Exception as e:
143
+ print(f"Error initializing OpenAI model: {e}")
144
+ raise
145
+
146
+ #### THE NODES #####
147
+ def assistant(state: AgentState):
148
+ try:
149
+ # Get the question text from state and ensure it's properly encoded
150
+ question = state.get("question_text", "")
151
+
152
+ # Build the system prompt piece by piece
153
+ parts = [
154
+ "You are a helpful assistant tasked with answering questions using a set of tools.",
155
+ "Report your thoughts, and finish your answer with the following template:",
156
+ "FINAL ANSWER: [YOUR FINAL ANSWER].",
157
+ "YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.",
158
+ "If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.",
159
+ "If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.",
160
+ "If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.",
161
+
162
+ # Tool descriptions
163
+ "You can use these provided tools:",
164
+ "search_wikipedia(query: str) -> str:",
165
+ " Searches Wikipedia for a given query and returns maximum 1 results.",
166
+ "divide(a: int, b: int) -> float:",
167
+ " Divide a and b"
168
+ ]
169
+
170
+ # Add the question
171
+ parts.append(f"The question you have to answer is: {question}")
172
+
173
+ # Join all parts with newlines and ensure proper encoding
174
+ sys_prompt = "\n".join(parts).encode('utf-8', errors='replace').decode('utf-8')
175
+
176
+ # Create system message with explicit encoding
177
+ sys_msg = SystemMessage(content=sys_prompt)
178
+
179
+ # Invoke the model with proper error handling
180
+ messages = llm_with_tools.invoke([sys_msg] + state["messages"])
181
+ return {
182
+ "messages": messages,
183
+ "question_text": question
184
+ }
185
+ except Exception as e:
186
+ error_msg = f"Error in assistant node: {str(e)}"
187
+ print(error_msg)
188
+ return {
189
+ "messages": [SystemMessage(content=f"An error occurred: {error_msg}")],
190
+ "question_text": state.get("question_text", "")
191
+ }
192
+
193
+ def build_graph():
194
+ # The graph
195
+ builder = StateGraph(AgentState)
196
+
197
+ # Define nodes: these do the work
198
+ builder.add_node("assistant", assistant)
199
+ builder.add_node("tools", ToolNode(tools))
200
+
201
+ # Define edges: these determine how the control flow moves
202
+ builder.add_edge(START, "assistant")
203
+ builder.add_conditional_edges(
204
+ "assistant",
205
+ # If the latest message requires a tool, route to tools
206
+ # Otherwise, provide a direct response
207
+ tools_condition,
208
+ )
209
+ builder.add_edge("tools", "assistant")
210
+ react_graph = builder.compile()
211
+ return react_graph
212
+
213
+
214
+ if __name__ == "__main__":
215
+ react_graph = build_graph()
216
+ # Show the butler's thought process
217
+ # Save the graph visualization to disk
218
+ graph_image = react_graph.get_graph(xray=True).draw_mermaid_png()
219
+ with open("graph_visualization.png", "wb") as f:
220
+ f.write(graph_image)
221
+ print("Graph visualization saved to 'graph_visualization.png'")
222
+
223
+ # Also display it if in an environment that supports display
224
+ #display(Image(graph_image))
225
+
226
+
227
+ question_sample = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia."
228
+ #question_sample = question_sample.encode('utf-8', errors='replace').decode('utf-8')
229
+
230
+ print("Running with sample question:", question_sample)
231
+
232
+ try:
233
+ # Create message with proper encoding
234
+ human_msg = HumanMessage(content=question_sample)
235
+
236
+ # Prepare input state
237
+ input_state = {
238
+ "messages": [human_msg],
239
+ "question_text": question_sample
240
+ }
241
+
242
+ # Invoke graph with proper encoding handling
243
+ print("Invoking graph...")
244
+ messages = react_graph.invoke(input_state)
245
+
246
+ # Show the messages
247
+ for m in messages['messages']:
248
+ m.pretty_print()
249
+ except Exception as e:
250
+ print(f"Error in Agent execution: {str(e)}")
251
+ import traceback
252
+ traceback.print_exc()
253
+
requirements.txt CHANGED
@@ -1,2 +1,7 @@
1
  gradio
2
- requests
 
 
 
 
 
 
1
  gradio
2
+ requests
3
+ python-dotenv
4
+ langchain
5
+ langchain-community
6
+ wikipedia
7
+ smolagents