leofltt commited on
Commit
9e08ba5
·
1 Parent(s): 449c6d9

smarter prompt approach

Browse files
Files changed (1) hide show
  1. app.py +84 -42
app.py CHANGED
@@ -8,7 +8,7 @@ from langchain_core.prompts import PromptTemplate
8
  from langchain.agents import AgentExecutor, create_react_agent
9
  from langchain import hub
10
  from langchain.tools import Tool
11
- from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
12
  from langchain_community.utilities import WikipediaAPIWrapper
13
  from youtube_transcript_api import YouTubeTranscriptApi
14
  import io
@@ -24,6 +24,7 @@ import torchaudio
24
  # --- Constants ---
25
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
26
 
 
27
  def python_interpreter(code: str) -> str:
28
  """
29
  Executes a string of Python code and returns the output.
@@ -35,7 +36,7 @@ def python_interpreter(code: str) -> str:
35
  # Add numpy and sympy to the local scope of exec
36
  local_scope = {}
37
  exec("import numpy as np\nimport sympy", local_scope)
38
-
39
  f = io.StringIO()
40
  with redirect_stdout(f):
41
  # Pass the local_scope to exec
@@ -44,6 +45,7 @@ def python_interpreter(code: str) -> str:
44
  except Exception as e:
45
  return str(e)
46
 
 
47
  def file_reader_tool(url: str) -> str:
48
  """
49
  Reads the content of a file from a URL and returns the first 5 rows as a string.
@@ -53,23 +55,28 @@ def file_reader_tool(url: str) -> str:
53
  try:
54
  # Download the file content
55
  response = requests.get(url)
56
- response.raise_for_status() # Raise an exception for bad status codes
57
 
58
  # Get the content type to guess the file type
59
- content_type = response.headers.get('content-type')
60
-
61
  # Use BytesIO for binary content like Excel files
62
  file_content = io.BytesIO(response.content)
63
 
64
  df = None
65
- if 'excel' in content_type or 'spreadsheet' in content_type or url.endswith('.xlsx') or url.endswith('.xls'):
 
 
 
 
 
66
  df = pd.read_excel(file_content)
67
- elif 'csv' in content_type or url.endswith('.csv'):
68
- df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))
69
  else:
70
  # Try reading as CSV as a fallback
71
  try:
72
- df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))
73
  except Exception:
74
  return "Error: Could not determine file type. Supported types are Excel and CSV."
75
 
@@ -81,6 +88,7 @@ def file_reader_tool(url: str) -> str:
81
  except Exception as e:
82
  return f"Error: Could not read or process the file: {e}"
83
 
 
84
  def youtube_transcript_tool(url: str) -> str:
85
  """
86
  Fetches the transcript of a YouTube video from its URL.
@@ -89,16 +97,17 @@ def youtube_transcript_tool(url: str) -> str:
89
  try:
90
  video_id = url.split("v=")[1]
91
  # Handle URLs with extra parameters
92
- ampersand_position = video_id.find('&')
93
  if ampersand_position != -1:
94
  video_id = video_id[:ampersand_position]
95
-
96
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
97
- transcript = " ".join([item['text'] for item in transcript_list])
98
  return transcript
99
  except Exception as e:
100
  return f"Error: Could not fetch transcript. Make sure the URL is a valid YouTube video with available transcripts. Error: {e}"
101
 
 
102
  def web_reader_tool(url: str) -> str:
103
  """
104
  Reads the text content of a web page from a URL.
@@ -107,13 +116,14 @@ def web_reader_tool(url: str) -> str:
107
  try:
108
  response = requests.get(url)
109
  response.raise_for_status()
110
- soup = BeautifulSoup(response.content, 'html.parser')
111
  return soup.get_text()
112
  except requests.exceptions.RequestException as e:
113
  return f"Error: Could not download the file from the URL: {e}"
114
  except Exception as e:
115
  return f"Error: Could not read or process the file: {e}"
116
 
 
117
  def audio_analyzer_tool(url: str) -> str:
118
  """
119
  Analyzes audio content from a URL by transcribing it into text.
@@ -122,19 +132,22 @@ def audio_analyzer_tool(url: str) -> str:
122
  try:
123
  print(f"Analyzing audio from URL: {url}")
124
  # Initialize the ASR pipeline
125
- asr_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-base")
126
-
 
 
127
  # Download the audio file
128
  response = requests.get(url)
129
  response.raise_for_status()
130
  audio_data = response.content
131
-
132
  # Transcribe the audio
133
  transcription = asr_pipeline(audio_data)
134
- return transcription['text']
135
  except Exception as e:
136
  return f"Error analyzing audio: {e}"
137
 
 
138
  def video_analyzer_tool(url: str) -> str:
139
  """
140
  Analyzes the audio track of a video from a URL by transcribing it into text.
@@ -143,35 +156,37 @@ def video_analyzer_tool(url: str) -> str:
143
  try:
144
  print(f"Analyzing video from URL: {url}")
145
  # Initialize the ASR pipeline
146
- asr_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-base")
147
-
 
 
148
  # Open the video file from the URL
149
  with av.open(url) as container:
150
  audio_stream = container.streams.audio[0]
151
-
152
  # Decode the audio stream
153
  frames = []
154
  for frame in container.decode(audio_stream):
155
  frames.append(frame.to_ndarray())
156
-
157
  if not frames:
158
  return "Error: No audio frames found in the video."
159
 
160
  # Concatenate frames and convert to a tensor
161
  import numpy as np
 
162
  audio_array = torch.from_numpy(np.concatenate(frames, axis=1))
163
-
164
  # Resample if necessary
165
  resampler = torchaudio.transforms.Resample(
166
- orig_freq=audio_stream.sample_rate,
167
- new_freq=16000
168
  )
169
  resampled_audio = resampler(audio_array)
170
-
171
  # Transcribe the audio
172
  transcription = asr_pipeline(resampled_audio.squeeze().numpy())
173
- return transcription['text']
174
-
175
  except Exception as e:
176
  return f"Error analyzing video: {e}"
177
 
@@ -184,7 +199,9 @@ class BasicAgent:
184
  # Get the token from environment variables (HF Spaces Secrets)
185
  hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN")
186
  if not hf_token:
187
- print("Warning: HUGGING_FACE_HUB_TOKEN secret not found. This will fail for gated models.")
 
 
188
 
189
  # It's better to initialize the model here to avoid reloading it for every call.
190
  # Using a smaller model for faster response times.
@@ -194,42 +211,46 @@ class BasicAgent:
194
  model_kwargs={"dtype": "float32"},
195
  pipeline_kwargs={"max_new_tokens": 256},
196
  )
197
-
198
  tools = [
199
  Tool(
200
  name="Python Interpreter",
201
  func=python_interpreter,
202
- description="A Python interpreter that can execute Python code. Use it for calculations, symbolic math (with sympy), and other tasks that can be solved with code. The code should use `print()` to output the result. Libraries like numpy (as np) and sympy are available."
 
 
 
 
 
203
  ),
204
- DuckDuckGoSearchRun(),
205
  Tool(
206
  name="File Reader from URL",
207
  func=file_reader_tool,
208
- description="Reads the content of a file from a URL and returns the first 5 rows as a string. It can handle Excel (.xlsx, .xls) and CSV (.csv) files. The URL must point directly to the file."
209
  ),
210
  WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
211
  Tool(
212
  name="YouTube Transcript Reader",
213
  func=youtube_transcript_tool,
214
- description="Fetches the full transcript of a YouTube video from its URL. Use this to answer questions about the content of a video."
215
  ),
216
  Tool(
217
  name="Web Page Reader",
218
  func=web_reader_tool,
219
- description="Reads the text content of a web page from a URL. Use this for any URL that doesn't point directly to a file like CSV or Excel."
220
  ),
221
  Tool(
222
  name="Audio Analyzer",
223
  func=audio_analyzer_tool,
224
- description="Analyzes audio content from a URL by transcribing it into text. The URL must point directly to an audio file (e.g., .wav, .mp3)."
225
  ),
226
  Tool(
227
  name="Video Analyzer",
228
  func=video_analyzer_tool,
229
- description="Analyzes the audio track of a video from a URL by transcribing it into text. The URL must point directly to a video file (e.g., .mp4)."
230
  ),
231
  ]
232
-
233
  # A more direct, instruction-based prompt with high-quality examples.
234
  gemma_react_prompt = '''<start_of_turn>user
235
  You are a helpful agent. Your goal is to answer the user's question.
@@ -242,7 +263,7 @@ To answer the question, you MUST follow these steps:
242
  2. **Action:** Choose one of the available tools for your 'Action'. Your 'Action' must be one of [{tool_names}].
243
  3. **Action Input:** Provide the input for that tool. The input should be a simple string, not a Python expression.
244
  4. **Observation:** Wait for the result of the tool.
245
- 5. **Thought:** Analyze the result and the current plan. If the plan needs to be revised, state the new plan. Then, proceed with the next step.
246
  6. Repeat steps 2-5 until you have enough information to answer the question.
247
  7. **Thought:** Once you have the final answer, you MUST use the following template: `FINAL ANSWER: [YOUR FINAL ANSWER]`.
248
 
@@ -255,7 +276,7 @@ To answer the question, you MUST follow these steps:
255
 
256
  Do not make up observations.
257
 
258
- Here is an example:
259
 
260
  Question: In the YouTube video at `https://www.youtube.com/watch?v=example`, the speaker mentions a specific battle. Use the transcript to find the name of the battle, then find out which year it took place.
261
 
@@ -275,6 +296,25 @@ Observation: Page: Battle of Hastings. Summary: The Battle of Hastings was fough
275
  Thought: I have found the year of the battle, 1066. I have completed all steps of my plan.
276
  FINAL ANSWER: Battle of Hastings, 1066
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  Begin!
279
 
280
  Question: {input}
@@ -282,10 +322,12 @@ Thought:{agent_scratchpad}<end_of_turn>
282
  <start_of_turn>model
283
  '''
284
  prompt = PromptTemplate.from_template(gemma_react_prompt)
285
-
286
  agent = create_react_agent(self.llm, tools, prompt)
287
-
288
- self.agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
 
 
289
 
290
  def __call__(self, question: str) -> str:
291
  print(f"Agent received question (first 50 chars): {question[:50]}...")
 
8
  from langchain.agents import AgentExecutor, create_react_agent
9
  from langchain import hub
10
  from langchain.tools import Tool
11
+ from langchain_community.tools import DuckDuckGoSearchResults, WikipediaQueryRun
12
  from langchain_community.utilities import WikipediaAPIWrapper
13
  from youtube_transcript_api import YouTubeTranscriptApi
14
  import io
 
24
  # --- Constants ---
25
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
26
 
27
+
28
  def python_interpreter(code: str) -> str:
29
  """
30
  Executes a string of Python code and returns the output.
 
36
  # Add numpy and sympy to the local scope of exec
37
  local_scope = {}
38
  exec("import numpy as np\nimport sympy", local_scope)
39
+
40
  f = io.StringIO()
41
  with redirect_stdout(f):
42
  # Pass the local_scope to exec
 
45
  except Exception as e:
46
  return str(e)
47
 
48
+
49
  def file_reader_tool(url: str) -> str:
50
  """
51
  Reads the content of a file from a URL and returns the first 5 rows as a string.
 
55
  try:
56
  # Download the file content
57
  response = requests.get(url)
58
+ response.raise_for_status() # Raise an exception for bad status codes
59
 
60
  # Get the content type to guess the file type
61
+ content_type = response.headers.get("content-type")
62
+
63
  # Use BytesIO for binary content like Excel files
64
  file_content = io.BytesIO(response.content)
65
 
66
  df = None
67
+ if (
68
+ "excel" in content_type
69
+ or "spreadsheet" in content_type
70
+ or url.endswith(".xlsx")
71
+ or url.endswith(".xls")
72
+ ):
73
  df = pd.read_excel(file_content)
74
+ elif "csv" in content_type or url.endswith(".csv"):
75
+ df = pd.read_csv(io.StringIO(response.content.decode("utf-8")))
76
  else:
77
  # Try reading as CSV as a fallback
78
  try:
79
+ df = pd.read_csv(io.StringIO(response.content.decode("utf-8")))
80
  except Exception:
81
  return "Error: Could not determine file type. Supported types are Excel and CSV."
82
 
 
88
  except Exception as e:
89
  return f"Error: Could not read or process the file: {e}"
90
 
91
+
92
  def youtube_transcript_tool(url: str) -> str:
93
  """
94
  Fetches the transcript of a YouTube video from its URL.
 
97
  try:
98
  video_id = url.split("v=")[1]
99
  # Handle URLs with extra parameters
100
+ ampersand_position = video_id.find("&")
101
  if ampersand_position != -1:
102
  video_id = video_id[:ampersand_position]
103
+
104
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
105
+ transcript = " ".join([item["text"] for item in transcript_list])
106
  return transcript
107
  except Exception as e:
108
  return f"Error: Could not fetch transcript. Make sure the URL is a valid YouTube video with available transcripts. Error: {e}"
109
 
110
+
111
  def web_reader_tool(url: str) -> str:
112
  """
113
  Reads the text content of a web page from a URL.
 
116
  try:
117
  response = requests.get(url)
118
  response.raise_for_status()
119
+ soup = BeautifulSoup(response.content, "html.parser")
120
  return soup.get_text()
121
  except requests.exceptions.RequestException as e:
122
  return f"Error: Could not download the file from the URL: {e}"
123
  except Exception as e:
124
  return f"Error: Could not read or process the file: {e}"
125
 
126
+
127
  def audio_analyzer_tool(url: str) -> str:
128
  """
129
  Analyzes audio content from a URL by transcribing it into text.
 
132
  try:
133
  print(f"Analyzing audio from URL: {url}")
134
  # Initialize the ASR pipeline
135
+ asr_pipeline = pipeline(
136
+ "automatic-speech-recognition", model="openai/whisper-base"
137
+ )
138
+
139
  # Download the audio file
140
  response = requests.get(url)
141
  response.raise_for_status()
142
  audio_data = response.content
143
+
144
  # Transcribe the audio
145
  transcription = asr_pipeline(audio_data)
146
+ return transcription["text"]
147
  except Exception as e:
148
  return f"Error analyzing audio: {e}"
149
 
150
+
151
  def video_analyzer_tool(url: str) -> str:
152
  """
153
  Analyzes the audio track of a video from a URL by transcribing it into text.
 
156
  try:
157
  print(f"Analyzing video from URL: {url}")
158
  # Initialize the ASR pipeline
159
+ asr_pipeline = pipeline(
160
+ "automatic-speech-recognition", model="openai/whisper-base"
161
+ )
162
+
163
  # Open the video file from the URL
164
  with av.open(url) as container:
165
  audio_stream = container.streams.audio[0]
166
+
167
  # Decode the audio stream
168
  frames = []
169
  for frame in container.decode(audio_stream):
170
  frames.append(frame.to_ndarray())
171
+
172
  if not frames:
173
  return "Error: No audio frames found in the video."
174
 
175
  # Concatenate frames and convert to a tensor
176
  import numpy as np
177
+
178
  audio_array = torch.from_numpy(np.concatenate(frames, axis=1))
179
+
180
  # Resample if necessary
181
  resampler = torchaudio.transforms.Resample(
182
+ orig_freq=audio_stream.sample_rate, new_freq=16000
 
183
  )
184
  resampled_audio = resampler(audio_array)
185
+
186
  # Transcribe the audio
187
  transcription = asr_pipeline(resampled_audio.squeeze().numpy())
188
+ return transcription["text"]
189
+
190
  except Exception as e:
191
  return f"Error analyzing video: {e}"
192
 
 
199
  # Get the token from environment variables (HF Spaces Secrets)
200
  hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN")
201
  if not hf_token:
202
+ print(
203
+ "Warning: HUGGING_FACE_HUB_TOKEN secret not found. This will fail for gated models."
204
+ )
205
 
206
  # It's better to initialize the model here to avoid reloading it for every call.
207
  # Using a smaller model for faster response times.
 
211
  model_kwargs={"dtype": "float32"},
212
  pipeline_kwargs={"max_new_tokens": 256},
213
  )
214
+
215
  tools = [
216
  Tool(
217
  name="Python Interpreter",
218
  func=python_interpreter,
219
+ description="A Python interpreter that can execute Python code. Use it for calculations, symbolic math (with sympy), and other tasks that can be solved with code. The code should use `print()` to output the result. Libraries like numpy (as np) and sympy are available.",
220
+ ),
221
+ Tool(
222
+ name="DuckDuckGo Search Results",
223
+ func=DuckDuckGoSearchResults(),
224
+ description="A wrapper around DuckDuckGo Search. Useful for when you need to answer questions about current events. Input should be a search query. Output is a list of results.",
225
  ),
 
226
  Tool(
227
  name="File Reader from URL",
228
  func=file_reader_tool,
229
+ description="Reads the content of a file from a URL and returns the first 5 rows as a string. It can handle Excel (.xlsx, .xls) and CSV (.csv) files. The URL must point directly to the file.",
230
  ),
231
  WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
232
  Tool(
233
  name="YouTube Transcript Reader",
234
  func=youtube_transcript_tool,
235
+ description="Fetches the full transcript of a YouTube video from its URL. Use this to answer questions about the content of a video.",
236
  ),
237
  Tool(
238
  name="Web Page Reader",
239
  func=web_reader_tool,
240
+ description="Reads the text content of a web page from a URL. Use this for any URL that doesn't point directly to a file like CSV or Excel.",
241
  ),
242
  Tool(
243
  name="Audio Analyzer",
244
  func=audio_analyzer_tool,
245
+ description="Analyzes audio content from a URL by transcribing it into text. The URL must point directly to an audio file (e.g., .wav, .mp3).",
246
  ),
247
  Tool(
248
  name="Video Analyzer",
249
  func=video_analyzer_tool,
250
+ description="Analyzes the audio track of a video from a URL by transcribing it into text. The URL must point directly to a video file (e.g., .mp4).",
251
  ),
252
  ]
253
+
254
  # A more direct, instruction-based prompt with high-quality examples.
255
  gemma_react_prompt = '''<start_of_turn>user
256
  You are a helpful agent. Your goal is to answer the user's question.
 
263
  2. **Action:** Choose one of the available tools for your 'Action'. Your 'Action' must be one of [{tool_names}].
264
  3. **Action Input:** Provide the input for that tool. The input should be a simple string, not a Python expression.
265
  4. **Observation:** Wait for the result of the tool.
266
+ 5. **Thought:** Analyze the result and the current plan. If a tool fails, you MUST try to recover by using a different tool or a different input. If you get stuck, you MUST try to search for the information using DuckDuckGo Search Results. If you find a relevant URL, you can use the Web Page Reader tool to read its content. If the plan needs to be revised, state the new plan. Then, proceed with the next step.
267
  6. Repeat steps 2-5 until you have enough information to answer the question.
268
  7. **Thought:** Once you have the final answer, you MUST use the following template: `FINAL ANSWER: [YOUR FINAL ANSWER]`.
269
 
 
276
 
277
  Do not make up observations.
278
 
279
+ Here are some examples:
280
 
281
  Question: In the YouTube video at `https://www.youtube.com/watch?v=example`, the speaker mentions a specific battle. Use the transcript to find the name of the battle, then find out which year it took place.
282
 
 
296
  Thought: I have found the year of the battle, 1066. I have completed all steps of my plan.
297
  FINAL ANSWER: Battle of Hastings, 1066
298
 
299
+ Question: How many studio albums did Mercedes Sosa release between 2000 and 2009?
300
+
301
+ Thought:
302
+ My plan is:
303
+ 1. Search for Mercedes Sosa's discography.
304
+ 2. From the search results, find a reliable source like Wikipedia or a music database.
305
+ 3. Read the content of the source.
306
+ 4. Count the number of studio albums released between 2000 and 2009.
307
+ I will now execute step 1.
308
+ Action: DuckDuckGo Search Results
309
+ Action Input: Mercedes Sosa discography
310
+ Observation: [snippet: Mercedes Sosa discography. Studio albums. Year, Album, Label. 1959, La voz de la zafra, RCA Victor. ... 2000, Acústico, ... 2005, Corazón libre, ... 2009, Cantora, ... link: https://en.wikipedia.org/wiki/Mercedes_Sosa_discography], ...
311
+ Thought: The search results show a Wikipedia page that seems to contain the discography. I will read the content of that page.
312
+ Action: Web Page Reader
313
+ Action Input: https://en.wikipedia.org/wiki/Mercedes_Sosa_discography
314
+ Observation: ...Studio albums... 2000 Acústico ... 2005 Corazón libre ... 2009 Cantora ...
315
+ Thought: I have the list of albums. Now I need to count the ones between 2000 and 2009. I see "Acústico" (2000), "Corazón libre" (2005), and "Cantora" (2009). That's 3 albums.
316
+ FINAL ANSWER: 3
317
+
318
  Begin!
319
 
320
  Question: {input}
 
322
  <start_of_turn>model
323
  '''
324
  prompt = PromptTemplate.from_template(gemma_react_prompt)
325
+
326
  agent = create_react_agent(self.llm, tools, prompt)
327
+
328
+ self.agent_executor = AgentExecutor(
329
+ agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
330
+ )
331
 
332
  def __call__(self, question: str) -> str:
333
  print(f"Agent received question (first 50 chars): {question[:50]}...")