.gitignore DELETED
@@ -1,6 +0,0 @@
1
- .git
2
- tools/__pycache__
3
- *.py~
4
- __pycache__/
5
- .env
6
-
 
 
 
 
 
 
 
agent/__init__.py DELETED
@@ -1,2 +0,0 @@
1
- # tools/__init__.py
2
-
 
 
 
agent/gaia_agent.py DELETED
@@ -1,94 +0,0 @@
1
- import os
2
- from langchain.agents import initialize_agent, AgentType
3
- from langchain.tools import Tool
4
- #from langchain_community.llms import OpenAI
5
- from tools.wikipedia_tool import wiki_search
6
- from tools.vegetable_classifier_tool import vegetable_classifier_2022
7
- from tools.vegetable_classifier_tool import vegetable_classifier_2022
8
- from tools.excel_sum_tool import excel_food_sales_sum
9
- from tools.audio_transcriber import transcribe_audio
10
- from tools.file_parser import parse_file_and_summarize
11
- from tools.image_chess_solver import solve_chess_image
12
- from tools.youtube_tool import extract_video_id, get_youtube_transcript
13
- from langchain.agents.agent import AgentExecutor
14
- from langchain_community.chat_models import ChatOpenAI
15
-
16
- def create_langchain_agent() -> AgentExecutor:
17
- llm = ChatOpenAI(
18
- model_name="gpt-4o",
19
- temperature=0.1,
20
- openai_api_key=os.getenv("OPENAI_API_KEY"),
21
- )
22
-
23
- tools = [
24
- Tool(name="wikipedia_search", func=wiki_search, description="Use to get factual info from Wikipedia.", return_direct=False),
25
- Tool(name="youtube_transcript", func=get_youtube_transcript, description="Extract transcripts from YouTube videos.", return_direct=False),
26
- Tool(name="audio_transcriber", func=transcribe_audio, description="Transcribe uploaded audio files."),
27
- Tool(name="chess_image_solver", func=solve_chess_image, description="Solve chess puzzles from images."),
28
- Tool(name="file_parser", func=parse_file_and_summarize, description="Parse and analyze Excel or CSV files."),
29
- Tool(name="vegetable_classifier_2022", func=vegetable_classifier_2022, description="Classify and extract only vegetables, excluding botanical fruits, based on a comma-separated list of food items."),
30
- Tool(name="excel_food_sales_sum", func=excel_food_sales_sum, description="Parse uploaded Excel file and return total food-related sales."),
31
- ]
32
-
33
- agent_kwargs = {
34
- "prefix": (
35
- "You are a helpful AI assistant completing GAIA benchmark tasks.\n"
36
- "You MUST use the tools provided to answer the user's question. Do not answer from your own knowledge.\n"
37
- "Carefully analyze the question to determine the most appropriate tool to use.\n"
38
- "Here are guidelines for using the tools:\n"
39
- "- Use 'wikipedia_search' to find factual information about topics, events, people, etc. (e.g., 'Use wikipedia_search to find the population of France').\n"
40
- "- Use 'youtube_transcript' to extract transcripts from YouTube videos when the question requires understanding the video content. (e.g., 'Use youtube_transcript to summarize the key points of this video').\n"
41
- "- Use 'audio_transcriber' to transcribe uploaded audio files. (e.g., 'Use audio_transcriber to get the text from this audio recording').\n"
42
- "- Use 'chess_image_solver' to analyze and solve chess puzzles from images. (e.g., 'Use chess_image_solver to determine the best move in this chess position').\n"
43
- "- Use 'file_parser' to parse and analyze data from Excel or CSV files. (e.g., 'Use file_parser to calculate the average sales from this data').\n"
44
- "- Use 'vegetable_classifier_2022' to classify a list of food items and extract only the vegetables. (e.g., 'Use vegetable_classifier_2022 to get a list of the vegetables in this grocery list').\n"
45
- "- Use 'excel_food_sales_sum' to extract total food sales from excel files. (e.g., 'Use excel_food_sales_sum to calculate the total food sales').\n"
46
- "Do NOT guess or make up answers. If a tool cannot provide the answer, truthfully respond that you were unable to find the information.\n"
47
- ),
48
- "suffix": (
49
- "Use the tools to research or calculate the answer.\n"
50
- "If a tool fails, explain the reason for the failure instead of hallucinating an answer.\n"
51
- "Provide concise and direct answers as requested in the questions. Do not add extra information unless explicitly asked for.\n"
52
- "For example, if asked for a number, return only the number. If asked for a list, return only the list.\n"
53
- ),
54
- "input_variables": ["input", "agent_scratchpad"]
55
- }
56
-
57
- agent = initialize_agent(
58
- tools=tools,
59
- llm=llm,
60
- agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
61
- handle_parsing_errors=True,
62
- verbose=True,
63
- max_iterations=10,
64
- max_execution_time=60,
65
- agent_kwargs=agent_kwargs # Place the agent_kwargs here
66
- )
67
-
68
- return agent
69
-
70
-
71
- '''
72
- agent = initialize_agent(
73
- tools=tools,
74
- llm=llm,
75
- agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
76
- handle_parsing_errors=True,
77
- verbose=True,
78
- max_iterations=10,
79
- max_execution_time=60,
80
- agent_kwargs={
81
- "prefix": (
82
- "You are a helpful AI assistant completing GAIA benchmark tasks.\n"
83
- "You must ALWAYS use the tools provided to answer the user's question.\n"
84
- "Do NOT guess or respond directly from your own knowledge.\n"
85
- ),
86
- "suffix": (
87
- "Use the tools to research or calculate the answer.\n"
88
- "If a tool fails, explain that instead of hallucinating an answer.\n"
89
- "Return only the final answer as a short response (e.g., a number, name, or sentence).\n"
90
- ),
91
- }
92
- )
93
- '''
94
- return agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -4,18 +4,6 @@ import requests
4
  import inspect
5
  import pandas as pd
6
 
7
- #from langchain.agents import initialize_agent, AgentType
8
- from agent.gaia_agent import create_langchain_agent
9
- #from langchain_community.llms import OpenAI
10
- from langchain.tools import Tool
11
- from agent.gaia_agent import create_langchain_agent
12
-
13
- from tools.wikipedia_tool import wiki_search
14
- from tools.youtube_tool import get_youtube_transcript
15
- from tools.audio_transcriber import transcribe_audio
16
- from tools.image_chess_solver import solve_chess_image
17
- from tools.file_parser import parse_file_and_summarize
18
-
19
  # (Keep Constants as is)
20
  # --- Constants ---
21
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
@@ -24,25 +12,14 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
24
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
25
  class BasicAgent:
26
  def __init__(self):
27
- print("Initializing LangChain Agent...")
28
- self.agent = create_langchain_agent()
29
-
30
  def __call__(self, question: str) -> str:
31
- try:
32
- result = self.agent.invoke({"input": question})
33
- if isinstance(result, dict) and "output" in result:
34
- return result["output"]
35
- return result
36
- except Exception as e:
37
- return f"Agent error: {e}"
38
-
39
-
40
- def manual_test(question):
41
- agent = BasicAgent()
42
- return agent(question)
43
 
44
  def run_and_submit_all( profile: gr.OAuthProfile | None):
45
-
46
  """
47
  Fetches all questions, runs the BasicAgent on them, submits all answers,
48
  and displays the results.
@@ -169,9 +146,11 @@ with gr.Blocks() as demo:
169
  gr.Markdown(
170
  """
171
  **Instructions:**
 
172
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
173
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
174
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
175
  ---
176
  **Disclaimers:**
177
  Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
@@ -180,14 +159,6 @@ with gr.Blocks() as demo:
180
  )
181
 
182
  gr.LoginButton()
183
-
184
- manual_input = gr.Textbox(label="Try the Agent Manually", placeholder="How many studio albums where published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest v2022 version of wikipedia.")
185
- manual_output = gr.Textbox(label="Agent Response", lines=4, interactive=False)
186
- manual_test_button = gr.Button("Run Agent Locally")
187
- manual_test_button.click(fn=manual_test, inputs=[manual_input], outputs=[manual_output])
188
-
189
- print(manual_input.placeholder )
190
-
191
 
192
  run_button = gr.Button("Run Evaluation & Submit All Answers")
193
 
@@ -200,7 +171,6 @@ with gr.Blocks() as demo:
200
  outputs=[status_output, results_table]
201
  )
202
 
203
-
204
  if __name__ == "__main__":
205
  print("\n" + "-"*30 + " App Starting " + "-"*30)
206
  # Check for SPACE_HOST and SPACE_ID at startup for information
@@ -221,10 +191,6 @@ if __name__ == "__main__":
221
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
222
 
223
  print("-"*(60 + len(" App Starting ")) + "\n")
224
-
225
- #from tools.wikipedia_tool import wiki_search
226
- #print(wiki_search("Mercedes Sosa discography"))
227
-
228
 
229
  print("Launching Gradio Interface for Basic Agent Evaluation...")
230
  demo.launch(debug=True, share=False)
 
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"
 
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,
25
  and displays the results.
 
146
  gr.Markdown(
147
  """
148
  **Instructions:**
149
+
150
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
+
154
  ---
155
  **Disclaimers:**
156
  Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
 
159
  )
160
 
161
  gr.LoginButton()
 
 
 
 
 
 
 
 
162
 
163
  run_button = gr.Button("Run Evaluation & Submit All Answers")
164
 
 
171
  outputs=[status_output, results_table]
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
 
191
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
192
 
193
  print("-"*(60 + len(" App Starting ")) + "\n")
 
 
 
 
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
  demo.launch(debug=True, share=False)
local_benchmark.py DELETED
@@ -1,26 +0,0 @@
1
- from agent.gaia_agent import create_langchain_agent
2
-
3
- questions = [
4
- "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)?",
5
- "In the video https://www.youtube.com/watch?v=u1xXCYZ4VYM, what is the highest number of bird species to be on screen at once?",
6
- "Reverse the string 'etisoppo eht'.",
7
- "What country had the least number of athletes at the 1928 Summer Olympics? Return the IOC country code.",
8
- "From the chessboard image at path 'chess_1.png', what is the best move?",
9
- "The attached Excel file contains food and drink sales. What are the total sales for food (excluding drinks)?",
10
- "Give me a comma-separated, alphabetized list of botanical vegetables from this: milk, eggs, flour, plums, lettuce, celery, broccoli, bell pepper, zucchini.",
11
- "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina’s 2010 paper eventually deposited? (City name only.)",
12
- "What is the name of the novel where a Martian child grows up on Earth and founds a church?",
13
- "Summarize the Wikipedia page on 'Taishō Tamai'."
14
- ]
15
-
16
- agent = create_langchain_agent()
17
-
18
- print("Running local benchmark...")
19
-
20
- for idx, question in enumerate(questions):
21
- print(f"\nQUESTION {idx + 1}: {question}")
22
- try:
23
- answer = agent.invoke({"input": question})
24
- print("ANSWER:", answer)
25
- except Exception as e:
26
- print("❌ Error:", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,20 +1,2 @@
1
  gradio
2
- requests
3
- transformers
4
- huggingface_hub
5
- langchain-community
6
- langchain
7
- openai
8
- wikipedia
9
- pytube
10
- whisper
11
- pillow
12
- opencv-python
13
- python-docx
14
- PyMuPDF
15
- youtube-transcript-api
16
- openai-whisper
17
- ffmpeg-python
18
- python-chess
19
- openpyxl
20
- gradio[oauth]
 
1
  gradio
2
+ requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/__init__.py DELETED
@@ -1,2 +0,0 @@
1
- # tools/__init__.py
2
-
 
 
 
tools/audio_transcriber.py DELETED
@@ -1,25 +0,0 @@
1
- # tools/audio_transcriber.py
2
-
3
- import os
4
- import tempfile
5
- import whisper
6
-
7
- # Load Whisper model only once (tiny, base, or small recommended for speed)
8
- MODEL_NAME = "base"
9
- whisper_model = whisper.load_model(MODEL_NAME)
10
-
11
- def transcribe_audio(audio_file_path: str) -> str:
12
- """
13
- Transcribes speech from an audio file using OpenAI Whisper.
14
-
15
- Args:
16
- audio_file_path (str): Path to the local audio file (.mp3, .wav, etc.).
17
-
18
- Returns:
19
- str: Transcribed text or error message.
20
- """
21
- try:
22
- result = whisper_model.transcribe(audio_file_path)
23
- return result["text"].strip()
24
- except Exception as e:
25
- return f"Transcription error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/excel_sum_tool.py DELETED
@@ -1,17 +0,0 @@
1
-
2
- from langchain.tools import tool
3
-
4
- @tool
5
- def excel_food_sales_sum(file_path: str) -> str:
6
- """
7
- Parses the Excel file and returns total sales of items classified as food.
8
- Assumes 'Item Type' and 'Sales USD' columns.
9
- """
10
- try:
11
- df = pd.read_excel(file_path)
12
- df.columns = [col.strip().lower() for col in df.columns]
13
- food_rows = df[df['item type'].str.lower().str.contains("food")]
14
- total = food_rows['sales usd'].sum()
15
- return f"{total:.2f}"
16
- except Exception as e:
17
- return f"Excel parsing failed: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/file_parser.py DELETED
@@ -1,44 +0,0 @@
1
- # tools/file_parser.py
2
-
3
- import pandas as pd
4
- import os
5
-
6
- def parse_file_and_summarize(file_path: str, query: str = "") -> str:
7
- """
8
- Reads a CSV or Excel file and optionally answers a simple question about it.
9
-
10
- Args:
11
- file_path (str): Path to the file (.csv or .xlsx).
12
- query (str): Optional freeform instruction (e.g. "total food sales").
13
-
14
- Returns:
15
- str: Summary or result from the file.
16
- """
17
- try:
18
- _, ext = os.path.splitext(file_path.lower())
19
- if ext == ".csv":
20
- df = pd.read_csv(file_path)
21
- elif ext in [".xls", ".xlsx"]:
22
- df = pd.read_excel(file_path)
23
- else:
24
- return "Unsupported file format. Please upload CSV or Excel."
25
-
26
- if df.empty:
27
- return "The file is empty or unreadable."
28
-
29
- if not query:
30
- return f"Loaded file with {df.shape[0]} rows and {df.shape[1]} columns.\nColumns: {', '.join(df.columns)}"
31
-
32
- # Very basic natural language query handling (expand with LLM if needed)
33
- if "total" in query.lower() and "food" in query.lower():
34
- food_rows = df[df['category'].str.lower() == "food"]
35
- if "sales" in df.columns:
36
- total = food_rows["sales"].sum()
37
- return f"Total food sales: ${total:.2f}"
38
- else:
39
- return "Could not find 'sales' column in the file."
40
- else:
41
- return "Query not supported. Please specify a clearer question."
42
-
43
- except Exception as e:
44
- return f"File parsing error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/image_chess_solver.py DELETED
@@ -1,50 +0,0 @@
1
- # tools/image_chess_solver.py
2
-
3
- import chess
4
- import chess.engine
5
- import tempfile
6
- from PIL import Image
7
-
8
- # Path to your Stockfish binary (update if needed)
9
- STOCKFISH_PATH = "/usr/bin/stockfish"
10
-
11
- def analyze_position_from_fen(fen: str, time_limit: float = 1.0) -> str:
12
- """
13
- Uses Stockfish to analyze the best move from a given FEN string.
14
-
15
- Args:
16
- fen (str): Forsyth–Edwards Notation of the board.
17
- time_limit (float): Time to let Stockfish think.
18
-
19
- Returns:
20
- str: Best move in algebraic notation.
21
- """
22
- try:
23
- board = chess.Board(fen)
24
- engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
25
- result = engine.play(board, chess.engine.Limit(time=time_limit))
26
- engine.quit()
27
- return board.san(result.move)
28
- except Exception as e:
29
- return f"Stockfish error: {e}"
30
-
31
- def solve_chess_image(image_path: str) -> str:
32
- """
33
- Stub function for image-to-FEN. Replace with actual OCR/vision logic.
34
-
35
- Args:
36
- image_path (str): Path to chessboard image.
37
-
38
- Returns:
39
- str: Best move or error.
40
- """
41
- # Placeholder FEN for development (e.g., black to move, guaranteed mate)
42
- sample_fen = "6k1/5ppp/8/8/8/8/5PPP/6K1 b - - 0 1"
43
-
44
- try:
45
- print(f"Simulating FEN extraction from image: {image_path}")
46
- # Replace the above with actual OCR image-to-FEN logic
47
- best_move = analyze_position_from_fen(sample_fen)
48
- return f"Detected FEN: {sample_fen}\nBest move for Black: {best_move}"
49
- except Exception as e:
50
- return f"Image analysis error: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/vegetable_classifier_tool.py DELETED
@@ -1,17 +0,0 @@
1
- # vegetable_classifier_tool.py
2
- from langchain.tools import tool
3
-
4
- @tool
5
- def vegetable_classifier_2022(question: str) -> str:
6
- """
7
- Classifies common grocery items from a 2022 Wikipedia-based classification.
8
- Returns a comma-separated list of vegetables excluding all botanical fruits.
9
- """
10
- known_vegetables = {
11
- "broccoli", "celery", "lettuce", "zucchini", "green beans",
12
- "sweet potatoes", "corn", "acorns", "peanuts", "rice", "flour"
13
- }
14
- # Accept question but only extract known food items
15
- input_items = [item.strip().lower() for item in question.split(',')]
16
- found = sorted([item for item in input_items if item in known_vegetables])
17
- return ", ".join(found)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/wikipedia_tool.py DELETED
@@ -1,29 +0,0 @@
1
- # tools/wikipedia_tool.py
2
- import wikipedia
3
-
4
- wikipedia.set_lang("en")
5
-
6
- def wiki_search(query: str) -> str:
7
- """
8
- Safe Wikipedia summary tool with disambiguation and fallback protection.
9
- """
10
- try:
11
- return wikipedia.summary(query, sentences=3)
12
- except wikipedia.DisambiguationError as e:
13
- # Try the first disambiguation option if available
14
- if e.options:
15
- try:
16
- return wikipedia.summary(e.options[0], sentences=3)
17
- except Exception as inner:
18
- return f"Disambiguation fallback failed: {inner}"
19
- return "Disambiguation error: No options available."
20
- except wikipedia.PageError:
21
- search_results = wikipedia.search(query)
22
- if not search_results:
23
- return "No relevant Wikipedia page found."
24
- try:
25
- return wikipedia.summary(search_results[0], sentences=3)
26
- except Exception as inner:
27
- return f"Wikipedia fallback summary error: {inner}"
28
- except Exception as e:
29
- return f"Wikipedia general error: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tools/youtube_tool.py DELETED
@@ -1,47 +0,0 @@
1
- # tools/youtube_tool.py
2
-
3
- from youtube_transcript_api import YouTubeTranscriptApi
4
- from youtube_transcript_api._errors import TranscriptsDisabled, NoTranscriptFound
5
- import re
6
-
7
- def extract_video_id(url: str) -> str:
8
- """
9
- Extracts the video ID from a YouTube URL.
10
-
11
- Args:
12
- url (str): The full YouTube video URL.
13
-
14
- Returns:
15
- str: The extracted video ID or raises ValueError.
16
- """
17
- patterns = [
18
- r"youtube\.com/watch\?v=([a-zA-Z0-9_-]{11})",
19
- r"youtu\.be/([a-zA-Z0-9_-]{11})"
20
- ]
21
- for pattern in patterns:
22
- match = re.search(pattern, url)
23
- if match:
24
- return match.group(1)
25
- raise ValueError("Invalid YouTube URL or unable to extract video ID.")
26
-
27
- def get_youtube_transcript(url: str) -> str:
28
- """
29
- Fetches the transcript text for a given YouTube video.
30
-
31
- Args:
32
- url (str): The YouTube video URL.
33
-
34
- Returns:
35
- str: Combined transcript text or an error message.
36
- """
37
- try:
38
- video_id = extract_video_id(url)
39
- transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
40
- full_text = " ".join([entry["text"] for entry in transcript_list])
41
- return full_text.strip()[:2000] # Truncate to 2000 chars to prevent token overflow
42
- except TranscriptsDisabled:
43
- return "This video has transcripts disabled."
44
- except NoTranscriptFound:
45
- return "No transcript was found for this video."
46
- except Exception as e:
47
- return f"Transcript error: {str(e)}"