Alejandro Ardila commited on
Commit
668ba52
·
1 Parent(s): 81917a3

Added first PoC with basic Tools

Browse files
.gitignore ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # PyCharm
132
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
133
+ # be added to the global gitignore or merged into this project gitignore. For a PyCharm
134
+ # project, it is recommended to include the following files:
135
+ #*.iws
136
+ #*.iml
137
+ #*.ipr
138
+
139
+ # IntelliJ
140
+ .idea/
141
+
142
+ # VSCode
143
+ .vscode/
144
+
145
+ # Vim
146
+ *.swp
147
+ *.swo
148
+ *~
149
+
150
+ # Emacs
151
+ *~
152
+ \#*\#
153
+ /.emacs.desktop
154
+ /.emacs.desktop.lock
155
+ *.elc
156
+ auto-save-list
157
+ tramp
158
+ .\#*
159
+
160
+ # OS generated files
161
+ .DS_Store
162
+ .DS_Store?
163
+ ._*
164
+ .Spotlight-V100
165
+ .Trashes
166
+ ehthumbs.db
167
+ Thumbs.db
168
+
169
+ # AI/ML specific
170
+ *.h5
171
+ *.hdf5
172
+ *.pkl
173
+ *.pickle
174
+ *.joblib
175
+ *.model
176
+ *.weights
177
+ models/
178
+ checkpoints/
179
+ logs/
180
+ tensorboard_logs/
181
+ wandb/
182
+ mlruns/
183
+
184
+ # Data files
185
+ data/
186
+ datasets/
187
+ *.csv
188
+ *.json
189
+ *.parquet
190
+ *.feather
191
+ *.xlsx
192
+ *.xls
193
+
194
+ # API keys and secrets
195
+ .env.local
196
+ .env.development.local
197
+ .env.test.local
198
+ .env.production.local
199
+ secrets.json
200
+ config.json
201
+ credentials.json
202
+
203
+ # LangGraph specific
204
+ .langgraph/
205
+ langgraph_data/
206
+ .langgraph_api/
207
+
208
+ # Temporary files
209
+ tmp/
210
+ temp/
211
+ *.tmp
212
+ *.temp
app.py CHANGED
@@ -3,22 +3,11 @@ 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
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from agent.basic_agent import BasicAgent
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
  """
13
  Fetches all questions, runs the BasicAgent on them, submits all answers,
requirements.txt CHANGED
@@ -1,2 +1,25 @@
1
  gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  gradio
2
+ requests
3
+ langgraph
4
+ langchain_core
5
+ pandas
6
+ langchain_community
7
+ pydantic
8
+ langchain-openai
9
+ python-dotenv
10
+ langgraph-prebuilt
11
+ langgraph-sdk
12
+ langgraph-checkpoint-sqlite
13
+ langsmith
14
+ notebook
15
+ tavily-python
16
+ wikipedia
17
+ trustcall
18
+ langgraph-cli[inmem]
19
+ duckduckgo-search
20
+ debugpy
21
+ arxiv
22
+ pymupdf
23
+ smolagents
24
+ datetime
25
+ python-chess
src/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # src package
src/agent/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # agent package
src/agent/basic_agent.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Basic Agent Definition ---
2
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
3
+ from langchain_openai import ChatOpenAI
4
+ from langgraph.graph import StateGraph, START, MessagesState
5
+ import requests
6
+ from langgraph.prebuilt import ToolNode, tools_condition
7
+ from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
8
+ import os
9
+ from dotenv import load_dotenv
10
+ from .tools import tools
11
+ from typing import Any
12
+
13
+ load_dotenv()
14
+
15
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
+
17
+ class BasicAgent:
18
+ def __init__(self):
19
+ # Load environment variables from .env file
20
+ self.model = ChatOpenAI(model_name="gpt-4o", temperature=0.0, api_key=os.getenv("OPENAI_API_KEY"))
21
+ with open("src/prompts/system.txt", "r") as file:
22
+ self.system_prompt = file.read()
23
+
24
+ builder = StateGraph(MessagesState)
25
+ builder.add_node("assistant", self.assistant)
26
+ builder.add_node("tools", ToolNode(tools=tools))
27
+ builder.add_edge(START, "assistant")
28
+ builder.add_conditional_edges("assistant", tools_condition)
29
+ builder.add_edge("tools", "assistant")
30
+ self.graph = builder.compile()
31
+ print("BasicAgent initialized.")
32
+
33
+ def __call__(self, question: str) -> MessagesState:
34
+ print(f"Agent received question...")
35
+ message = HumanMessage(content=question)
36
+ answer = self.graph.invoke({"messages": [message]})
37
+ last_message = answer["messages"][-1]
38
+ if isinstance(last_message, AIMessage) and "FINAL ANSWER:" in last_message.content:
39
+ content = last_message.content
40
+ # Find the position after "FINAL ANSWER: " and return only the text after it
41
+ final_answer_pos = content.find("FINAL ANSWER:") + len("FINAL ANSWER:")
42
+ return content[final_answer_pos:].strip()
43
+
44
+ return "I'm sorry, I can't answer that question."
45
+
46
+ def download_file(self, task_id: str) -> Any:
47
+ api_url = DEFAULT_API_URL
48
+ file_url = f"{api_url}/files/{task_id}"
49
+ response = requests.get(file_url)
50
+ return response.content
51
+
52
+ def assistant(self, state: MessagesState):
53
+ """
54
+ This function takes a question and returns a list of sub-tasks.
55
+ """
56
+ sys_msg = SystemMessage(content=self.system_prompt)
57
+ llm_with_tools = self.model.bind_tools(tools, parallel_tool_calls=False)
58
+ state["messages"] = [sys_msg] + state["messages"]
59
+ return { "messages": [llm_with_tools.invoke(state["messages"])] }
src/agent/tools.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .tools import tools, math_tools, web_tools, coding_tools
2
+
3
+ # Re-export for backward compatibility
4
+ __all__ = ['tools', 'math_tools', 'web_tools', 'coding_tools']
src/agent/tools/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from .math_tools import math_tools
2
+ from .web_tools import web_tools
3
+
4
+ # Consolidate all tools into a single exportable array
5
+ tools = web_tools + math_tools
6
+
7
+ # For backward compatibility, also export individual tool categories
8
+ __all__ = ['tools', 'math_tools', 'web_tools']
src/agent/tools/coding_tools.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from smolagents import CodeAgent, HfApiModel
3
+
4
+
5
+ imports = ['datetime', 'pandas', 'numpy', 'python-chess', 'set', 'math']
6
+ agent = CodeAgent(tools=[], model=HfApiModel(), additional_authorized_imports=imports, max_steps=10,)
7
+
8
+ @tool
9
+ def code_agent(question: str) -> str:
10
+ """
11
+ A tool that can be used to code a solution to a problem.
12
+ Args:
13
+ question (str): The question to code a solution to.
14
+ """
15
+ return agent.run(question)
src/agent/tools/math_tools.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cmath
2
+ from langchain_core.tools import tool
3
+
4
+
5
+ @tool
6
+ def multiply(a: float, b: float) -> float:
7
+ """
8
+ Multiplies two numbers.
9
+ Args:
10
+ a (float): the first number
11
+ b (float): the second number
12
+ """
13
+ return a * b
14
+
15
+
16
+ @tool
17
+ def add(a: float, b: float) -> float:
18
+ """
19
+ Adds two numbers.
20
+ Args:
21
+ a (float): the first number
22
+ b (float): the second number
23
+ """
24
+ return a + b
25
+
26
+
27
+ @tool
28
+ def subtract(a: float, b: float) -> int:
29
+ """
30
+ Subtracts two numbers.
31
+ Args:
32
+ a (float): the first number
33
+ b (float): the second number
34
+ """
35
+ return a - b
36
+
37
+
38
+ @tool
39
+ def divide(a: float, b: float) -> float:
40
+ """
41
+ Divides two numbers.
42
+ Args:
43
+ a (float): the first float number
44
+ b (float): the second float number
45
+ """
46
+ if b == 0:
47
+ raise ValueError("Cannot divided by zero.")
48
+ return a / b
49
+
50
+
51
+ @tool
52
+ def modulus(a: int, b: int) -> int:
53
+ """
54
+ Get the modulus of two numbers.
55
+ Args:
56
+ a (int): the first number
57
+ b (int): the second number
58
+ """
59
+ return a % b
60
+
61
+
62
+ @tool
63
+ def power(a: float, b: float) -> float:
64
+ """
65
+ Get the power of two numbers.
66
+ Args:
67
+ a (float): the first number
68
+ b (float): the second number
69
+ """
70
+ return a**b
71
+
72
+
73
+ @tool
74
+ def square_root(a: float) -> float | complex:
75
+ """
76
+ Get the square root of a number.
77
+ Args:
78
+ a (float): the number to get the square root of
79
+ """
80
+ if a >= 0:
81
+ return a**0.5
82
+ return cmath.sqrt(a)
83
+
84
+
85
+ # Export all math tools
86
+ math_tools = [multiply, add, subtract, divide, modulus, power, square_root]
src/agent/tools/web_tools.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
3
+ from langchain_community.tools import TavilySearchResults
4
+
5
+
6
+ @tool
7
+ def wiki_search(query: str) -> str:
8
+ """Search Wikipedia for a query and return maximum 2 results.
9
+ Args:
10
+ query: The search query."""
11
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
12
+ formatted_search_docs = "\n\n---\n\n".join(
13
+ [
14
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
15
+ for doc in search_docs
16
+ ]
17
+ )
18
+ return formatted_search_docs
19
+
20
+
21
+ @tool
22
+ def web_search(query: str) -> str:
23
+ """Search Tavily for a query and return maximum 3 results.
24
+ Args:
25
+ query: The search query."""
26
+ search_docs = TavilySearchResults(max_results=3).invoke({"query": query})
27
+ formatted_search_docs = "\n\n---\n\n".join(
28
+ [
29
+ f'<Document source="{doc.get("url", "Unknown")}" title="{doc.get("title", "")}"/>\n{doc.get("content", "")}\n</Document>'
30
+ for doc in search_docs
31
+ ]
32
+ )
33
+ return formatted_search_docs
34
+
35
+
36
+ @tool
37
+ def scientific_paper_search(query: str) -> str:
38
+ """Search Arxiv for scientific papers and publications and return maximum 3 results.
39
+ Args:
40
+ query: The search query."""
41
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
42
+ formatted_search_docs = "\n\n---\n\n".join(
43
+ [
44
+ f'<Document title="{doc.metadata.get("Title", "")}" authors="{doc.metadata.get("Authors", "")}" published="{doc.metadata.get("Published", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
45
+ for doc in search_docs
46
+ ]
47
+ )
48
+ return formatted_search_docs
49
+
50
+
51
+ # Export all web tools
52
+ web_tools = [web_search, scientific_paper_search]
src/prompts/system.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant that answers questions that requires one or multiple tools to answer.
2
+ You are given a question and sometimes a file. You will finish your answer with the following template:
3
+ <template>
4
+ FINAL ANSWER: <your final answer>
5
+ </template>
6
+ You need to use the file to answer the question if it is provided.
7
+ Your FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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.
8
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
9
+
10
+ Before selecting a tool, ALWAYS plan and decompose the question into sub-tasks. This will help you to select all the tools necessary to give a more accurate answer.
11
+
12
+ You have a suite of tools available:
13
+ <tools>
14
+ - `web_tools`:
15
+ - web_search: For searching the web
16
+ - scientific_paper_search: For searching for scientific papers or articles
17
+ - `math_tools`: Contains a suite of basic algebraic tools
18
+ - `coding_tools`: For executing especific code. This is general purpose but also useful for analyzing .xlsx or .csv data, and also it has a chess engine.
19
+ </tools>
test_agent.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for the BasicAgent
4
+ """
5
+ import sys
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ # Load environment variables from .env file
10
+ load_dotenv()
11
+
12
+ # Add the project root to the Python path
13
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
+
15
+ from src.agent.basic_agent import BasicAgent
16
+
17
+ agent = BasicAgent()
18
+ graph = agent.graph
19
+
20
+ def main():
21
+ print("Testing BasicAgent...")
22
+
23
+ # Create the agent
24
+ agent = BasicAgent()
25
+
26
+ # Run the agent
27
+ answer = agent("On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?")
28
+ print(f"Final answer: {answer}")
29
+
30
+ if __name__ == "__main__":
31
+ main()