kylea commited on
Commit
01aa6b9
·
1 Parent(s): c4256c5

inital agent build

Browse files
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .env
app.py CHANGED
@@ -3,10 +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
  def run_and_submit_all( profile: gr.OAuthProfile | None):
12
  """
@@ -29,7 +35,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
29
 
30
  # 1. Instantiate Agent ( modify this part to create your agent)
31
  try:
32
- agent = BasicAgent()
33
  except Exception as e:
34
  print(f"Error instantiating agent: {e}")
35
  return f"Error initializing agent: {e}", None
@@ -69,64 +75,71 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
69
  print(f"Skipping item with missing task_id or question: {item}")
70
  continue
71
  try:
72
- submitted_answer = agent(question_text)
 
 
73
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
74
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
75
  except Exception as e:
76
  print(f"Error running agent on task {task_id}: {e}")
77
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
 
78
 
79
  if not answers_payload:
80
  print("Agent did not produce any answers to submit.")
81
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
 
 
82
 
83
  # 4. Prepare Submission
84
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
85
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
86
- print(status_update)
87
-
88
- # 5. Submit
89
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
90
- try:
91
- response = requests.post(submit_url, json=submission_data, timeout=60)
92
- response.raise_for_status()
93
- result_data = response.json()
94
- final_status = (
95
- f"Submission Successful!\n"
96
- f"User: {result_data.get('username')}\n"
97
- f"Overall Score: {result_data.get('score', 'N/A')}% "
98
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
99
- f"Message: {result_data.get('message', 'No message received.')}"
100
- )
101
- print("Submission successful.")
102
- results_df = pd.DataFrame(results_log)
103
- return final_status, results_df
104
- except requests.exceptions.HTTPError as e:
105
- error_detail = f"Server responded with status {e.response.status_code}."
106
- try:
107
- error_json = e.response.json()
108
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
109
- except requests.exceptions.JSONDecodeError:
110
- error_detail += f" Response: {e.response.text[:500]}"
111
- status_message = f"Submission Failed: {error_detail}"
112
- print(status_message)
113
- results_df = pd.DataFrame(results_log)
114
- return status_message, results_df
115
- except requests.exceptions.Timeout:
116
- status_message = "Submission Failed: The request timed out."
117
- print(status_message)
118
- results_df = pd.DataFrame(results_log)
119
- return status_message, results_df
120
- except requests.exceptions.RequestException as e:
121
- status_message = f"Submission Failed: Network error - {e}"
122
- print(status_message)
123
- results_df = pd.DataFrame(results_log)
124
- return status_message, results_df
125
- except Exception as e:
126
- status_message = f"An unexpected error occurred during submission: {e}"
127
- print(status_message)
128
- results_df = pd.DataFrame(results_log)
129
- return status_message, results_df
130
 
131
 
132
  # --- Build Gradio Interface using Blocks ---
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from dotenv import load_dotenv
7
+
8
+ from langchain_core.messages import HumanMessage
9
+
10
+ from src.gaia_agent import GaiaAgent
11
 
12
  # (Keep Constants as is)
13
  # --- Constants ---
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
+ load_dotenv() # Load environment variables from .env file
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 = GaiaAgent()
39
  except Exception as e:
40
  print(f"Error instantiating agent: {e}")
41
  return f"Error initializing agent: {e}", None
 
75
  print(f"Skipping item with missing task_id or question: {item}")
76
  continue
77
  try:
78
+ print(f"Running agent on task {task_id}: {question_text}")
79
+ submitted_answer = agent.graph.invoke({"messages": [HumanMessage(content=question_text)]})
80
+ print(submitted_answer)
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
+
87
+ break
88
 
89
  if not answers_payload:
90
  print("Agent did not produce any answers to submit.")
91
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
92
+
93
+ print(f"Agent finished running on {len(answers_payload)} questions.")
94
+ print(f"Results log: {results_log}")
95
 
96
  # 4. Prepare Submission
97
+ # submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
98
+ # status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
99
+ # print(status_update)
100
+
101
+ # # 5. Submit
102
+ # print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
103
+ # try:
104
+ # response = requests.post(submit_url, json=submission_data, timeout=60)
105
+ # response.raise_for_status()
106
+ # result_data = response.json()
107
+ # final_status = (
108
+ # f"Submission Successful!\n"
109
+ # f"User: {result_data.get('username')}\n"
110
+ # f"Overall Score: {result_data.get('score', 'N/A')}% "
111
+ # f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
112
+ # f"Message: {result_data.get('message', 'No message received.')}"
113
+ # )
114
+ # print("Submission successful.")
115
+ # results_df = pd.DataFrame(results_log)
116
+ # return final_status, results_df
117
+ # except requests.exceptions.HTTPError as e:
118
+ # error_detail = f"Server responded with status {e.response.status_code}."
119
+ # try:
120
+ # error_json = e.response.json()
121
+ # error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
122
+ # except requests.exceptions.JSONDecodeError:
123
+ # error_detail += f" Response: {e.response.text[:500]}"
124
+ # status_message = f"Submission Failed: {error_detail}"
125
+ # print(status_message)
126
+ # results_df = pd.DataFrame(results_log)
127
+ # return status_message, results_df
128
+ # except requests.exceptions.Timeout:
129
+ # status_message = "Submission Failed: The request timed out."
130
+ # print(status_message)
131
+ # results_df = pd.DataFrame(results_log)
132
+ # return status_message, results_df
133
+ # except requests.exceptions.RequestException as e:
134
+ # status_message = f"Submission Failed: Network error - {e}"
135
+ # print(status_message)
136
+ # results_df = pd.DataFrame(results_log)
137
+ # return status_message, results_df
138
+ # except Exception as e:
139
+ # status_message = f"An unexpected error occurred during submission: {e}"
140
+ # print(status_message)
141
+ # results_df = pd.DataFrame(results_log)
142
+ # return status_message, results_df
143
 
144
 
145
  # --- Build Gradio Interface using Blocks ---
gaia_agent.py DELETED
@@ -1,3 +0,0 @@
1
- class GaiaAgent:
2
- def __init__(self):
3
- pass
 
 
 
 
model.py DELETED
File without changes
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
  gradio
2
  requests
3
- langchain
 
 
 
 
 
1
  gradio
2
  requests
3
+ python-dotenv
4
+ langchain
5
+ langchain-google-genai
6
+ langchain-tavily
7
+ langgraph
src/__pycache__/config.cpython-310.pyc ADDED
Binary file (2.47 kB). View file
 
src/__pycache__/gaia_agent.cpython-310.pyc ADDED
Binary file (2.39 kB). View file
 
src/__pycache__/model.cpython-310.pyc ADDED
Binary file (888 Bytes). View file
 
src/__pycache__/prompts.cpython-310.pyc ADDED
Binary file (398 Bytes). View file
 
src/__pycache__/state.cpython-310.pyc ADDED
Binary file (1.48 kB). View file
 
src/__pycache__/tools.cpython-310.pyc ADDED
Binary file (989 Bytes). View file
 
src/config.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Define the configurable parameters for the agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field, fields
6
+ from typing import Annotated
7
+
8
+ from langchain_core.runnables import ensure_config
9
+ from langgraph.config import get_config
10
+
11
+ from src.prompts import SYSTEM_PROMPT
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class Configuration:
16
+ """The configuration for the agent."""
17
+
18
+ system_prompt: str = field(
19
+ default=SYSTEM_PROMPT,
20
+ metadata={
21
+ "description": "The system prompt to use for the agent's interactions. "
22
+ "This prompt sets the context and behavior for the agent."
23
+ },
24
+ )
25
+
26
+ google_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
27
+ default="gemini-2.0-flash",
28
+ metadata={
29
+ "description": "The name of the Google AI language model to use for the agent's main interactions. "
30
+ "Should be in the form: model-name."
31
+ },
32
+ )
33
+
34
+ max_iter: int = field(
35
+ default=5,
36
+ metadata={
37
+ "description": "The maximum number of iterations to run."
38
+ },
39
+ )
40
+
41
+ max_search_results: int = field(
42
+ default=5,
43
+ metadata={
44
+ "description": "The maximum number of search results to return for each search query."
45
+ },
46
+ )
47
+
48
+ temperature: float = field(
49
+ default=0.2,
50
+ metadata={
51
+ "description": "The temperature to use for the model's responses. "
52
+ "Higher values result in more random outputs, while lower values make the output more deterministic."
53
+ },
54
+ )
55
+
56
+ @classmethod
57
+ def from_context(cls) -> Configuration:
58
+ """Create a Configuration instance from a RunnableConfig object."""
59
+ try:
60
+ config = get_config()
61
+ except RuntimeError:
62
+ config = None
63
+ config = ensure_config(config)
64
+ configurable = config.get("configurable") or {}
65
+ _fields = {f.name for f in fields(cls) if f.init}
66
+ return cls(**{k: v for k, v in configurable.items() if k in _fields})
src/gaia_agent.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, cast
2
+
3
+ from langchain_core.messages import AIMessage
4
+ from langgraph.graph import StateGraph
5
+ from langgraph.prebuilt import ToolNode, tools_condition
6
+
7
+ from src.config import Configuration
8
+ from src.model import GoogleModel
9
+ from src.state import InputState, State
10
+ from src.tools import TOOLS
11
+
12
+ class GaiaAgent:
13
+ def __init__(self):
14
+ self.graph = self._build_graph()
15
+
16
+ def _build_graph(self) -> StateGraph:
17
+ builder = StateGraph(State, input=InputState, config_schema=Configuration)
18
+
19
+ # Define the two nodes we will cycle between
20
+ builder.add_node("call_model", self._call_model)
21
+ builder.add_node("tools", ToolNode(TOOLS))
22
+
23
+ # Set the entrypoint as `call_model`
24
+ # This means that this node is the first one called
25
+ builder.add_edge("__start__", "call_model")
26
+ builder.add_conditional_edges(
27
+ "call_model",
28
+ # If the latest message requires a tool, route to tools
29
+ # Otherwise, provide a direct response
30
+ tools_condition,
31
+ )
32
+ builder.add_edge("tools", "call_model")
33
+
34
+ graph = builder.compile(name="GAIA Agent", debug=True)
35
+
36
+ return graph
37
+
38
+ def _call_model(self, state: State) -> Dict[str, List[AIMessage]]:
39
+ """Call the LLM powering our "agent".
40
+
41
+ This function prepares the prompt, initializes the model, and processes the response.
42
+
43
+ Args:
44
+ state (State): The current state of the conversation.
45
+ config (RunnableConfig): Configuration for the model run.
46
+
47
+ Returns:
48
+ dict: A dictionary containing the model's response message.
49
+ """
50
+ configuration = Configuration.from_context()
51
+
52
+ # Initialize the model with tool binding. Change the model or add more tools here.
53
+ model = GoogleModel(
54
+ model=configuration.google_model,
55
+ temperature=configuration.temperature,
56
+ tools=TOOLS
57
+ )
58
+
59
+ # Format the system prompt. Customize this to change the agent's behavior.
60
+ system_message = configuration.system_prompt
61
+
62
+ # Get the model's response
63
+ response = cast(
64
+ AIMessage,
65
+ model.llm.invoke(
66
+ [{"role": "system", "content": system_message}, *state.messages]
67
+ ),
68
+ )
69
+
70
+ print(response.tool_calls)
71
+
72
+ # Handle the case when it's the last step and the model still wants to use a tool
73
+ if state.is_last_step and response.tool_calls:
74
+ return {
75
+ "messages": [
76
+ AIMessage(
77
+ id=response.id,
78
+ content="Sorry, I could not find an answer to your question in the specified number of steps.",
79
+ )
80
+ ]
81
+ }
82
+
83
+ # Return the model's response as a list to be added to existing messages
84
+ return {"messages": [response]}
src/model.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_google_genai import ChatGoogleGenerativeAI
2
+
3
+ class GoogleModel:
4
+ def __init__(
5
+ self,
6
+ model: str = "gemini-2.0-flash",
7
+ temperature: int = 0.2,
8
+ tools: list = None,
9
+ ):
10
+ """Initialize the GoogleModel with the specified model name and temperature.
11
+ """
12
+
13
+ if tools:
14
+ self.llm = ChatGoogleGenerativeAI(
15
+ model=model,
16
+ temperature=temperature,
17
+ max_tokens=None,
18
+ timeout=None,
19
+ max_retries=1,
20
+ ).bind_tools(tools=tools)
21
+ else:
22
+ self.llm = ChatGoogleGenerativeAI(
23
+ model=model,
24
+ temperature=temperature,
25
+ max_tokens=None,
26
+ timeout=None,
27
+ max_retries=1,
28
+ )
src/prompts.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ SYSTEM_PROMPT = (
2
+ "You are a helpful AI assistant.\n"
3
+ "Please answer the question to the best of your ability.\n"
4
+ "Use the tools provided to you to find the answer.\n"
5
+ "Do not ask for permission to use the tools.\n"
6
+ "If you think you should use a tool, do so.\n"
7
+ )
src/state.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Define the state structures for the agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Sequence
7
+
8
+ from langchain_core.messages import AnyMessage
9
+ from langgraph.graph import add_messages
10
+ from langgraph.managed import IsLastStep
11
+ from typing_extensions import Annotated
12
+
13
+
14
+ @dataclass
15
+ class InputState:
16
+ """Defines the input state for the agent, representing a narrower interface to the outside world.
17
+
18
+ This class is used to define the initial state and structure of incoming data.
19
+ """
20
+
21
+ messages: Annotated[Sequence[AnyMessage], add_messages] = field(
22
+ default_factory=list
23
+ )
24
+ """
25
+ Messages tracking the primary execution state of the agent.
26
+
27
+ Typically accumulates a pattern of:
28
+ 1. HumanMessage - user input
29
+ 2. AIMessage with .tool_calls - agent picking tool(s) to use to collect information
30
+ 3. ToolMessage(s) - the responses (or errors) from the executed tools
31
+ 4. AIMessage without .tool_calls - agent responding in unstructured format to the user
32
+ 5. HumanMessage - user responds with the next conversational turn
33
+
34
+ Steps 2-5 may repeat as needed.
35
+
36
+ The `add_messages` annotation ensures that new messages are merged with existing ones,
37
+ updating by ID to maintain an "append-only" state unless a message with the same ID is provided.
38
+ """
39
+
40
+
41
+ @dataclass
42
+ class State(InputState):
43
+ """Represents the complete state of the agent, extending InputState with additional attributes.
44
+
45
+ This class can be used to store any information needed throughout the agent's lifecycle.
46
+ """
47
+
48
+ is_last_step: IsLastStep = field(default=False)
49
+ """
50
+ Indicates whether the current step is the last one before the graph raises an error.
51
+
52
+ This is a 'managed' variable, controlled by the state machine rather than user code.
53
+ It is set to 'True' when the step count reaches recursion_limit - 1.
54
+ """
55
+
56
+ # Additional attributes can be added here as needed.
57
+ # Common examples include:
58
+ # retrieved_documents: List[Document] = field(default_factory=list)
59
+ # extracted_entities: Dict[str, Any] = field(default_factory=dict)
60
+ # api_connections: Dict[str, Any] = field(default_factory=dict)
src/tools.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Callable, List, Optional, cast
2
+
3
+ from langchain_tavily import TavilySearch # type: ignore[import-not-found]
4
+
5
+ from src.config import Configuration
6
+
7
+ def search(query: str) -> Optional[dict[str, Any]]:
8
+ """Search for general web results.
9
+
10
+ This function performs a search using the Tavily search engine, which is designed
11
+ to provide comprehensive, accurate, and trusted results. It's particularly useful
12
+ for answering questions about current events.
13
+ """
14
+ configuration = Configuration.from_context()
15
+ wrapped = TavilySearch(max_results=configuration.max_search_results)
16
+ return cast(dict[str, Any], wrapped.invoke({"query": query}))
17
+
18
+
19
+ TOOLS: List[Callable[..., Any]] = [search]