minhnguyent546 commited on
Commit
30c51ff
·
unverified ·
1 Parent(s): 81917a3

feat: update app.py, add tools.py, and tons of other files

Browse files
Files changed (8) hide show
  1. .env.sample +7 -0
  2. .gitignore +5 -0
  3. .python-version +1 -0
  4. README.md +3 -2
  5. app.py +86 -14
  6. requirements.txt +14 -2
  7. system_prompt.txt +2 -0
  8. tools.py +134 -0
.env.sample ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ LANGFUSE_SECRET_KEY=
2
+ LANGFUSE_PUBLIC_KEY=
3
+ LANGFUSE_HOST=
4
+ GOOGLE_API_KEY=
5
+ TAVILY_API_KEY=
6
+ GROQ_API_KEY=
7
+ MODEL_PROVIDER= # choose from: groq, google, openai
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__
2
+ .venv
3
+ _*
4
+ *.bak
5
+ *.tmp
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.10
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Template Final Assignment
3
  emoji: 🕵🏻‍♂️
4
  colorFrom: indigo
5
  colorTo: indigo
@@ -8,8 +8,9 @@ sdk_version: 5.25.2
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
 
11
  # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Agent Course - Final assignment
3
  emoji: 🕵🏻‍♂️
4
  colorFrom: indigo
5
  colorTo: indigo
 
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
11
+ python_version: "3.10"
12
  # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
13
  hf_oauth_expiration_minutes: 480
14
  ---
15
 
16
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,23 +1,89 @@
1
  import os
2
- 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 +106,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
@@ -54,6 +120,9 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
54
  response = requests.get(questions_url, timeout=15)
55
  response.raise_for_status()
56
  questions_data = response.json()
 
 
 
57
  if not questions_data:
58
  print("Fetched questions list is empty.")
59
  return "Fetched questions list is empty or invalid format.", None
@@ -73,12 +142,14 @@ 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:
80
  print(f"Skipping item with missing task_id or question: {item}")
81
  continue
 
82
  try:
83
  submitted_answer = agent(question_text)
84
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
@@ -172,6 +243,7 @@ with gr.Blocks() as demo:
172
  )
173
 
174
  if __name__ == "__main__":
 
175
  print("\n" + "-"*30 + " App Starting " + "-"*30)
176
  # Check for SPACE_HOST and SPACE_ID at startup for information
177
  space_host_startup = os.getenv("SPACE_HOST")
@@ -193,4 +265,4 @@ if __name__ == "__main__":
193
  print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
1
  import os
 
2
  import requests
3
+ from typing import Literal
4
+
5
+ import gradio as gr
6
  import pandas as pd
7
+ from dotenv import load_dotenv
8
+ from langchain_core.messages import HumanMessage, SystemMessage
9
+ from langchain_google_genai import ChatGoogleGenerativeAI
10
+ from langchain_groq import ChatGroq
11
+ from langchain_openai import ChatOpenAI
12
+ from langgraph.graph import MessagesState, START, StateGraph
13
+ from langgraph.prebuilt import ToolNode, tools_condition
14
+
15
+ from tools import all_tools
16
 
17
  # (Keep Constants as is)
18
  # --- Constants ---
19
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
20
 
21
+ class MyAgent:
22
+ def __init__(self, provider: Literal['openai', 'google', 'groq'] = 'groq') -> None:
23
+ if provider == 'openai':
24
+ self.llm = ChatOpenAI(model='gpt-4.1-nano', temperature=0)
25
+ elif provider == 'google':
26
+ self.llm = ChatGoogleGenerativeAI(model='gemini-2.0-flash', temperature=0)
27
+ elif provider == 'groq':
28
+ self.llm = ChatGroq(model='qwen-qwq-32b', temperature=0)
29
+ else:
30
+ raise ValueError('Invalid provider. Choose "openai", "google", or "groq".')
31
+
32
+ self.tools = all_tools
33
+ self.llm_with_tools = self.llm.bind_tools(tools=self.tools)
34
+ self.agent = self.build_graph()
35
+
36
+ with open('system_prompt.txt', 'r', encoding='utf-8') as f:
37
+ self.SYSTEM_PROMPT = f.read()
38
+
39
+ if 'LANGFUSE_SECRET_KEY' in os.environ:
40
+ from langfuse.callback import CallbackHandler
41
+ self.callbacks = [
42
+ CallbackHandler(),
43
+ ]
44
+
45
  def __call__(self, question: str) -> str:
46
+ messages = [
47
+ SystemMessage(content=self.SYSTEM_PROMPT),
48
+ HumanMessage(content=question),
49
+ ]
50
+
51
+ response = self.agent.invoke(
52
+ input={'messages': messages},
53
+ config={'callbacks': self.callbacks},
54
+ )
55
+
56
+ response_content = response['messages'][-1].content
57
+ final_answer = self.extract_final_answer(response_content)
58
+ return final_answer
59
+
60
+ def assistant(self, state: MessagesState):
61
+ return {
62
+ 'messages': [self.llm_with_tools.invoke(state['messages'])],
63
+ }
64
+
65
+ def build_graph(self):
66
+ builder = StateGraph(MessagesState)
67
+
68
+ builder.add_node('assistant', self.assistant)
69
+ builder.add_node('tools', ToolNode(self.tools))
70
+
71
+ builder.add_edge(START, 'assistant')
72
+ builder.add_conditional_edges(
73
+ 'assistant',
74
+ tools_condition,
75
+ )
76
+ builder.add_edge('tools', 'assistant')
77
+ compiled_graph = builder.compile()
78
+
79
+ return compiled_graph
80
+
81
+ def extract_final_answer(self, response_content: str) -> str:
82
+ start_answer_idx = response_content.find("FINAL ANSWER: ")
83
+ if start_answer_idx == -1:
84
+ return "Invalid response format. No final answer found."
85
+ final_answer = response_content[start_answer_idx + len("FINAL ANSWER: "):].strip()
86
+ return final_answer
87
 
88
  def run_and_submit_all( profile: gr.OAuthProfile | None):
89
  """
 
106
 
107
  # 1. Instantiate Agent ( modify this part to create your agent)
108
  try:
109
+ agent = MyAgent(provider=os.environ.get('MODEL_PROVIDER', 'groq').lower())
110
  except Exception as e:
111
  print(f"Error instantiating agent: {e}")
112
  return f"Error initializing agent: {e}", None
 
120
  response = requests.get(questions_url, timeout=15)
121
  response.raise_for_status()
122
  questions_data = response.json()
123
+
124
+ # questions_data = questions_data[:5]
125
+
126
  if not questions_data:
127
  print("Fetched questions list is empty.")
128
  return "Fetched questions list is empty or invalid format.", None
 
142
  results_log = []
143
  answers_payload = []
144
  print(f"Running agent on {len(questions_data)} questions...")
145
+ for idx, item in enumerate(questions_data):
146
+ print(f'Running agent on question {idx + 1}/{len(questions_data)}')
147
  task_id = item.get("task_id")
148
  question_text = item.get("question")
149
  if not task_id or question_text is None:
150
  print(f"Skipping item with missing task_id or question: {item}")
151
  continue
152
+
153
  try:
154
  submitted_answer = agent(question_text)
155
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
 
243
  )
244
 
245
  if __name__ == "__main__":
246
+ load_dotenv()
247
  print("\n" + "-"*30 + " App Starting " + "-"*30)
248
  # Check for SPACE_HOST and SPACE_ID at startup for information
249
  space_host_startup = os.getenv("SPACE_HOST")
 
265
  print("-"*(60 + len(" App Starting ")) + "\n")
266
 
267
  print("Launching Gradio Interface for Basic Agent Evaluation...")
268
+ demo.launch(debug=True, share=False)
requirements.txt CHANGED
@@ -1,2 +1,14 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dotenv==0.9.9
2
+ gradio[oauth]==5.29.0
3
+ langchain-community==0.3.23
4
+ langchain-google-genai==2.1.0
5
+ langchain-groq==0.2.4
6
+ langchain-openai==0.3.16
7
+ langchain-tavily==0.1.6
8
+ langchain-unstructured==0.1.6
9
+ langfuse==2.60.3
10
+ langgraph==0.4.1
11
+ pillow==11.2.1
12
+ pytesseract==0.3.13
13
+ requests==2.32.3
14
+ wikipedia==1.4.0
system_prompt.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. 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.
2
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
tools.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytesseract
2
+ from PIL import Image
3
+ from dotenv import load_dotenv
4
+ from langchain.tools import tool
5
+ from langchain_community.document_loaders import WikipediaLoader
6
+ from langchain_community.tools import TavilySearchResults
7
+
8
+
9
+ @tool
10
+ def multiply(a: int, b: int) -> int:
11
+ """Multiply two numbers.
12
+ Args:
13
+ a: first int
14
+ b: second int
15
+ """
16
+ return a * b
17
+
18
+ @tool
19
+ def add(a: int, b: int) -> int:
20
+ """Add two numbers.
21
+
22
+ Args:
23
+ a: first int
24
+ b: second int
25
+ """
26
+ return a + b
27
+
28
+ @tool
29
+ def subtract(a: int, b: int) -> int:
30
+ """Subtract two numbers.
31
+
32
+ Args:
33
+ a: first int
34
+ b: second int
35
+ """
36
+ return a - b
37
+
38
+ @tool
39
+ def divide(a: int, b: int) -> float:
40
+ """Divide two numbers.
41
+
42
+ Args:
43
+ a: first int
44
+ b: second int
45
+ """
46
+ try:
47
+ return a / b
48
+ except ZeroDivisionError:
49
+ raise ValueError("Cannot divide by zero.")
50
+
51
+ @tool
52
+ def modulus(a: int, b: int) -> int:
53
+ """Get the modulus of two numbers.
54
+
55
+ Args:
56
+ a: first int
57
+ b: second int
58
+ """
59
+ return a % b
60
+
61
+ @tool
62
+ def wiki_search(query: str) -> str:
63
+ """Search Wikipedia for a query and return up to 3 results.
64
+
65
+ Args:
66
+ query: query to search.
67
+ """
68
+
69
+ docs = WikipediaLoader(query, load_max_docs=3).load()
70
+ results = "wiki_search results:\n\n"
71
+ results += '\n\n---\n\n'.join([
72
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
73
+ for doc in docs
74
+ ])
75
+ return results
76
+
77
+ @tool
78
+ def web_search(query: str) -> str:
79
+ """Search the web for a query and return up to 3 results.
80
+
81
+ Args:
82
+ query: query to search.
83
+ """
84
+ docs = TavilySearchResults(max_results=3).invoke(query)
85
+ results = "web_search results:\n\n"
86
+ results += '\n\n---\n\n'.join([
87
+ f'<Document source="{doc["url"]}"/>\n{doc["content"]}\n</Document>'
88
+ for doc in docs
89
+ ])
90
+ return results
91
+
92
+ @tool
93
+ def extract_text_from_image(image_path: str) -> str:
94
+ """Extract text from a image.
95
+
96
+ Args:
97
+ image_path: path to the image
98
+
99
+ Returns:
100
+ extracted text from the image
101
+ """
102
+ try:
103
+ image = Image.open(image_path)
104
+
105
+ text = pytesseract.image_to_string(image)
106
+ return f'Extracted text: {text}'
107
+ except Exception as err:
108
+ return f'Error extracting text from the image {image_path}: {str(err)}'
109
+
110
+ all_tools = [
111
+ multiply,
112
+ add,
113
+ subtract,
114
+ divide,
115
+ modulus,
116
+ wiki_search,
117
+ web_search,
118
+ extract_text_from_image,
119
+ ]
120
+
121
+ def main():
122
+ load_dotenv()
123
+
124
+ results = wiki_search('What is Dijkstra algorithm?')
125
+ print(results)
126
+ print('-'*80)
127
+
128
+ results = web_search('What is TypedDict in python')
129
+ print(results)
130
+ print('-'*80)
131
+
132
+
133
+ if __name__ == '__main__':
134
+ main()