kyoussef commited on
Commit
3823795
·
1 Parent(s): 81917a3

Implement QA Agent

Browse files
Files changed (4) hide show
  1. app.py +49 -3
  2. qa_agent.py +49 -0
  3. sandbox.ipynb +482 -0
  4. tools.py +174 -0
app.py CHANGED
@@ -3,6 +3,7 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
@@ -18,6 +19,29 @@ class BasicAgent:
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 +64,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
@@ -72,17 +96,37 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
72
  # 3. Run your Agent
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})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
86
  except Exception as e:
87
  print(f"Error running agent on task {task_id}: {e}")
88
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
@@ -90,6 +134,8 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
90
  if not answers_payload:
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
 
93
 
94
  # 4. Prepare Submission
95
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from qa_agent import QAAgent
7
 
8
  # (Keep Constants as is)
9
  # --- Constants ---
 
19
  fixed_answer = "This is a default answer."
20
  print(f"Agent returning fixed answer: {fixed_answer}")
21
  return fixed_answer
22
+
23
+ def cache_results(results, filename="results.json"):
24
+ """
25
+ Writes the results to a json file.
26
+ """
27
+ import json
28
+ with open(filename, "w") as f:
29
+ json.dump(results, f)
30
+ print("Results cached to results.json")
31
+
32
+ def load_results(results_file):
33
+ """
34
+ Loads the results from a json file.
35
+ """
36
+ import json
37
+ try:
38
+ with open(results_file, "r") as f:
39
+ results = json.load(f)
40
+ print("Results loaded from results.json")
41
+ return results
42
+ except FileNotFoundError:
43
+ print("No cached results found.")
44
+ return {}
45
 
46
  def run_and_submit_all( profile: gr.OAuthProfile | None):
47
  """
 
64
 
65
  # 1. Instantiate Agent ( modify this part to create your agent)
66
  try:
67
+ agent = QAAgent()
68
  except Exception as e:
69
  print(f"Error instantiating agent: {e}")
70
  return f"Error initializing agent: {e}", None
 
96
  # 3. Run your Agent
97
  results_log = []
98
  answers_payload = []
99
+ results_cache = load_results("results.json")
100
  print(f"Running agent on {len(questions_data)} questions...")
101
  for item in questions_data:
102
+
103
  task_id = item.get("task_id")
104
+
105
+ print(f"\n\n######################## Processing task: {task_id} ########################\n\n")
106
+
107
  question_text = item.get("question")
108
+ file_name = item['file_name']
109
+ if file_name:
110
+ image_path = api_url + "/files/" + task_id
111
+ msg = f"{question_text} -- filename={image_path}"
112
+ else:
113
+ msg = question_text
114
+
115
+ prompt = f"""You are a helpful assistant. Answer the following question by providing the direct answer, don't repeat the question or provide any explanation, just the ground answer:\n\n {msg}"""
116
+
117
  if not task_id or question_text is None:
118
  print(f"Skipping item with missing task_id or question: {item}")
119
  continue
120
  try:
121
+ if results_cache and task_id in results_cache:
122
+ print(f"Read from cache")
123
+ submitted_answer = results_cache[task_id]["Submitted Answer"]
124
+ else:
125
+ submitted_answer = agent(prompt)
126
+ result_payload = {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}
127
+ results_cache[task_id] = result_payload
128
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
129
+ results_log.append(result_payload)
130
  except Exception as e:
131
  print(f"Error running agent on task {task_id}: {e}")
132
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
134
  if not answers_payload:
135
  print("Agent did not produce any answers to submit.")
136
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
137
+
138
+ cache_results(results_cache)
139
 
140
  # 4. Prepare Submission
141
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
qa_agent.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated, TypedDict
2
+ from langchain_openai import ChatOpenAI
3
+ from tools import add, subtract, multiply, divide, exponentiate, web_search, paper_search, load_web_page, understand_image, transcribe_audio
4
+ from langchain_core.messages import AnyMessage
5
+ from langgraph.graph.message import add_messages
6
+ from langchain_core.messages import AnyMessage, HumanMessage
7
+ from langgraph.prebuilt import ToolNode, tools_condition
8
+ from langgraph.graph import START, StateGraph
9
+
10
+
11
+ class AgentState(TypedDict):
12
+ messages: Annotated[list[AnyMessage], add_messages]
13
+
14
+ class QAAgent:
15
+ def __init__(self):
16
+ print("QA Agent initialized.")
17
+ self.agent = self.build_agent()
18
+
19
+ def __call__(self, question: str) -> str:
20
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
21
+ response = self.agent.invoke({"messages": [HumanMessage(content=question)]})
22
+ ret = response['messages'][-1].content
23
+ print(f"Agent returning fixed answer: {ret}")
24
+ return ret
25
+
26
+ def build_agent(self):
27
+ model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
28
+ tools = [add, subtract, multiply, divide, exponentiate, web_search, paper_search, load_web_page, understand_image, transcribe_audio]
29
+ model_with_tools = model.bind_tools(tools)
30
+
31
+ def assistant(state: AgentState):
32
+ return {
33
+ "messages": [model_with_tools.invoke(state["messages"])],
34
+ }
35
+
36
+ builder = StateGraph(AgentState)
37
+
38
+ builder.add_node("assistant", assistant)
39
+ builder.add_node("tools", ToolNode(tools))
40
+
41
+ builder.add_edge(START, "assistant")
42
+ builder.add_conditional_edges("assistant", tools_condition)
43
+ builder.add_edge("tools", "assistant")
44
+
45
+ agent = builder.compile()
46
+ return agent
47
+
48
+
49
+
sandbox.ipynb ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "02186871",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import requests\n",
11
+ "from dotenv import load_dotenv\n",
12
+ "import os"
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "execution_count": 3,
18
+ "id": "b2275123",
19
+ "metadata": {},
20
+ "outputs": [
21
+ {
22
+ "data": {
23
+ "text/plain": [
24
+ "False"
25
+ ]
26
+ },
27
+ "execution_count": 3,
28
+ "metadata": {},
29
+ "output_type": "execute_result"
30
+ }
31
+ ],
32
+ "source": [
33
+ "DEFAULT_API_URL = \"https://agents-course-unit4-scoring.hf.space\"\n",
34
+ "api_url = DEFAULT_API_URL\n",
35
+ "questions_url = f\"{api_url}/questions\"\n",
36
+ "\n",
37
+ "\n",
38
+ "load_dotenv()\n",
39
+ "#print(os.environ[\"OPENAI_API_KEY\"])\n",
40
+ "#print(os.environ[\"TAVILY_API_KEY\"])"
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "code",
45
+ "execution_count": 4,
46
+ "id": "8faab0d9",
47
+ "metadata": {},
48
+ "outputs": [],
49
+ "source": [
50
+ "response = requests.get(questions_url, timeout=15)\n",
51
+ "response.raise_for_status()\n",
52
+ "questions_data = response.json()"
53
+ ]
54
+ },
55
+ {
56
+ "cell_type": "code",
57
+ "execution_count": 5,
58
+ "id": "eb345cd7",
59
+ "metadata": {},
60
+ "outputs": [
61
+ {
62
+ "name": "stderr",
63
+ "output_type": "stream",
64
+ "text": [
65
+ "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
66
+ ]
67
+ }
68
+ ],
69
+ "source": [
70
+ "from langchain.tools import tool\n",
71
+ "#from langchain_community.tools import DuckDuckGoSearchRun\n",
72
+ "from langchain_community.tools import TavilySearchResults\n",
73
+ "from langchain_community.document_loaders import WebBaseLoader\n",
74
+ "from langchain_community.document_loaders import YoutubeLoader\n",
75
+ "from langchain_community.document_loaders import WikipediaLoader\n",
76
+ "from langchain_community.document_loaders import ArxivLoader\n",
77
+ "from typing import TypedDict, Annotated\n",
78
+ "from langgraph.graph.message import add_messages\n",
79
+ "from langchain_core.messages import AnyMessage, HumanMessage, AIMessage\n",
80
+ "from langgraph.prebuilt import ToolNode, tools_condition\n",
81
+ "from langgraph.graph import START, StateGraph\n",
82
+ "#from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace\n",
83
+ "from langchain_openai import ChatOpenAI\n",
84
+ "\n",
85
+ "@tool\n",
86
+ "def add(a: float, b: float) -> float:\n",
87
+ " \"\"\"Add two integers and return the result.\"\"\"\n",
88
+ " return a + b\n",
89
+ "\n",
90
+ "@tool\n",
91
+ "def subtract(a: float, b: float) -> float:\n",
92
+ " \"\"\"Subtract two integers and return the result.\"\"\"\n",
93
+ " return a - b\n",
94
+ "\n",
95
+ "@tool\n",
96
+ "def multiply(a: float, b: float) -> float:\n",
97
+ " \"\"\"Multiply two integers and return the result.\"\"\"\n",
98
+ " return a * b\n",
99
+ "\n",
100
+ "@tool\n",
101
+ "def divide(a: float, b: float) -> float:\n",
102
+ " \"\"\"Divide two integers and return the result.\"\"\"\n",
103
+ " if b == 0:\n",
104
+ " raise ValueError(\"Cannot divide by zero.\")\n",
105
+ " return a / b\n",
106
+ "\n",
107
+ "@tool\n",
108
+ "def exponentiate(base: float, exponent: float) -> float:\n",
109
+ " \"\"\"Raise a number to the power of another number and return the result.\"\"\"\n",
110
+ " return base ** exponent\n",
111
+ "\n",
112
+ "@tool\n",
113
+ "def modulus(a: float, b: float) -> float:\n",
114
+ " \"\"\"Return the modulus of two integers.\"\"\"\n",
115
+ " return a % b\n",
116
+ "\n",
117
+ "@tool\n",
118
+ "def wiki_search(query: str) -> str:\n",
119
+ " \"\"\"Search Wikipedia and returns only 2 results. \n",
120
+ " \n",
121
+ " Args:\n",
122
+ " query: The search query.\"\"\"\n",
123
+ " docs = WikipediaLoader(query=query, load_max_docs=2).load()\n",
124
+ " res = \"\\n#######\\n\".join(\n",
125
+ " [\n",
126
+ " f\"Document {i+1}:\\nSource: {doc.metadata.get('source', '')}\\nPage: {doc.metadata.get('page', '')}\\nContent:\\n{doc.page_content}\\n\"\n",
127
+ " for i, doc in enumerate(docs)\n",
128
+ " ])\n",
129
+ " print(f\"load wiki page : {res}\")\n",
130
+ " return {\"results\": res}\n",
131
+ "\n",
132
+ "@tool\n",
133
+ "def load_web_page(url: str) -> str:\n",
134
+ " \"\"\"Load a web page and return its content.\n",
135
+ " \n",
136
+ " Args:\n",
137
+ " url: The URL of the web page to load.\n",
138
+ " \"\"\"\n",
139
+ " loader = WebBaseLoader(url)\n",
140
+ " docs = loader.load()\n",
141
+ " res = \"\\n#######\\n\".join(\n",
142
+ " [\n",
143
+ " f\"Document {i+1}:\\nSource: {doc.metadata.get('source', '')}\\nPage: {doc.metadata.get('page', '')}\\nContent:\\n{doc.page_content}\\n\"\n",
144
+ " for i, doc in enumerate(docs)\n",
145
+ " ])\n",
146
+ " print(f\"load web page : {res}\")\n",
147
+ " return {\"results\": res}\n",
148
+ " \n",
149
+ "@tool\n",
150
+ "def paper_search(query: str) -> str:\n",
151
+ " \"\"\"Search Arxiv for a query and return maximum 3 result.\n",
152
+ " \n",
153
+ " Args:\n",
154
+ " query: The search query.\"\"\"\n",
155
+ " docs = ArxivLoader(query=query, load_max_docs=3).load()\n",
156
+ " res = \"\\n#######\\n\".join(\n",
157
+ " [\n",
158
+ " f\"Document {i+1}:\\nSource: {doc.metadata.get('source', '')}\\nPage: {doc.metadata.get('page', '')}\\nContent:\\n{doc.page_content}\\n\"\n",
159
+ " for i, doc in enumerate(docs)\n",
160
+ " ])\n",
161
+ " print(f\"load paper page : {res}\")\n",
162
+ " return {\"results\": res}\n",
163
+ "\n",
164
+ "@tool\n",
165
+ "def understand_image(text: str, image_url: str):\n",
166
+ " \"\"\"\n",
167
+ " Sends a text prompt and an image URL to OpenAI's API using the ChatOpenAI model.\n",
168
+ " Returns the model's response.\n",
169
+ "\n",
170
+ " Args:\n",
171
+ " text (str): The text prompt to send.\n",
172
+ " image_url (str): URL to the image to send.\n",
173
+ "\n",
174
+ " Returns:\n",
175
+ " str: The response from the model.\n",
176
+ " \"\"\"\n",
177
+ "\n",
178
+ " # Fetch image from URL and encode as base64\n",
179
+ " #response = requests.get(image_url)\n",
180
+ " #image_bytes = response.content\n",
181
+ " #image_b64 = base64.b64encode(image_bytes).decode(\"utf-8\")\n",
182
+ "\n",
183
+ " # Prepare message with text and image\n",
184
+ " message = HumanMessage(\n",
185
+ " content=[\n",
186
+ " {\"type\": \"text\", \"text\": text},\n",
187
+ " #{\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/png;base64,{image_b64}\", \"detail\": \"auto\"}}\n",
188
+ " {\"type\": \"image_url\", \"image_url\": {\"url\": image_url}}\n",
189
+ " ]\n",
190
+ " )\n",
191
+ " model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
192
+ " response = model.invoke([message])\n",
193
+ " return response.content\n",
194
+ "\n",
195
+ "@tool\n",
196
+ "def load_youtube_video(url: str) -> str:\n",
197
+ " \"\"\"Load a YouTube video and return its content.\"\"\"\n",
198
+ " loader = YoutubeLoader.from_youtube_url(url, add_video_info=True)\n",
199
+ " documents = loader.load()\n",
200
+ " return documents[0].page_content if documents else \"No content found\"\n",
201
+ "\n",
202
+ "@tool\n",
203
+ "def web_search(query: str) -> str:\n",
204
+ " \"\"\"Search Tavily for a query and return maximum 5 results.\n",
205
+ " \n",
206
+ " Args:\n",
207
+ " query: The search query.\"\"\"\n",
208
+ " documents = TavilySearchResults(max_results=5).invoke(input=query)\n",
209
+ " res = \"\\n#######\\n\".join(\n",
210
+ " [\n",
211
+ " f\"Document {i+1}:\\nContent: {doc['content']}\\n\"\n",
212
+ " for i, doc in enumerate(documents)\n",
213
+ " ])\n",
214
+ " print(f\"load tavily search : {res}\")\n",
215
+ " return {\"results\": res}\n",
216
+ "\n",
217
+ "@tool\n",
218
+ "def transcribe_audio(audio_url: str) -> str:\n",
219
+ " \"\"\"Transcribe audio from a URL and return the text.\n",
220
+ " \n",
221
+ " Args:\n",
222
+ " audio_url: The URL of the audio file to transcribe.\n",
223
+ " \"\"\"\n",
224
+ " \n",
225
+ " response = requests.get(audio_url)\n",
226
+ " audio_file = \"audio.mp3\"\n",
227
+ " with open(audio_file, \"wb\") as f:\n",
228
+ " f.write(response.content)\n",
229
+ "\n",
230
+ " # Step 2: Send it to OpenAI's transcription API\n",
231
+ " headers = {\n",
232
+ " \"Authorization\": f\"Bearer {api_key}\"\n",
233
+ " }\n",
234
+ " files = {\n",
235
+ " 'file': (audio_file, open(audio_file, 'rb')),\n",
236
+ " 'model': (None, 'whisper-1')\n",
237
+ " }\n",
238
+ "\n",
239
+ " transcribe_response = requests.post(\n",
240
+ " \"https://api.openai.com/v1/audio/transcriptions\",\n",
241
+ " headers=headers,\n",
242
+ " files=files\n",
243
+ " )\n",
244
+ " print(f\"Transcription response: {transcribe_response.json()}\")\n",
245
+ " return {\"results\": transcribe_response.json().get(\"text\", \"Transcription failed.\")}\n"
246
+ ]
247
+ },
248
+ {
249
+ "cell_type": "code",
250
+ "execution_count": 6,
251
+ "id": "1ac4d78a",
252
+ "metadata": {},
253
+ "outputs": [],
254
+ "source": [
255
+ "\n",
256
+ "model = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
257
+ "tools = [add, subtract, multiply, divide, exponentiate, web_search, paper_search, load_web_page, understand_image, transcribe_audio]\n",
258
+ "model_with_tools = model.bind_tools(tools)"
259
+ ]
260
+ },
261
+ {
262
+ "cell_type": "code",
263
+ "execution_count": 15,
264
+ "id": "b8f136a6",
265
+ "metadata": {},
266
+ "outputs": [
267
+ {
268
+ "data": {
269
+ "text/plain": [
270
+ "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_nUuMRddDxj42ZLqmcvcldXGl', 'function': {'arguments': '{\"query\":\"Featured Article dinosaur November 2016 site:en.wikipedia.org\"}', 'name': 'web_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 363, 'total_tokens': 389, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_96c46af214', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--adcbcb47-d3e2-4ded-9b3a-842e89b5df75-0', tool_calls=[{'name': 'web_search', 'args': {'query': 'Featured Article dinosaur November 2016 site:en.wikipedia.org'}, 'id': 'call_nUuMRddDxj42ZLqmcvcldXGl', 'type': 'tool_call'}], usage_metadata={'input_tokens': 363, 'output_tokens': 26, 'total_tokens': 389, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})"
271
+ ]
272
+ },
273
+ "execution_count": 15,
274
+ "metadata": {},
275
+ "output_type": "execute_result"
276
+ }
277
+ ],
278
+ "source": [
279
+ "model_with_tools.invoke([HumanMessage(\"Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?\")])"
280
+ ]
281
+ },
282
+ {
283
+ "cell_type": "code",
284
+ "execution_count": 7,
285
+ "id": "050fafc8",
286
+ "metadata": {},
287
+ "outputs": [],
288
+ "source": [
289
+ "class AgentState(TypedDict):\n",
290
+ " messages: Annotated[list[AnyMessage], add_messages]"
291
+ ]
292
+ },
293
+ {
294
+ "cell_type": "code",
295
+ "execution_count": 8,
296
+ "id": "d3c345b0",
297
+ "metadata": {},
298
+ "outputs": [],
299
+ "source": [
300
+ "def assistant(state: AgentState):\n",
301
+ " return {\n",
302
+ " \"messages\": [model_with_tools.invoke(state[\"messages\"])],\n",
303
+ " }"
304
+ ]
305
+ },
306
+ {
307
+ "cell_type": "code",
308
+ "execution_count": 9,
309
+ "id": "8e861b9b",
310
+ "metadata": {},
311
+ "outputs": [],
312
+ "source": [
313
+ "builder = StateGraph(AgentState)\n",
314
+ "\n",
315
+ "builder.add_node(\"assistant\", assistant) \n",
316
+ "builder.add_node(\"tools\", ToolNode(tools))\n",
317
+ "\n",
318
+ "builder.add_edge(START, \"assistant\")\n",
319
+ "builder.add_conditional_edges(\"assistant\", tools_condition)\n",
320
+ "builder.add_edge(\"tools\", \"assistant\")\n",
321
+ "\n",
322
+ "agent = builder.compile()\n",
323
+ "\n"
324
+ ]
325
+ },
326
+ {
327
+ "cell_type": "code",
328
+ "execution_count": 10,
329
+ "id": "69d23363",
330
+ "metadata": {},
331
+ "outputs": [
332
+ {
333
+ "data": {
334
+ "text/plain": [
335
+ "{'task_id': '305ac316-eef6-4446-960a-92d80d542f82',\n",
336
+ " 'question': 'Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.',\n",
337
+ " 'Level': '1',\n",
338
+ " 'file_name': ''}"
339
+ ]
340
+ },
341
+ "execution_count": 10,
342
+ "metadata": {},
343
+ "output_type": "execute_result"
344
+ }
345
+ ],
346
+ "source": [
347
+ "questions_data[10]\n",
348
+ "#TavilySearchResults(max_results=10).invoke(input=\"What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?\")\n",
349
+ "#transcribe_audio(api_url + \"/files/\" + questions_data[9][\"task_id\"])"
350
+ ]
351
+ },
352
+ {
353
+ "cell_type": "code",
354
+ "execution_count": 11,
355
+ "id": "ebd9bab7",
356
+ "metadata": {},
357
+ "outputs": [
358
+ {
359
+ "name": "stdout",
360
+ "output_type": "stream",
361
+ "text": [
362
+ "load tavily search : Document 1:\n",
363
+ "Content: Magda M.\n",
364
+ "\n",
365
+ "Magda M. (Polish pronunciation: [ˈmaɡda ˈɛm]) was a Polish soap opera which aired on TVN from 2005 to 2007.\n",
366
+ "\n",
367
+ "Magda M. [...] Actor | Role | Status\n",
368
+ "Joanna Brodzik | Magda Miłowicz | 2005–2007\n",
369
+ "Paweł Małaszyński | Piotr Korzecki | 2005–2007\n",
370
+ "Ewa Kasprzyk | Teresa Miłowicz | 2005–2007\n",
371
+ "Bartłomiej Świderski | Sebastian Lewicki | 2005–2007\n",
372
+ "Daria Widawska | Agata Bielecka | 2005–2007\n",
373
+ "Krzysztof Stelmaszyk | Wiktor Waligóra | 2005–2007\n",
374
+ "Katarzyna Herman | Karolina Waligóra | 2005–2007\n",
375
+ "Bartek Kasprzykowski | Wojciech Płaska | 2005–2007\n",
376
+ "Katarzyna Bujakiewicz | Mariola Adamska-Płaska | 2005–2007 [...] Genre | Soap opera\n",
377
+ "Created by | Michał Kwieciński,Dorota Chamczyk\n",
378
+ "Written by | Radosław Figura(Head writer)\n",
379
+ "Starring | Joanna BrodzikPaweł MałaszyńskiEwa KasprzykBartłomiej ŚwiderskiDaria WidawskaKrzysztof StelmaszykKatarzyna HermanBartek KasprzykowskiKatarzyna BujakiewiczSzymon BobrowskiJacek BraciakPatrycja Durska\n",
380
+ "No.of episodes | 55\n",
381
+ "Production\n",
382
+ "Executive producer | Dariusz Gąsiorowski\n",
383
+ "Running time | 42–47 minutes\n",
384
+ "Original release\n",
385
+ "Network | TVN\n",
386
+ "\n",
387
+ "#######\n",
388
+ "Document 2:\n",
389
+ "Content: | \n",
390
+ "GAIA\n",
391
+ "| \n",
392
+ "broccoli, celery, fresh basil, lettuce, sweet potatoes\n",
393
+ "| \n",
394
+ "None\n",
395
+ "|\n",
396
+ "| \n",
397
+ "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.\n",
398
+ "| \n",
399
+ "GAIA\n",
400
+ "| \n",
401
+ "Wojciech\n",
402
+ "| \n",
403
+ "None\n",
404
+ "|\n",
405
+ "| \n",
406
+ "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?\n",
407
+ "| \n",
408
+ "GAIA\n",
409
+ "| \n",
410
+ "6\n",
411
+ "| \n",
412
+ "None\n",
413
+ "|\n",
414
+ "|\n",
415
+ "\n",
416
+ "#######\n",
417
+ "Document 3:\n",
418
+ "Content: Wszyscy kochają Romana (Everybody Loves Roman) is a Polish television sitcom that premiered on TVN on 2 September 2011.[1][2] The series is a Polish-language adaptation of the American Emmy Awards winner, Everybody Loves Raymond and stars Bartłomiej Kasprzykowski as the titular Roman, a newspaper sportswriter.\n",
419
+ "\n",
420
+ "#######\n",
421
+ "Document 4:\n",
422
+ "Content: Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name. | Wojciech | GAIA\n",
423
+ "How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need? | 6 | GAIA\n",
424
+ "\n",
425
+ "#######\n",
426
+ "Document 5:\n",
427
+ "Content: Show RomancesFavorite Sub-Plot Love StoriesFavorite TV CouplesFavorite \"will they, or won't they\" CouplesGuilty Pleasure CouplesHot SeatHugo & Nika - Operación Triunfo (Spanish)If You Could Only Choose One Couple...Juan Miguel & Marichuy - Cuidado con el Angel (Spanish)Keepers ListLove Triangles: Love it? Hate it?Most Romantic MoviesMost Romantic On-Screen KissMusician CouplesMy Best Friend's GirlOff Topic ThreadPeriod Drama CouplesPiotr & Magda - Magda M. (Polish)Polish (Foreign)Pop Culture's\n",
428
+ "\n",
429
+ "Question: Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.\n",
430
+ "🎩 Agent's Response:\n",
431
+ "The actor who played Ray in the Polish-language version of Everybody Loves Raymond played \"Wojciech\" in Magda M.\n"
432
+ ]
433
+ }
434
+ ],
435
+ "source": [
436
+ "import os\n",
437
+ "file_name = questions_data[10]['file_name']\n",
438
+ "query = questions_data[10]['question']\n",
439
+ "if file_name:\n",
440
+ " image_path = api_url + \"/files/\" + file_name.split(\".\")[0]\n",
441
+ " msg = f\"{query} -- filename={image_path}\"\n",
442
+ "else:\n",
443
+ " msg = query\n",
444
+ "messages = [HumanMessage(content=msg),]\n",
445
+ "response = agent.invoke({\"messages\": messages})\n",
446
+ "\n",
447
+ "print(f\"Question: {msg}\")\n",
448
+ "print(\"🎩 Agent's Response:\")\n",
449
+ "print(response['messages'][-1].content)"
450
+ ]
451
+ },
452
+ {
453
+ "cell_type": "code",
454
+ "execution_count": null,
455
+ "id": "5d828780",
456
+ "metadata": {},
457
+ "outputs": [],
458
+ "source": []
459
+ }
460
+ ],
461
+ "metadata": {
462
+ "kernelspec": {
463
+ "display_name": "py311",
464
+ "language": "python",
465
+ "name": "python3"
466
+ },
467
+ "language_info": {
468
+ "codemirror_mode": {
469
+ "name": "ipython",
470
+ "version": 3
471
+ },
472
+ "file_extension": ".py",
473
+ "mimetype": "text/x-python",
474
+ "name": "python",
475
+ "nbconvert_exporter": "python",
476
+ "pygments_lexer": "ipython3",
477
+ "version": "3.11.10"
478
+ }
479
+ },
480
+ "nbformat": 4,
481
+ "nbformat_minor": 5
482
+ }
tools.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.tools import tool
2
+ #from langchain_community.tools import DuckDuckGoSearchRun
3
+ from langchain_community.tools import TavilySearchResults
4
+ from langchain_community.document_loaders import WebBaseLoader
5
+ from langchain_community.document_loaders import YoutubeLoader
6
+ from langchain_community.document_loaders import WikipediaLoader
7
+ from langchain_community.document_loaders import ArxivLoader
8
+ import requests
9
+ import os
10
+ from langchain_core.messages import HumanMessage
11
+ from langchain_openai import ChatOpenAI
12
+
13
+ @tool
14
+ def add(a: float, b: float) -> float:
15
+ """Add two integers and return the result."""
16
+ return a + b
17
+
18
+ @tool
19
+ def subtract(a: float, b: float) -> float:
20
+ """Subtract two integers and return the result."""
21
+ return a - b
22
+
23
+ @tool
24
+ def multiply(a: float, b: float) -> float:
25
+ """Multiply two integers and return the result."""
26
+ return a * b
27
+
28
+ @tool
29
+ def divide(a: float, b: float) -> float:
30
+ """Divide two integers and return the result."""
31
+ if b == 0:
32
+ raise ValueError("Cannot divide by zero.")
33
+ return a / b
34
+
35
+ @tool
36
+ def exponentiate(base: float, exponent: float) -> float:
37
+ """Raise a number to the power of another number and return the result."""
38
+ return base ** exponent
39
+
40
+ @tool
41
+ def modulus(a: float, b: float) -> float:
42
+ """Return the modulus of two integers."""
43
+ return a % b
44
+
45
+ @tool
46
+ def wiki_search(query: str) -> str:
47
+ """Search Wikipedia and returns only 2 results.
48
+
49
+ Args:
50
+ query: The search query."""
51
+ docs = WikipediaLoader(query=query, load_max_docs=2).load()
52
+ res = "\n#######\n".join(
53
+ [
54
+ f"Document {i+1}:\nSource: {doc.metadata.get('source', '')}\nPage: {doc.metadata.get('page', '')}\nContent:\n{doc.page_content}\n"
55
+ for i, doc in enumerate(docs)
56
+ ])
57
+ print(f"load wiki page : {res}")
58
+ return {"results": res}
59
+
60
+ @tool
61
+ def load_web_page(url: str) -> str:
62
+ """Load a web page and return its content.
63
+
64
+ Args:
65
+ url: The URL of the web page to load.
66
+ """
67
+ loader = WebBaseLoader(url)
68
+ docs = loader.load()
69
+ res = "\n#######\n".join(
70
+ [
71
+ f"Document {i+1}:\nSource: {doc.metadata.get('source', '')}\nPage: {doc.metadata.get('page', '')}\nContent:\n{doc.page_content}\n"
72
+ for i, doc in enumerate(docs)
73
+ ])
74
+ print(f"load web page : {res}")
75
+ return {"results": res}
76
+
77
+ @tool
78
+ def paper_search(query: str) -> str:
79
+ """Search Arxiv for a query and return maximum 3 result.
80
+
81
+ Args:
82
+ query: The search query."""
83
+ docs = ArxivLoader(query=query, load_max_docs=3).load()
84
+ res = "\n#######\n".join(
85
+ [
86
+ f"Document {i+1}:\nSource: {doc.metadata.get('source', '')}\nPage: {doc.metadata.get('page', '')}\nContent:\n{doc.page_content}\n"
87
+ for i, doc in enumerate(docs)
88
+ ])
89
+ print(f"load paper page : {res}")
90
+ return {"results": res}
91
+
92
+ @tool
93
+ def understand_image(text: str, image_url: str):
94
+ """
95
+ Sends a text prompt and an image URL to OpenAI's API using the ChatOpenAI model.
96
+ Returns the model's response.
97
+
98
+ Args:
99
+ text (str): The text prompt to send.
100
+ image_url (str): URL to the image to send.
101
+
102
+ Returns:
103
+ str: The response from the model.
104
+ """
105
+
106
+ # Fetch image from URL and encode as base64
107
+ #response = requests.get(image_url)
108
+ #image_bytes = response.content
109
+ #image_b64 = base64.b64encode(image_bytes).decode("utf-8")
110
+
111
+ # Prepare message with text and image
112
+ message = HumanMessage(
113
+ content=[
114
+ {"type": "text", "text": text},
115
+ #{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_b64}", "detail": "auto"}}
116
+ {"type": "image_url", "image_url": {"url": image_url}}
117
+ ]
118
+ )
119
+ model = ChatOpenAI(model="gpt-4o", temperature=0)
120
+ response = model.invoke([message])
121
+ return response.content
122
+
123
+ @tool
124
+ def load_youtube_video(url: str) -> str:
125
+ """Load a YouTube video and return its content."""
126
+ loader = YoutubeLoader.from_youtube_url(url, add_video_info=True)
127
+ documents = loader.load()
128
+ return documents[0].page_content if documents else "No content found"
129
+
130
+ @tool
131
+ def web_search(query: str) -> str:
132
+ """Search Tavily for a query and return maximum 5 results.
133
+
134
+ Args:
135
+ query: The search query."""
136
+ documents = TavilySearchResults(max_results=5).invoke(input=query)
137
+ res = "\n#######\n".join(
138
+ [
139
+ f"Document {i+1}:\nContent: {doc['content']}\n"
140
+ for i, doc in enumerate(documents)
141
+ ])
142
+ print(f"load tavily search : {res}")
143
+ return {"results": res}
144
+
145
+ @tool
146
+ def transcribe_audio(audio_url: str) -> str:
147
+ """Transcribe audio from a URL and return the text.
148
+
149
+ Args:
150
+ audio_url: The URL of the audio file to transcribe.
151
+ """
152
+
153
+ response = requests.get(audio_url)
154
+ audio_file = "audio.mp3"
155
+ with open(audio_file, "wb") as f:
156
+ f.write(response.content)
157
+
158
+ # Step 2: Send it to OpenAI's transcription API
159
+ api_key = os.environ.get("OPENAI_API_KEY")
160
+ headers = {
161
+ "Authorization": f"Bearer {api_key}"
162
+ }
163
+ files = {
164
+ 'file': (audio_file, open(audio_file, 'rb')),
165
+ 'model': (None, 'whisper-1')
166
+ }
167
+
168
+ transcribe_response = requests.post(
169
+ "https://api.openai.com/v1/audio/transcriptions",
170
+ headers=headers,
171
+ files=files
172
+ )
173
+ print(f"Transcription response: {transcribe_response.json()}")
174
+ return {"results": transcribe_response.json().get("text", "Transcription failed.")}