ekabaruh commited on
Commit
5440605
·
verified ·
1 Parent(s): d237b9e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -131
app.py CHANGED
@@ -3,102 +3,80 @@ 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
- # --- LangGraph GPT-4.1 Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- import os
14
  import json
15
  from typing import Dict, Any, List, Literal, TypedDict, Annotated, Sequence, cast
16
  import operator
17
  from functools import partial
18
 
19
  # LangChain and LangGraph imports
20
- import langchain
21
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
22
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
23
  from langchain_core.output_parsers import StrOutputParser
24
- from langchain_core.pydantic_v1 import BaseModel, Field, validator
25
- from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
26
  from langchain_openai import ChatOpenAI
27
- from langchain.tools import DuckDuckGoSearchRun
28
- from langchain.utilities import GoogleSearchAPIWrapper
29
 
30
- # LangGraph imports
31
  from langgraph.graph import END, StateGraph
32
- from langgraph.prebuilt import ToolInvocation, ToolNode
33
 
34
- # Define our search tools
 
 
 
35
  class SearchTools:
36
  def __init__(self):
37
  self.search_tool = DuckDuckGoSearchRun()
38
 
39
  def web_search(self, query: str) -> str:
40
- """Search the web for information about a topic."""
41
  try:
42
  result = self.search_tool.run(query)
43
  return result
44
  except Exception as e:
45
  return f"Error searching the web: {str(e)}"
46
 
 
 
 
 
 
 
 
 
 
 
47
  class LangGraphAgent:
48
  def __init__(self):
49
- print("LangGraph GPT-4.1 Agent initializing...")
50
  # Get API key from environment variable
51
  self.api_key = os.getenv("OPENAI_API_KEY")
52
  if not self.api_key:
53
  print("WARNING: OPENAI_API_KEY environment variable not found.")
54
  print("Please set your OpenAI API key as an environment variable or in the space secrets.")
55
- self.model = None
56
  return
57
 
58
- # Initialize GPT-4.1 Turbo model
59
- self.model = ChatOpenAI(
60
- temperature=0,
61
- model="gpt-4-turbo",
62
- api_key=self.api_key,
63
- max_tokens=1000
64
- )
65
- print("OpenAI GPT-4.1 model initialized successfully.")
66
 
67
  # Initialize tools
68
  self.search_tools = SearchTools()
 
69
 
70
- # Build the agent graph
71
- self.agent_executor = self._build_agent_executor()
72
  print("LangGraph agent initialized successfully.")
73
 
74
- def _build_agent_executor(self):
75
- """Build the LangGraph agent with tools"""
76
-
77
- # Define the available tools
78
- tools = [
79
- {
80
- "type": "function",
81
- "function": {
82
- "name": "web_search",
83
- "description": "Search the web for information about a topic",
84
- "parameters": {
85
- "type": "object",
86
- "properties": {
87
- "query": {
88
- "type": "string",
89
- "description": "The search query to use"
90
- }
91
- },
92
- "required": ["query"]
93
- }
94
- }
95
- }
96
- ]
97
 
98
  # Define the system prompt for the agent
99
  system_prompt = """You are an intelligent agent designed to answer questions from the GAIA dataset.
100
 
101
- You have access to search tools to help you find information. Use them when needed to get accurate information.
102
 
103
  IMPORTANT INSTRUCTIONS FOR FINAL ANSWERS:
104
  1. For your FINAL ANSWER, provide ONLY the exact answer - no explanations, no reasoning, no additional text.
@@ -110,86 +88,154 @@ IMPORTANT INSTRUCTIONS FOR FINAL ANSWERS:
110
 
111
  For your thought process, you can think step-by-step about the question, search for relevant information, and consider what would be the most accurate answer.
112
  """
113
-
114
- # Create prompt with tool instructions included
115
- prompt = ChatPromptTemplate.from_messages(
116
- [
117
- ("system", system_prompt),
118
- MessagesPlaceholder(variable_name="messages"),
119
- MessagesPlaceholder(variable_name="agent_scratchpad"),
120
- ]
121
- )
122
-
123
- # Create the ChatOpenAI model with function calling
124
- tool_model = self.model.bind_functions(functions=tools)
125
 
126
- # Define the agent state
127
- class AgentState(TypedDict):
128
- messages: list
129
- agent_scratchpad: list
130
-
131
- # Define the agent runner
132
- def run_agent(state: AgentState):
133
  messages = state["messages"]
134
- agent_scratchpad = state["agent_scratchpad"]
135
- response = tool_model.invoke({
136
- "messages": messages,
137
- "agent_scratchpad": agent_scratchpad,
138
- })
139
- return {"messages": messages + [response], "agent_scratchpad": agent_scratchpad + [response]}
140
-
141
- # Define the tool execution node for the web search
142
- def run_tool(state: AgentState, tool_invocation: ToolInvocation):
143
- messages = state["messages"]
144
- agent_scratchpad = state["agent_scratchpad"]
145
- if tool_invocation.name == "web_search":
146
- tool_result = self.search_tools.web_search(tool_invocation.arguments["query"])
147
- return {"messages": messages, "agent_scratchpad": agent_scratchpad + [AIMessage(content=tool_result)]}
148
- else:
149
- return {"messages": messages, "agent_scratchpad": agent_scratchpad + [AIMessage(content="Tool not found")]}
150
 
151
- # Define should_continue function to determine if we should continue or provide the final answer
152
- def should_continue(state: AgentState) -> Literal["agent", "tool", "end"]:
153
- """Determine if we should continue with the agent or end."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  messages = state["messages"]
155
- if not messages:
156
- return "agent"
157
 
158
- last_message = messages[-1]
 
 
 
 
 
 
159
 
160
- # Check if the message has a function call
161
- if hasattr(last_message, "function_call") and last_message.function_call:
162
- return "tool"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
- # If no function call and it's an AI message, we're done
165
- if isinstance(last_message, AIMessage):
166
- return "end"
167
 
168
- # Continue with the agent
169
- return "agent"
170
-
171
  # Create the graph
172
  workflow = StateGraph(AgentState)
173
 
174
  # Add nodes
175
- workflow.add_node("agent", run_agent)
176
- workflow.add_node("tool", partial(ToolNode(), run_tool))
177
 
178
- # Add conditional edges
179
  workflow.add_conditional_edges(
180
  "agent",
181
- should_continue,
182
  {
183
- "tool": "tool",
184
- "end": END,
185
- "agent": "agent",
186
- },
187
  )
188
 
189
- # Connect tool back to agent
190
- workflow.add_edge("tool", "agent")
191
 
192
- # Set entry point
193
  workflow.set_entry_point("agent")
194
 
195
  # Compile the graph
@@ -198,31 +244,31 @@ For your thought process, you can think step-by-step about the question, search
198
  def __call__(self, question: str) -> str:
199
  print(f"Agent received question (first 50 chars): {question[:50]}...")
200
 
201
- if not self.model or not self.agent_executor:
202
  return "OpenAI API key not set. Please set the OPENAI_API_KEY as a secret in your space settings."
203
 
204
  try:
205
  # Initial state with the question
206
  initial_state = {
207
- "messages": [HumanMessage(content=question)],
208
- "agent_scratchpad": []
209
  }
210
 
211
- # Execute the agent
212
- result = self.agent_executor.invoke(initial_state)
213
 
214
- # Get the final message from the result
215
- messages = result.get("messages", [])
216
  if not messages:
217
  return "No response generated."
218
 
219
- # Extract the final answer from the last AI message
220
- final_messages = [m for m in messages if isinstance(m, AIMessage)]
221
  if not final_messages:
222
- return "No AI response found."
223
 
224
- last_ai_message = final_messages[-1]
225
- raw_answer = last_ai_message.content
226
 
227
  # Clean up the answer
228
  answer = raw_answer.strip()
@@ -250,9 +296,9 @@ For your thought process, you can think step-by-step about the question, search
250
  print(f"Error while processing question: {e}")
251
  return f"Error processing question: {str(e)}"
252
 
253
- def run_and_submit_all( profile: gr.OAuthProfile | None):
254
  """
255
- Fetches all questions, runs the BasicAgent on them, submits all answers,
256
  and displays the results.
257
  """
258
  # --- Determine HF Space Runtime URL and Repo URL ---
@@ -269,13 +315,13 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
269
  questions_url = f"{api_url}/questions"
270
  submit_url = f"{api_url}/submit"
271
 
272
- # 1. Instantiate Agent ( modify this part to create your agent)
273
  try:
274
  agent = LangGraphAgent()
275
  except Exception as e:
276
  print(f"Error instantiating agent: {e}")
277
  return f"Error initializing agent: {e}", None
278
- # 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)
279
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
280
  print(agent_code)
281
 
@@ -394,7 +440,6 @@ with gr.Blocks() as demo:
394
  run_button = gr.Button("Run Evaluation & Submit All Answers")
395
 
396
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
397
- # Removed max_rows=10 from DataFrame constructor
398
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
399
 
400
  run_button.click(
 
3
  import requests
4
  import inspect
5
  import pandas as pd
 
 
 
 
 
 
 
 
6
  import json
7
  from typing import Dict, Any, List, Literal, TypedDict, Annotated, Sequence, cast
8
  import operator
9
  from functools import partial
10
 
11
  # LangChain and LangGraph imports
 
12
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
13
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
14
  from langchain_core.output_parsers import StrOutputParser
15
+ from pydantic import BaseModel, Field, validator
 
16
  from langchain_openai import ChatOpenAI
17
+ from langchain_community.tools import DuckDuckGoSearchRun
18
+ from openai import OpenAI
19
 
20
+ # LangGraph imports for latest version
21
  from langgraph.graph import END, StateGraph
22
+ from langgraph.graph.message import MessageGraph
23
 
24
+ # --- Constants ---
25
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
26
+
27
+ # --- Define our tool for web search ---
28
  class SearchTools:
29
  def __init__(self):
30
  self.search_tool = DuckDuckGoSearchRun()
31
 
32
  def web_search(self, query: str) -> str:
33
+ """Search the web for information."""
34
  try:
35
  result = self.search_tool.run(query)
36
  return result
37
  except Exception as e:
38
  return f"Error searching the web: {str(e)}"
39
 
40
+ # --- Tool types and classes ---
41
+ class ToolCall(BaseModel):
42
+ name: str
43
+ input: Dict[str, Any]
44
+
45
+ class AgentState(TypedDict):
46
+ messages: List[Dict[str, Any]]
47
+ next: str
48
+
49
+ # --- LangGraph Agent ---
50
  class LangGraphAgent:
51
  def __init__(self):
52
+ print("LangGraph GPT-4 Agent initializing...")
53
  # Get API key from environment variable
54
  self.api_key = os.getenv("OPENAI_API_KEY")
55
  if not self.api_key:
56
  print("WARNING: OPENAI_API_KEY environment variable not found.")
57
  print("Please set your OpenAI API key as an environment variable or in the space secrets.")
58
+ self.client = None
59
  return
60
 
61
+ # Initialize the OpenAI client
62
+ self.client = OpenAI(api_key=self.api_key)
63
+ print("OpenAI client initialized successfully.")
 
 
 
 
 
64
 
65
  # Initialize tools
66
  self.search_tools = SearchTools()
67
+ print("Search tools initialized.")
68
 
69
+ # Build the agent graph using the latest LangGraph version
70
+ self.graph = self._build_agent_graph()
71
  print("LangGraph agent initialized successfully.")
72
 
73
+ def _build_agent_graph(self):
74
+ """Build a LangGraph agent that uses the OpenAI client directly."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  # Define the system prompt for the agent
77
  system_prompt = """You are an intelligent agent designed to answer questions from the GAIA dataset.
78
 
79
+ You have access to a web search tool to help you find accurate information when needed.
80
 
81
  IMPORTANT INSTRUCTIONS FOR FINAL ANSWERS:
82
  1. For your FINAL ANSWER, provide ONLY the exact answer - no explanations, no reasoning, no additional text.
 
88
 
89
  For your thought process, you can think step-by-step about the question, search for relevant information, and consider what would be the most accurate answer.
90
  """
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ # Define the agent function using direct OpenAI API calls
93
+ def agent(state: AgentState) -> Dict:
94
+ # Extract the messages from the state
 
 
 
 
95
  messages = state["messages"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ # Prepare messages for OpenAI API
98
+ formatted_messages = []
99
+ formatted_messages.append({"role": "system", "content": system_prompt})
100
+
101
+ for message in messages:
102
+ if message.get("role") == "user":
103
+ formatted_messages.append({"role": "user", "content": message.get("content", "")})
104
+ elif message.get("role") == "assistant":
105
+ # Handle assistant messages
106
+ if message.get("tool_calls") and len(message.get("tool_calls", [])) > 0:
107
+ # Format the tool calls for OpenAI
108
+ formatted_messages.append({
109
+ "role": "assistant",
110
+ "content": message.get("content", ""),
111
+ "tool_calls": message.get("tool_calls", [])
112
+ })
113
+ else:
114
+ formatted_messages.append({
115
+ "role": "assistant",
116
+ "content": message.get("content", "")
117
+ })
118
+ elif message.get("role") == "tool":
119
+ # Handle tool results
120
+ formatted_messages.append({
121
+ "role": "tool",
122
+ "content": message.get("content", ""),
123
+ "tool_call_id": message.get("tool_call_id", "")
124
+ })
125
+
126
+ # Define the available tools
127
+ tools = [
128
+ {
129
+ "type": "function",
130
+ "function": {
131
+ "name": "web_search",
132
+ "description": "Search the web for information about a topic",
133
+ "parameters": {
134
+ "type": "object",
135
+ "properties": {
136
+ "query": {
137
+ "type": "string",
138
+ "description": "The search query to use"
139
+ }
140
+ },
141
+ "required": ["query"]
142
+ }
143
+ }
144
+ }
145
+ ]
146
+
147
+ # Call the OpenAI API
148
+ response = self.client.chat.completions.create(
149
+ model="gpt-4-turbo",
150
+ messages=formatted_messages,
151
+ tools=tools,
152
+ tool_choice="auto",
153
+ temperature=0
154
+ )
155
+
156
+ # Get the response message
157
+ response_message = response.choices[0].message
158
+
159
+ # Create a standardized message structure
160
+ new_message = {
161
+ "role": "assistant",
162
+ "content": response_message.content or ""
163
+ }
164
+
165
+ # Check if there are tool calls
166
+ if response_message.tool_calls:
167
+ new_message["tool_calls"] = []
168
+
169
+ for tool_call in response_message.tool_calls:
170
+ new_message["tool_calls"].append({
171
+ "id": tool_call.id,
172
+ "name": tool_call.function.name,
173
+ "arguments": tool_call.function.arguments
174
+ })
175
+
176
+ # If we have tool calls, the next node should be 'action'
177
+ return {"messages": messages + [new_message], "next": "action"}
178
+
179
+ # If no tool calls, this is our final answer, so end the graph
180
+ return {"messages": messages + [new_message], "next": "end"}
181
+
182
+ # Define the action function for executing tools
183
+ def action(state: AgentState) -> Dict:
184
+ # Get the messages and find the last assistant message with tool calls
185
  messages = state["messages"]
186
+ assistant_messages = [m for m in messages if m.get("role") == "assistant" and m.get("tool_calls")]
 
187
 
188
+ if not assistant_messages:
189
+ # No tool calls found, just continue
190
+ return {"messages": messages, "next": "agent"}
191
+
192
+ # Get the last assistant message with tool calls
193
+ last_assistant_message = assistant_messages[-1]
194
+ tool_calls = last_assistant_message.get("tool_calls", [])
195
 
196
+ # Process each tool call
197
+ tool_results = []
198
+ for tool_call in tool_calls:
199
+ tool_name = tool_call.get("name")
200
+ arguments = json.loads(tool_call.get("arguments", "{}"))
201
+
202
+ # Execute the appropriate tool
203
+ if tool_name == "web_search":
204
+ query = arguments.get("query", "")
205
+ result = self.search_tools.web_search(query)
206
+ else:
207
+ result = f"Error: Unknown tool {tool_name}"
208
+
209
+ # Add the result as a tool message
210
+ tool_results.append({
211
+ "role": "tool",
212
+ "tool_call_id": tool_call.get("id"),
213
+ "content": result
214
+ })
215
 
216
+ # Add all tool results to messages
217
+ return {"messages": messages + tool_results, "next": "agent"}
 
218
 
 
 
 
219
  # Create the graph
220
  workflow = StateGraph(AgentState)
221
 
222
  # Add nodes
223
+ workflow.add_node("agent", agent)
224
+ workflow.add_node("action", action)
225
 
226
+ # Add edges
227
  workflow.add_conditional_edges(
228
  "agent",
229
+ lambda x: x["next"],
230
  {
231
+ "action": "action",
232
+ "end": END
233
+ }
 
234
  )
235
 
236
+ workflow.add_edge("action", "agent")
 
237
 
238
+ # Set the entry point
239
  workflow.set_entry_point("agent")
240
 
241
  # Compile the graph
 
244
  def __call__(self, question: str) -> str:
245
  print(f"Agent received question (first 50 chars): {question[:50]}...")
246
 
247
+ if not self.client or not self.graph:
248
  return "OpenAI API key not set. Please set the OPENAI_API_KEY as a secret in your space settings."
249
 
250
  try:
251
  # Initial state with the question
252
  initial_state = {
253
+ "messages": [{"role": "user", "content": question}],
254
+ "next": "agent"
255
  }
256
 
257
+ # Run the graph
258
+ result = self.graph.invoke(initial_state)
259
 
260
+ # Extract the final answer from the result
261
+ messages = result["messages"]
262
  if not messages:
263
  return "No response generated."
264
 
265
+ # Find all assistant messages without tool calls (these are response messages)
266
+ final_messages = [m for m in messages if m.get("role") == "assistant" and not m.get("tool_calls")]
267
  if not final_messages:
268
+ return "No final answer found."
269
 
270
+ # Get the content from the last assistant message
271
+ raw_answer = final_messages[-1].get("content", "")
272
 
273
  # Clean up the answer
274
  answer = raw_answer.strip()
 
296
  print(f"Error while processing question: {e}")
297
  return f"Error processing question: {str(e)}"
298
 
299
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
300
  """
301
+ Fetches all questions, runs the Agent on them, submits all answers,
302
  and displays the results.
303
  """
304
  # --- Determine HF Space Runtime URL and Repo URL ---
 
315
  questions_url = f"{api_url}/questions"
316
  submit_url = f"{api_url}/submit"
317
 
318
+ # 1. Instantiate Agent
319
  try:
320
  agent = LangGraphAgent()
321
  except Exception as e:
322
  print(f"Error instantiating agent: {e}")
323
  return f"Error initializing agent: {e}", None
324
+ # In the case of an app running as a hugging Face space, this link points toward your codebase
325
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
326
  print(agent_code)
327
 
 
440
  run_button = gr.Button("Run Evaluation & Submit All Answers")
441
 
442
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
443
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
444
 
445
  run_button.click(