mchinea commited on
Commit
a92d3ed
·
1 Parent(s): 81917a3

add my agent and tools

Browse files
Files changed (3) hide show
  1. agent.py +55 -0
  2. app.py +5 -3
  3. tools.py +203 -0
agent.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LangGraph Agent"""
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ from langchain_openai import ChatOpenAI
6
+
7
+ from langgraph.graph import START, StateGraph
8
+ from langgraph.prebuilt import tools_condition, ToolNode
9
+ from langgraph.graph import START, StateGraph, MessagesState
10
+ from langchain_core.messages import SystemMessage, HumanMessage
11
+
12
+ from tools import level1_tools
13
+
14
+ load_dotenv()
15
+
16
+ # Build graph function
17
+ def build_agent_graph():
18
+ """Build the graph"""
19
+ # Load environment variables from .env file
20
+ llm = ChatOpenAI(model="gpt-4o-mini")
21
+
22
+ # Bind tools to LLM
23
+ llm_with_tools = llm.bind_tools(level1_tools)
24
+
25
+ # Node
26
+ def assistant(state: MessagesState):
27
+ """Assistant node"""
28
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
29
+
30
+
31
+ builder = StateGraph(MessagesState)
32
+ builder.add_node("assistant", assistant)
33
+ builder.add_node("tools", ToolNode(level1_tools))
34
+ builder.add_edge(START, "assistant")
35
+ builder.add_conditional_edges(
36
+ "assistant",
37
+ tools_condition,
38
+ )
39
+ builder.add_edge("tools", "assistant")
40
+
41
+ # Compile graph
42
+ return builder.compile()
43
+
44
+
45
+ # test
46
+ if __name__ == "__main__":
47
+ question1 = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)?"
48
+ question2 = "Convert 10 miles to kilometers."
49
+ # Build the graph
50
+ graph = build_agent_graph()
51
+ # Run the graph
52
+ messages = [HumanMessage(content=question1)]
53
+ messages = graph.invoke({"messages": messages})
54
+ for m in messages["messages"]:
55
+ m.pretty_print()
app.py CHANGED
@@ -4,15 +4,17 @@ 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."
@@ -40,7 +42,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
 
4
  import inspect
5
  import pandas as pd
6
 
7
+ from agent import build_agent_graph
8
  # (Keep Constants as is)
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
12
  # --- Basic Agent Definition ---
13
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
14
+ class MyAgent:
15
  def __init__(self):
16
+ print("MyAgent initialized.")
17
+ self.graph = build_agent_graph()
18
  def __call__(self, question: str) -> str:
19
  print(f"Agent received question (first 50 chars): {question[:50]}...")
20
  fixed_answer = "This is a default answer."
 
42
 
43
  # 1. Instantiate Agent ( modify this part to create your agent)
44
  try:
45
+ agent = MyAgent()
46
  except Exception as e:
47
  print(f"Error instantiating agent: {e}")
48
  return f"Error initializing agent: {e}", None
tools.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+
4
+ from typing import Dict
5
+ from pathlib import Path
6
+
7
+ from langchain_core.tools import tool
8
+
9
+ from langchain_core.messages import ToolMessage
10
+ from langchain_tavily import TavilySearch
11
+ from langchain_community.document_loaders import WikipediaLoader
12
+ from langchain_community.document_loaders import ArxivLoader
13
+
14
+ @tool
15
+ def web_search(query: str) -> ToolMessage:
16
+ """Search in the web with Tavily for a query and return maximum 5 results.
17
+ Args:
18
+ query: The search query.
19
+ Returns:
20
+ Tavily output, and snippet for the top 5 results
21
+ """
22
+ return TavilySearch(max_results=5, include_images=False).invoke({"query": query})
23
+
24
+
25
+ @tool
26
+ def wikipedia_search(query: str) -> Dict[str, list]:
27
+ """Search Wikipedia for a given query and return the first 5 results.
28
+ Args:
29
+ query: The search term or topic.
30
+ Returns:
31
+ A dictionary containing the formatted Wikipedia results.
32
+ """
33
+ search_docs = WikipediaLoader(query=query, load_max_docs=5).load()
34
+ formatted_search_docs = "\n\n---\n\n".join(
35
+ [
36
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
37
+ for doc in search_docs
38
+ ]
39
+ )
40
+ return {"wiki_results": formatted_search_docs}
41
+
42
+
43
+ #Mathematical tools
44
+ @tool
45
+ def multiply(a: float, b: float) -> float:
46
+ """Multiply two numbers.
47
+ Args:
48
+ a: first number
49
+ b: second number
50
+ Returns:
51
+ Multiplication result
52
+ """
53
+ return a * b
54
+
55
+
56
+ @tool
57
+ def add(a: float, b: float) -> float:
58
+ """Add two numbers.
59
+ Args:
60
+ a: first number
61
+ b: second number
62
+ Returns:
63
+ Addition result
64
+ """
65
+ return a + b
66
+
67
+
68
+ @tool
69
+ def subtract(a: float, b: float) -> float:
70
+ """Subtract two numbers.
71
+ Args:
72
+ a: first number
73
+ b: second number
74
+ Returns:
75
+ Subtraction result
76
+ """
77
+ return a - b
78
+
79
+
80
+ @tool
81
+ def divide(a: float, b: float) -> float:
82
+ """Divide two numbers.
83
+ Args:
84
+ a: first number
85
+ b: second number
86
+ Returns:
87
+ Division result
88
+ """
89
+ if b == 0:
90
+ raise ValueError("Cannot divide by zero.")
91
+ return a / b
92
+
93
+
94
+ @tool
95
+ def modulus(a: int, b: int) -> int:
96
+ """Get the modulus of two numbers.
97
+ Args:
98
+ a: first number
99
+ b: second number
100
+ Returns:
101
+ Modulus result
102
+ """
103
+ return a % b
104
+
105
+ from langchain_core.tools import tool
106
+
107
+
108
+ @tool
109
+ def convert_units(value: float, from_unit: str, to_unit: str) -> float:
110
+ """
111
+ Converts a value from one unit to another.
112
+
113
+ Args:
114
+ value: The numerical value to convert.
115
+ from_unit: The original unit (e.g. 'miles', 'kg', 'celsius').
116
+ to_unit: The target unit (e.g. 'kilometers', 'lb', 'fahrenheit').
117
+
118
+ Supported conversions:
119
+ - miles <-> kilometers
120
+ - kilograms <-> pounds
121
+ - celsius <-> fahrenheit
122
+
123
+ Returns:
124
+ The converted value result.
125
+ """
126
+ conversions = {
127
+ ("miles", "kilometers"): lambda v: v * 1.60934,
128
+ ("kilometers", "miles"): lambda v: v / 1.60934,
129
+ ("kilograms", "pounds"): lambda v: v * 2.20462,
130
+ ("pounds", "kilograms"): lambda v: v / 2.20462,
131
+ ("celsius", "fahrenheit"): lambda v: (v * 9/5) + 32,
132
+ ("fahrenheit", "celsius"): lambda v: (v - 32) * 5/9,
133
+ }
134
+
135
+ key = (from_unit.lower(), to_unit.lower())
136
+ if key not in conversions:
137
+ raise ValueError(f"Conversion from {from_unit} to {to_unit} not supported.")
138
+
139
+ return conversions[key](value)
140
+
141
+ @tool
142
+ def query_table_data(file_path: str, query: str, sheet_name: str = None) -> str:
143
+ """
144
+ Loads a table from CSV or Excel and filters it using a pandas query.
145
+
146
+ Args:
147
+ file_path: Path to the table file (.xlsx, .xls).
148
+ query: A pandas-compatible query string, e.g., "Age > 30 and Country == 'USA'".
149
+ sheet_name: Optional sheet name if the file is Excel.
150
+
151
+ Returns:
152
+ A string representation (markdown) of the filtered table (max 10 rows).
153
+ """
154
+ try:
155
+ import pandas as pd
156
+ path = Path(file_path)
157
+ if not path.exists():
158
+ raise FileNotFoundError(f"File not found: {file_path}")
159
+ ext = path.suffix.lower()
160
+ if ext == ".csv":
161
+ df = pd.read_csv(path)
162
+ elif ext in [".xlsx", ".xls"]:
163
+ df = pd.read_excel(path, sheet_name=sheet_name)
164
+ else:
165
+ raise ValueError(f"Unsupported file extension: {ext}")
166
+ try:
167
+ filtered_df = df.query(query)
168
+ return filtered_df.head(10).to_markdown(index=False)
169
+ except Exception as e:
170
+ raise ValueError(f"Invalid query: {query}. Error: {e}")
171
+ except ImportError:
172
+ return "Error: pandas and openpyxl are not installed. Please install them with 'pip install pandas openpyxl'."
173
+
174
+
175
+ @tool
176
+ def arvix_search(query: str) -> str:
177
+ """Search Arxiv for a query and return maximum 5 result.
178
+ Args:
179
+ query: The search query.
180
+ Returns:
181
+ A dictionary containing the formatted Arvix results, and snippet for the top 5 results.
182
+ """
183
+ search_docs = ArxivLoader(query=query, load_max_docs=5).load()
184
+ formatted_search_docs = "\n\n---\n\n".join(
185
+ [
186
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
187
+ for doc in search_docs
188
+ ])
189
+ return {"arvix_results": formatted_search_docs}
190
+
191
+
192
+ level1_tools = [
193
+ multiply,
194
+ add,
195
+ subtract,
196
+ divide,
197
+ modulus,
198
+ wikipedia_search,
199
+ web_search,
200
+ arvix_search,
201
+ convert_units,
202
+ query_table_data
203
+ ]