Lasdw commited on
Commit
158215f
·
1 Parent(s): bbb069d

updated input features to be processed by the agent

Browse files
Files changed (10) hide show
  1. .gitattributes +0 -36
  2. .gitignore +6 -0
  3. TEMPP/chess.png +0 -0
  4. TEMPP/code.py +0 -35
  5. TEMPP/excel.xlsx +0 -0
  6. agent.py +236 -20
  7. app.py +63 -15
  8. requirements.txt +3 -0
  9. run_code.py +30 -0
  10. tools.py +440 -191
.gitattributes DELETED
@@ -1,36 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- *.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -15,3 +15,9 @@ __pycache__/*
15
  *.pyo
16
  *.pyd
17
 
 
 
 
 
 
 
 
15
  *.pyo
16
  *.pyd
17
 
18
+ TEMPP/*
19
+ TEMPP/chess.png
20
+ TEMPP/audio1.mp3
21
+ TEMPP/code.py
22
+ TEMPP/audio2.mp3
23
+ TEMPP/excel.xlsx
TEMPP/chess.png DELETED
Binary file (130 Bytes)
 
TEMPP/code.py DELETED
@@ -1,35 +0,0 @@
1
- from random import randint
2
- import time
3
-
4
- class UhOh(Exception):
5
- pass
6
-
7
- class Hmm:
8
- def __init__(self):
9
- self.value = randint(-100, 100)
10
-
11
- def Yeah(self):
12
- if self.value == 0:
13
- return True
14
- else:
15
- raise UhOh()
16
-
17
- def Okay():
18
- while True:
19
- yield Hmm()
20
-
21
- def keep_trying(go, first_try=True):
22
- maybe = next(go)
23
- try:
24
- if maybe.Yeah():
25
- return maybe.value
26
- except UhOh:
27
- if first_try:
28
- print("Working...")
29
- print("Please wait patiently...")
30
- time.sleep(0.1)
31
- return keep_trying(go, first_try=False)
32
-
33
- if __name__ == "__main__":
34
- go = Okay()
35
- print(f"{keep_trying(go)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
TEMPP/excel.xlsx DELETED
Binary file (5.29 kB)
 
agent.py CHANGED
@@ -30,7 +30,9 @@ from tools import (
30
  save_attachment_to_tempfile,
31
  process_youtube_video,
32
  transcribe_audio,
33
- extract_python_code_from_complex_input
 
 
34
  )
35
 
36
  load_dotenv()
@@ -60,7 +62,7 @@ The way you use the tools is by specifying a json blob.
60
  Specifically, this json should have an `action` key (with the name of the tool to use) and an `action_input` key (with the input to the tool going here).
61
 
62
  The only values that should be in the "action" field are:
63
- python_code: Execute Python code. Use this tool to calculate math problems. args: {"code": {"type": "string"}}
64
  wikipedia_search: Search Wikipedia for information about a specific topic. Optionally specify the number of results to return, args: {"query": {"type": "string"}, "num_results": {"type": "integer", "optional": true}}
65
  tavily_search: Search the web using Tavily for more comprehensive results. Optionally specify search_depth as 'basic' or 'comprehensive', args: {"query": {"type": "string"}, "search_depth": {"type": "string", "optional": true}}
66
  arxiv_search: Search ArXiv for scientific papers. Optionally specify max_results to control the number of papers returned, args: {"query": {"type": "string"}, "max_results": {"type": "integer", "optional": true}}
@@ -69,6 +71,8 @@ supabase_operation: Perform database operations, args: {"operation_type": {"type
69
  excel_to_text: Convert Excel to Markdown table with attachment, args: {"excel_path": {"type": "string"}, "file_content": {"type": "string"}, "sheet_name": {"type": "string", "optional": true}}
70
  process_youtube_video: Extract and analyze YouTube video content by providing the video URL. Returns video metadata and transcript, args: {"url": {"type": "string"}, "summarize": {"type": "boolean", "optional": true}}
71
  transcribe_audio: Transcribe audio files using OpenAI Whisper, args: {"audio_path": {"type": "string"}, "file_content": {"type": "string", "optional": true}, "language": {"type": "string", "optional": true}}
 
 
72
 
73
  If you get stuck, try using another tool. For example if you are unable to find relevant information from the tavily_search tool, try using the wikipedia_search tool and vice versa.
74
  IMPORTANT: Make sure your JSON is properly formatted with double quotes around keys and string values.
@@ -88,6 +92,13 @@ or
88
  "action_input": {"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "summarize": true}
89
  }
90
  ```
 
 
 
 
 
 
 
91
 
92
  ALWAYS follow this specific format for your responses. Your entire response will follow this pattern:
93
  Question: [the user's question]
@@ -830,17 +841,20 @@ def excel_to_text_node(state: AgentState) -> Dict[str, Any]:
830
  sheet_name = action_input.get("sheet_name")
831
 
832
  # Check if there's attached file content (base64 encoded) directly in the action_input
833
- if "file_content" in action_input:
834
  try:
835
  file_content = base64.b64decode(action_input["file_content"])
836
  print(f"Decoded attached file content, size: {len(file_content)} bytes")
837
  except Exception as e:
838
- print(f"Error decoding file content: {e}")
 
839
  # Check if we should use a file from the attachments dictionary
840
- elif excel_path and "attachments" in state and excel_path in state["attachments"]:
841
  try:
842
- file_content = base64.b64decode(state["attachments"][excel_path])
843
- print(f"Using attachment '{excel_path}' from state, size: {len(file_content)} bytes")
 
 
844
  except Exception as e:
845
  print(f"Error using attachment {excel_path}: {e}")
846
 
@@ -849,8 +863,17 @@ def excel_to_text_node(state: AgentState) -> Dict[str, Any]:
849
  # Safety check
850
  if not excel_path and not file_content:
851
  result = "Error: Either Excel file path or file content is required"
 
 
 
 
 
 
 
 
 
852
  else:
853
- # Call the Excel to text function
854
  result = excel_to_text(excel_path, sheet_name, file_content)
855
 
856
  print(f"Excel to text result length: {len(result)}")
@@ -903,11 +926,14 @@ def process_youtube_video_node(state: AgentState) -> Dict[str, Any]:
903
  # Safety check - don't run with empty URL
904
  if not url:
905
  result = "Error: No URL provided. Please provide a valid YouTube URL."
 
 
906
  else:
907
- # Import the function dynamically to ensure we're using the latest version
908
- from tools import process_youtube_video
909
  # Call the YouTube processing function
910
- result = process_youtube_video(url, summarize)
 
 
 
911
 
912
  print(f"YouTube processing result length: {len(result)}")
913
 
@@ -948,27 +974,39 @@ def transcribe_audio_node(state: AgentState) -> Dict[str, Any]:
948
  language = action_input.get("language")
949
 
950
  # Check if there's attached file content (base64 encoded) directly in the action_input
951
- if "file_content" in action_input:
952
  try:
953
  file_content = base64.b64decode(action_input["file_content"])
954
  print(f"Decoded attached audio file content, size: {len(file_content)} bytes")
955
  except Exception as e:
956
- print(f"Error decoding file content: {e}")
 
957
  # Check if we should use a file from the attachments dictionary
958
- elif audio_path and "attachments" in state and audio_path in state["attachments"]:
959
  try:
960
- file_content = base64.b64decode(state["attachments"][audio_path])
961
- print(f"Using attachment '{audio_path}' from state, size: {len(file_content)} bytes")
 
 
962
  except Exception as e:
963
  print(f"Error using attachment {audio_path}: {e}")
964
 
965
  print(f"Audio transcription: path={audio_path}, language={language or 'auto-detect'}, has_attachment={file_content is not None}")
966
 
967
  # Safety check
968
- if not audio_path and not file_content:
969
- result = "Error: Either audio file path or file content is required"
 
 
 
 
 
 
 
 
 
970
  else:
971
- # Call the audio transcription function
972
  result = transcribe_audio(audio_path, file_content, language)
973
 
974
  print(f"Audio transcription result length: {len(result)}")
@@ -991,6 +1029,173 @@ def transcribe_audio_node(state: AgentState) -> Dict[str, Any]:
991
  "action_input": None # Clear the action input
992
  }
993
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
994
  # Router function to direct to the correct tool
995
  def router(state: AgentState) -> str:
996
  """Route to the appropriate tool based on the current_tool field."""
@@ -1017,6 +1222,10 @@ def router(state: AgentState) -> str:
1017
  return "process_youtube_video"
1018
  elif tool == "transcribe_audio":
1019
  return "transcribe_audio"
 
 
 
 
1020
  else:
1021
  return "end"
1022
 
@@ -1036,6 +1245,8 @@ def create_agent_graph() -> StateGraph:
1036
  builder.add_node("excel_to_text", excel_to_text_node)
1037
  builder.add_node("process_youtube_video", process_youtube_video_node)
1038
  builder.add_node("transcribe_audio", transcribe_audio_node)
 
 
1039
 
1040
  # Define edges: these determine how the control flow moves
1041
  builder.add_edge(START, "assistant")
@@ -1069,6 +1280,8 @@ def create_agent_graph() -> StateGraph:
1069
  "excel_to_text": "excel_to_text",
1070
  "process_youtube_video": "process_youtube_video",
1071
  "transcribe_audio": "transcribe_audio",
 
 
1072
  "end": END
1073
  }
1074
  )
@@ -1083,6 +1296,8 @@ def create_agent_graph() -> StateGraph:
1083
  builder.add_edge("excel_to_text", "assistant")
1084
  builder.add_edge("process_youtube_video", "assistant")
1085
  builder.add_edge("transcribe_audio", "assistant")
 
 
1086
 
1087
  # Compile the graph
1088
  return builder.compile()
@@ -1145,13 +1360,14 @@ class TurboNerd:
1145
  # Extract the final message and return just the final answer
1146
  final_message = result["messages"][-1].content
1147
  print("Final message: ", final_message)
 
1148
  # Extract just the final answer part
1149
  if "Final Answer:" in final_message:
1150
  final_answer = final_message.split("Final Answer:", 1)[1].strip()
1151
  return final_answer
1152
 
1153
  return final_message
1154
-
1155
  except Exception as e:
1156
  print(f"Error processing question: {str(e)}")
1157
  # Otherwise return the error
 
30
  save_attachment_to_tempfile,
31
  process_youtube_video,
32
  transcribe_audio,
33
+ extract_python_code_from_complex_input,
34
+ process_image,
35
+ read_file
36
  )
37
 
38
  load_dotenv()
 
62
  Specifically, this json should have an `action` key (with the name of the tool to use) and an `action_input` key (with the input to the tool going here).
63
 
64
  The only values that should be in the "action" field are:
65
+ python_code: Execute Python code. Use this tool to calculate math problems. make sure to use prints to be able to view the final result. args: {"code": {"type": "string"}}
66
  wikipedia_search: Search Wikipedia for information about a specific topic. Optionally specify the number of results to return, args: {"query": {"type": "string"}, "num_results": {"type": "integer", "optional": true}}
67
  tavily_search: Search the web using Tavily for more comprehensive results. Optionally specify search_depth as 'basic' or 'comprehensive', args: {"query": {"type": "string"}, "search_depth": {"type": "string", "optional": true}}
68
  arxiv_search: Search ArXiv for scientific papers. Optionally specify max_results to control the number of papers returned, args: {"query": {"type": "string"}, "max_results": {"type": "integer", "optional": true}}
 
71
  excel_to_text: Convert Excel to Markdown table with attachment, args: {"excel_path": {"type": "string"}, "file_content": {"type": "string"}, "sheet_name": {"type": "string", "optional": true}}
72
  process_youtube_video: Extract and analyze YouTube video content by providing the video URL. Returns video metadata and transcript, args: {"url": {"type": "string"}, "summarize": {"type": "boolean", "optional": true}}
73
  transcribe_audio: Transcribe audio files using OpenAI Whisper, args: {"audio_path": {"type": "string"}, "file_content": {"type": "string", "optional": true}, "language": {"type": "string", "optional": true}}
74
+ process_image: Process and analyze image files, args: {"image_path": {"type": "string"}, "image_url": {"type": "string", "optional": true}, "file_content": {"type": "string", "optional": true}, "analyze_content": {"type": "boolean", "optional": true}}
75
+ read_file: Read and display the contents of a text file, args: {"file_path": {"type": "string"}, "file_content": {"type": "string", "optional": true}, "line_start": {"type": "integer", "optional": true}, "line_end": {"type": "integer", "optional": true}}
76
 
77
  If you get stuck, try using another tool. For example if you are unable to find relevant information from the tavily_search tool, try using the wikipedia_search tool and vice versa.
78
  IMPORTANT: Make sure your JSON is properly formatted with double quotes around keys and string values.
 
92
  "action_input": {"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "summarize": true}
93
  }
94
  ```
95
+ or
96
+ ```json
97
+ {
98
+ "action": "process_image",
99
+ "action_input": {"image_path": "example.jpg", "analyze_content": true}
100
+ }
101
+ ```
102
 
103
  ALWAYS follow this specific format for your responses. Your entire response will follow this pattern:
104
  Question: [the user's question]
 
841
  sheet_name = action_input.get("sheet_name")
842
 
843
  # Check if there's attached file content (base64 encoded) directly in the action_input
844
+ if "file_content" in action_input and action_input["file_content"]:
845
  try:
846
  file_content = base64.b64decode(action_input["file_content"])
847
  print(f"Decoded attached file content, size: {len(file_content)} bytes")
848
  except Exception as e:
849
+ print(f"Error decoding file content from action_input: {e}")
850
+
851
  # Check if we should use a file from the attachments dictionary
852
+ if not file_content and excel_path and "attachments" in state and excel_path in state["attachments"]:
853
  try:
854
+ attachment_data = state["attachments"][excel_path]
855
+ if attachment_data: # Make sure it's not empty
856
+ file_content = base64.b64decode(attachment_data)
857
+ print(f"Using attachment '{excel_path}' from state, size: {len(file_content)} bytes")
858
  except Exception as e:
859
  print(f"Error using attachment {excel_path}: {e}")
860
 
 
863
  # Safety check
864
  if not excel_path and not file_content:
865
  result = "Error: Either Excel file path or file content is required"
866
+ elif not file_content:
867
+ # If we have a path but no content, check if it's a local file that exists
868
+ local_file_path = Path(excel_path).expanduser().resolve()
869
+ if local_file_path.is_file():
870
+ # Local file exists, use it directly
871
+ result = excel_to_text(str(local_file_path), sheet_name, None)
872
+ else:
873
+ # No file content and path doesn't exist as a local file
874
+ result = f"Error: Excel file not found at {local_file_path} and no attachment data available"
875
  else:
876
+ # We have file content, use it
877
  result = excel_to_text(excel_path, sheet_name, file_content)
878
 
879
  print(f"Excel to text result length: {len(result)}")
 
926
  # Safety check - don't run with empty URL
927
  if not url:
928
  result = "Error: No URL provided. Please provide a valid YouTube URL."
929
+ elif not url.startswith(("http://", "https://")) or not ("youtube.com" in url or "youtu.be" in url):
930
+ result = f"Error: Invalid YouTube URL format: {url}. Please provide a valid URL starting with http:// or https:// and containing youtube.com or youtu.be."
931
  else:
 
 
932
  # Call the YouTube processing function
933
+ try:
934
+ result = process_youtube_video(url, summarize)
935
+ except Exception as e:
936
+ result = f"Error processing YouTube video: {str(e)}\n\nThis could be due to:\n- The video is private or has been removed\n- Network connectivity issues\n- YouTube API changes\n- Rate limiting"
937
 
938
  print(f"YouTube processing result length: {len(result)}")
939
 
 
974
  language = action_input.get("language")
975
 
976
  # Check if there's attached file content (base64 encoded) directly in the action_input
977
+ if "file_content" in action_input and action_input["file_content"]:
978
  try:
979
  file_content = base64.b64decode(action_input["file_content"])
980
  print(f"Decoded attached audio file content, size: {len(file_content)} bytes")
981
  except Exception as e:
982
+ print(f"Error decoding file content from action_input: {e}")
983
+
984
  # Check if we should use a file from the attachments dictionary
985
+ if not file_content and audio_path and "attachments" in state and audio_path in state["attachments"]:
986
  try:
987
+ attachment_data = state["attachments"][audio_path]
988
+ if attachment_data: # Make sure it's not empty
989
+ file_content = base64.b64decode(attachment_data)
990
+ print(f"Using attachment '{audio_path}' from state, size: {len(file_content)} bytes")
991
  except Exception as e:
992
  print(f"Error using attachment {audio_path}: {e}")
993
 
994
  print(f"Audio transcription: path={audio_path}, language={language or 'auto-detect'}, has_attachment={file_content is not None}")
995
 
996
  # Safety check
997
+ if not audio_path:
998
+ result = "Error: Audio file path is required"
999
+ elif not file_content:
1000
+ # If we have a path but no content, check if it's a local file that exists
1001
+ local_file_path = Path(audio_path).expanduser().resolve()
1002
+ if local_file_path.is_file():
1003
+ # Local file exists, use it directly
1004
+ result = transcribe_audio(str(local_file_path), None, language)
1005
+ else:
1006
+ # No file content and path doesn't exist as a local file
1007
+ result = f"Error: Audio file not found at {local_file_path} and no attachment data available"
1008
  else:
1009
+ # We have file content, use it
1010
  result = transcribe_audio(audio_path, file_content, language)
1011
 
1012
  print(f"Audio transcription result length: {len(result)}")
 
1029
  "action_input": None # Clear the action input
1030
  }
1031
 
1032
+ def process_image_node(state: AgentState) -> Dict[str, Any]:
1033
+ """Node that processes image analysis requests."""
1034
+ print("Image Processing Tool Called...\n\n")
1035
+
1036
+ # Extract tool arguments
1037
+ action_input = state.get("action_input", {})
1038
+ print(f"Image processing action_input: {action_input}")
1039
+
1040
+ # Extract required parameters
1041
+ image_path = ""
1042
+ image_url = None
1043
+ analyze_content = True # Default to true
1044
+ file_content = None
1045
+
1046
+ if isinstance(action_input, dict):
1047
+ image_path = action_input.get("image_path", "")
1048
+ image_url = action_input.get("image_url")
1049
+
1050
+ # Check if analyze_content parameter exists and is a boolean
1051
+ if "analyze_content" in action_input:
1052
+ try:
1053
+ analyze_content = bool(action_input["analyze_content"])
1054
+ except:
1055
+ print("Invalid analyze_content parameter, using default (True)")
1056
+
1057
+ # Check if there's attached file content (base64 encoded) directly in the action_input
1058
+ if "file_content" in action_input and action_input["file_content"]:
1059
+ try:
1060
+ file_content = base64.b64decode(action_input["file_content"])
1061
+ print(f"Decoded attached image file content, size: {len(file_content)} bytes")
1062
+ except Exception as e:
1063
+ print(f"Error decoding file content from action_input: {e}")
1064
+
1065
+ # Check if we should use a file from the attachments dictionary
1066
+ if not file_content and image_path and "attachments" in state and image_path in state["attachments"]:
1067
+ try:
1068
+ attachment_data = state["attachments"][image_path]
1069
+ if attachment_data: # Make sure it's not empty
1070
+ file_content = base64.b64decode(attachment_data)
1071
+ print(f"Using attachment '{image_path}' from state, size: {len(file_content)} bytes")
1072
+ except Exception as e:
1073
+ print(f"Error using attachment {image_path}: {e}")
1074
+
1075
+ print(f"Image processing: path={image_path}, url={image_url or 'None'}, analyze_content={analyze_content}, has_attachment={file_content is not None}")
1076
+
1077
+ # Safety check
1078
+ if not image_path and not image_url and not file_content:
1079
+ result = "Error: Either image path, image URL, or file content is required"
1080
+ elif not file_content and not image_url:
1081
+ # If we have a path but no content, check if it's a local file that exists
1082
+ local_file_path = Path(image_path).expanduser().resolve()
1083
+ if local_file_path.is_file():
1084
+ # Local file exists, use it directly
1085
+ result = process_image(str(local_file_path), image_url, None, analyze_content)
1086
+ else:
1087
+ # No file content and path doesn't exist as a local file
1088
+ result = f"Error: Image file not found at {local_file_path} and no attachment data available"
1089
+ else:
1090
+ # We have file content or URL, use it
1091
+ result = process_image(image_path, image_url, file_content, analyze_content)
1092
+
1093
+ print(f"Image processing result length: {len(result)}")
1094
+
1095
+ # Format the observation to continue the ReAct cycle
1096
+ tool_message = AIMessage(
1097
+ content=f"Observation: {result.strip()}"
1098
+ )
1099
+
1100
+ # Print the observation that will be sent back to the assistant
1101
+ print("\n=== TOOL OBSERVATION ===")
1102
+ content_preview = tool_message.content[:500] + "..." if len(tool_message.content) > 500 else tool_message.content
1103
+ print(content_preview)
1104
+ print("=== END OBSERVATION ===\n")
1105
+
1106
+ # Return the updated state
1107
+ return {
1108
+ "messages": state["messages"] + [tool_message],
1109
+ "current_tool": None, # Reset the current tool
1110
+ "action_input": None # Clear the action input
1111
+ }
1112
+
1113
+ def read_file_node(state: AgentState) -> Dict[str, Any]:
1114
+ """Node that reads text file contents."""
1115
+ print("File Reading Tool Called...\n\n")
1116
+
1117
+ # Extract tool arguments
1118
+ action_input = state.get("action_input", {})
1119
+ print(f"File reading action_input: {action_input}")
1120
+
1121
+ # Extract required parameters
1122
+ file_path = ""
1123
+ line_start = None
1124
+ line_end = None
1125
+ file_content = None
1126
+
1127
+ if isinstance(action_input, dict):
1128
+ file_path = action_input.get("file_path", "")
1129
+
1130
+ # Check if line range parameters exist
1131
+ if "line_start" in action_input:
1132
+ try:
1133
+ line_start = int(action_input["line_start"])
1134
+ except:
1135
+ print("Invalid line_start parameter, using default (None)")
1136
+
1137
+ if "line_end" in action_input:
1138
+ try:
1139
+ line_end = int(action_input["line_end"])
1140
+ except:
1141
+ print("Invalid line_end parameter, using default (None)")
1142
+
1143
+ # Check if there's attached file content (base64 encoded) directly in the action_input
1144
+ if "file_content" in action_input and action_input["file_content"]:
1145
+ try:
1146
+ file_content = base64.b64decode(action_input["file_content"])
1147
+ print(f"Decoded attached file content, size: {len(file_content)} bytes")
1148
+ except Exception as e:
1149
+ print(f"Error decoding file content from action_input: {e}")
1150
+
1151
+ # Check if we should use a file from the attachments dictionary
1152
+ if not file_content and file_path and "attachments" in state and file_path in state["attachments"]:
1153
+ try:
1154
+ attachment_data = state["attachments"][file_path]
1155
+ if attachment_data: # Make sure it's not empty
1156
+ file_content = base64.b64decode(attachment_data)
1157
+ print(f"Using attachment '{file_path}' from state, size: {len(file_content)} bytes")
1158
+ except Exception as e:
1159
+ print(f"Error using attachment {file_path}: {e}")
1160
+
1161
+ print(f"File reading: path={file_path}, line_range={line_start}-{line_end if line_end else 'end'}, has_attachment={file_content is not None}")
1162
+
1163
+ # Safety check
1164
+ if not file_path:
1165
+ result = "Error: File path is required"
1166
+ elif not file_content:
1167
+ # If we have a path but no content, check if it's a local file that exists
1168
+ local_file_path = Path(file_path).expanduser().resolve()
1169
+ if local_file_path.is_file():
1170
+ # Local file exists, use it directly
1171
+ result = read_file(str(local_file_path), None, line_start, line_end)
1172
+ else:
1173
+ # No file content and path doesn't exist as a local file
1174
+ result = f"Error: File not found at {local_file_path} and no attachment data available"
1175
+ else:
1176
+ # We have file content, use it
1177
+ result = read_file(file_path, file_content, line_start, line_end)
1178
+
1179
+ print(f"File reading result length: {len(result)}")
1180
+
1181
+ # Format the observation to continue the ReAct cycle
1182
+ tool_message = AIMessage(
1183
+ content=f"Observation: {result.strip()}"
1184
+ )
1185
+
1186
+ # Print the observation that will be sent back to the assistant
1187
+ print("\n=== TOOL OBSERVATION ===")
1188
+ content_preview = tool_message.content[:500] + "..." if len(tool_message.content) > 500 else tool_message.content
1189
+ print(content_preview)
1190
+ print("=== END OBSERVATION ===\n")
1191
+
1192
+ # Return the updated state
1193
+ return {
1194
+ "messages": state["messages"] + [tool_message],
1195
+ "current_tool": None, # Reset the current tool
1196
+ "action_input": None # Clear the action input
1197
+ }
1198
+
1199
  # Router function to direct to the correct tool
1200
  def router(state: AgentState) -> str:
1201
  """Route to the appropriate tool based on the current_tool field."""
 
1222
  return "process_youtube_video"
1223
  elif tool == "transcribe_audio":
1224
  return "transcribe_audio"
1225
+ elif tool == "process_image":
1226
+ return "process_image"
1227
+ elif tool == "read_file":
1228
+ return "read_file"
1229
  else:
1230
  return "end"
1231
 
 
1245
  builder.add_node("excel_to_text", excel_to_text_node)
1246
  builder.add_node("process_youtube_video", process_youtube_video_node)
1247
  builder.add_node("transcribe_audio", transcribe_audio_node)
1248
+ builder.add_node("process_image", process_image_node)
1249
+ builder.add_node("read_file", read_file_node)
1250
 
1251
  # Define edges: these determine how the control flow moves
1252
  builder.add_edge(START, "assistant")
 
1280
  "excel_to_text": "excel_to_text",
1281
  "process_youtube_video": "process_youtube_video",
1282
  "transcribe_audio": "transcribe_audio",
1283
+ "process_image": "process_image",
1284
+ "read_file": "read_file",
1285
  "end": END
1286
  }
1287
  )
 
1296
  builder.add_edge("excel_to_text", "assistant")
1297
  builder.add_edge("process_youtube_video", "assistant")
1298
  builder.add_edge("transcribe_audio", "assistant")
1299
+ builder.add_edge("process_image", "assistant")
1300
+ builder.add_edge("read_file", "assistant")
1301
 
1302
  # Compile the graph
1303
  return builder.compile()
 
1360
  # Extract the final message and return just the final answer
1361
  final_message = result["messages"][-1].content
1362
  print("Final message: ", final_message)
1363
+
1364
  # Extract just the final answer part
1365
  if "Final Answer:" in final_message:
1366
  final_answer = final_message.split("Final Answer:", 1)[1].strip()
1367
  return final_answer
1368
 
1369
  return final_message
1370
+
1371
  except Exception as e:
1372
  print(f"Error processing question: {str(e)}")
1373
  # Otherwise return the error
app.py CHANGED
@@ -3,10 +3,12 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
6
  from agent import TurboNerd
7
 
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
10
 
11
  # --- Basic Agent Definition ---
12
  class BasicAgent:
@@ -21,19 +23,48 @@ class BasicAgent:
21
  return answer
22
 
23
  # --- Chat Interface Functions ---
24
- def chat_with_agent(question: str, history: list) -> tuple:
25
  """
26
- Handle chat interaction with TurboNerd agent.
27
  """
28
- if not question.strip():
29
  return history, ""
30
 
31
  try:
32
  # Initialize agent
33
  agent = TurboNerd()
34
 
35
- # Get response from agent
36
- response = agent(question)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  # Add question and response to history
39
  history.append([question, response])
@@ -46,7 +77,7 @@ def chat_with_agent(question: str, history: list) -> tuple:
46
 
47
  def clear_chat():
48
  """Clear the chat history."""
49
- return [], ""
50
 
51
  # --- Evaluation Functions ---
52
  def run_and_submit_all(profile: gr.OAuthProfile | None):
@@ -103,6 +134,13 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
103
  # 3. Run your Agent
104
  results_log = []
105
  answers_payload = []
 
 
 
 
 
 
 
106
  print(f"Running agent on {len(questions_data)} questions...")
107
  for item in questions_data:
108
  task_id = item.get("task_id")
@@ -111,6 +149,8 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
111
  print(f"Skipping item with missing task_id or question: {item}")
112
  continue
113
  try:
 
 
114
  submitted_answer = agent(question_text)
115
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
116
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
@@ -188,12 +228,20 @@ with gr.Blocks(title="TurboNerd Agent🤓") as demo:
188
  with gr.Row():
189
  with gr.Column(scale=4):
190
  chatbot = gr.Chatbot(label="Conversation", height=300)
191
- question_input = gr.Textbox(
192
- label="Ask a question",
193
- placeholder="Type your question here...",
194
- lines=2,
195
- max_lines=5
196
- )
 
 
 
 
 
 
 
 
197
  with gr.Row():
198
  submit_btn = gr.Button("Send", variant="primary")
199
  clear_btn = gr.Button("Clear Chat", variant="secondary")
@@ -201,19 +249,19 @@ with gr.Blocks(title="TurboNerd Agent🤓") as demo:
201
  # Chat interface event handlers
202
  submit_btn.click(
203
  fn=chat_with_agent,
204
- inputs=[question_input, chatbot],
205
  outputs=[chatbot, question_input]
206
  )
207
 
208
  question_input.submit(
209
  fn=chat_with_agent,
210
- inputs=[question_input, chatbot],
211
  outputs=[chatbot, question_input]
212
  )
213
 
214
  clear_btn.click(
215
  fn=clear_chat,
216
- outputs=[chatbot, question_input]
217
  )
218
 
219
  # Tab 2: Evaluation Interface
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ import base64
7
  from agent import TurboNerd
8
 
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
+ ALLOWED_FILE_EXTENSIONS = [".mp3", ".xlsx", ".py", ".png", ".jpg", ".jpeg", ".gif", ".txt", ".md", ".json", ".csv", ".yml", ".yaml", ".html", ".css", ".js"]
12
 
13
  # --- Basic Agent Definition ---
14
  class BasicAgent:
 
23
  return answer
24
 
25
  # --- Chat Interface Functions ---
26
+ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
27
  """
28
+ Handle chat interaction with TurboNerd agent, now with file upload support.
29
  """
30
+ if not question.strip() and not file_uploads:
31
  return history, ""
32
 
33
  try:
34
  # Initialize agent
35
  agent = TurboNerd()
36
 
37
+ # Process uploaded files if any
38
+ attachments = {}
39
+ file_info = ""
40
+
41
+ if file_uploads:
42
+ for file in file_uploads:
43
+ if file is not None:
44
+ file_path = file.name
45
+ file_name = os.path.basename(file_path)
46
+ file_ext = os.path.splitext(file_name)[1].lower()
47
+
48
+ # Check if file extension is allowed
49
+ if file_ext in ALLOWED_FILE_EXTENSIONS:
50
+ # Read file content and encode as base64
51
+ with open(file_path, "rb") as f:
52
+ file_content = f.read()
53
+ file_content_b64 = base64.b64encode(file_content).decode("utf-8")
54
+ attachments[file_name] = file_content_b64
55
+ file_info += f"\nUploaded file: {file_name}"
56
+
57
+ if file_info:
58
+ if question.strip():
59
+ question = question + file_info
60
+ else:
61
+ question = f"Please analyze these files: {file_info}"
62
+
63
+ # Get response from agent with attachments if available
64
+ if attachments:
65
+ response = agent(question, attachments)
66
+ else:
67
+ response = agent(question)
68
 
69
  # Add question and response to history
70
  history.append([question, response])
 
77
 
78
  def clear_chat():
79
  """Clear the chat history."""
80
+ return [], "", None
81
 
82
  # --- Evaluation Functions ---
83
  def run_and_submit_all(profile: gr.OAuthProfile | None):
 
134
  # 3. Run your Agent
135
  results_log = []
136
  answers_payload = []
137
+
138
+ tasks = {"cca530fc-4052-43b2-b130-b30968d8aa44":"chess.png",
139
+ "1f975693-876d-457b-a649-393859e79bf3":"audio1.mp3",
140
+ "f918266a-b3e0-4914-865d-4faa564f1aef":"code.py",
141
+ "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3":"audio2.mp3",
142
+ "7bd855d8-463d-4ed5-93ca-5fe35145f733":"excel.xlsx"}
143
+
144
  print(f"Running agent on {len(questions_data)} questions...")
145
  for item in questions_data:
146
  task_id = item.get("task_id")
 
149
  print(f"Skipping item with missing task_id or question: {item}")
150
  continue
151
  try:
152
+ if task_id in tasks:
153
+ question_text = question_text + f"\n\nThis is the file path: {tasks[task_id]}"
154
  submitted_answer = agent(question_text)
155
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
156
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
228
  with gr.Row():
229
  with gr.Column(scale=4):
230
  chatbot = gr.Chatbot(label="Conversation", height=300)
231
+ with gr.Row():
232
+ question_input = gr.Textbox(
233
+ label="Ask a question",
234
+ placeholder="Type your question here...",
235
+ lines=2,
236
+ max_lines=5,
237
+ scale=3
238
+ )
239
+ file_upload = gr.File(
240
+ label="Upload Files",
241
+ file_types=ALLOWED_FILE_EXTENSIONS,
242
+ file_count="multiple",
243
+ scale=1
244
+ )
245
  with gr.Row():
246
  submit_btn = gr.Button("Send", variant="primary")
247
  clear_btn = gr.Button("Clear Chat", variant="secondary")
 
249
  # Chat interface event handlers
250
  submit_btn.click(
251
  fn=chat_with_agent,
252
+ inputs=[question_input, file_upload, chatbot],
253
  outputs=[chatbot, question_input]
254
  )
255
 
256
  question_input.submit(
257
  fn=chat_with_agent,
258
+ inputs=[question_input, file_upload, chatbot],
259
  outputs=[chatbot, question_input]
260
  )
261
 
262
  clear_btn.click(
263
  fn=clear_chat,
264
+ outputs=[chatbot, question_input, file_upload]
265
  )
266
 
267
  # Tab 2: Evaluation Interface
requirements.txt CHANGED
@@ -19,3 +19,6 @@ yt_dlp
19
  wikipedia
20
  arxiv
21
  openai
 
 
 
 
19
  wikipedia
20
  arxiv
21
  openai
22
+ openpyxl
23
+ Pillow
24
+ numpy
run_code.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import sys
3
+
4
+ def run_code(code_string):
5
+ """Run the provided code string directly"""
6
+ # Create a clean globals dictionary
7
+ globals_dict = {
8
+ '__name__': '__main__'
9
+ }
10
+
11
+ # Execute the code
12
+ try:
13
+ exec(code_string, globals_dict)
14
+ print("Code executed successfully.")
15
+ except Exception as e:
16
+ print(f"Error: {type(e).__name__}: {e}")
17
+ import traceback
18
+ traceback.print_exc()
19
+
20
+ if __name__ == "__main__":
21
+ # Get code from command line argument or stdin
22
+ if len(sys.argv) > 1:
23
+ # Code is provided in the argument
24
+ code = sys.argv[1]
25
+ else:
26
+ # Read code from stdin
27
+ code = sys.stdin.read()
28
+
29
+ # Run the code
30
+ run_code(code)
tools.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  from dotenv import load_dotenv
3
  from typing import Dict, Any, Optional, Union, List
4
  from pathlib import Path
@@ -22,6 +23,11 @@ import re
22
  import pytube
23
  from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
24
 
 
 
 
 
 
25
  load_dotenv()
26
 
27
  def extract_python_code_from_complex_input(input_text):
@@ -129,63 +135,73 @@ def extract_python_code_from_complex_input(input_text):
129
  # If all else fails, return the original input
130
  return input_text
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def run_python_code(code: str):
133
- """Execute Python code safely using exec() instead of subprocess."""
134
  try:
135
  # Pre-process code to handle complex nested structures
136
- # This is our most aggressive approach to extract the actual code
137
  code = extract_python_code_from_complex_input(code)
138
 
139
- # First, check if the input is a nested JSON structure
140
- if code.strip().startswith('{') and ('"action"' in code or "'action'" in code):
141
- try:
142
- # Common issue: escaped quotes causing JSON parse errors
143
- # Pre-process to handle common escaping problems
144
- preprocessed_code = code
145
-
146
- # Handle the specific case we're seeing with nested escaped quotes
147
- import re
148
-
149
- # Search for nested code pattern - this is a more direct approach
150
- code_pattern = re.search(r'"code"\s*:\s*"(.*?)"\s*\}\s*\}\s*\}', code, re.DOTALL)
151
- if code_pattern:
152
- extracted_code = code_pattern.group(1)
153
- # Unescape the extracted code
154
- extracted_code = extracted_code.replace('\\n', '\n').replace('\\"', '"').replace("\\'", "'")
155
- code = extracted_code
156
- print(f"Extracted code using regex pattern: {code[:100]}")
157
- else:
158
- # Try JSON parsing approach if regex fails
159
- import json
160
- try:
161
- # First try direct parsing
162
- parsed_json = json.loads(code)
163
-
164
- # Check if this is an action structure with embedded code
165
- if 'action' in parsed_json and 'action_input' in parsed_json:
166
- if isinstance(parsed_json['action_input'], dict) and 'code' in parsed_json['action_input']:
167
- # Extract the actual code from the nested structure
168
- code = parsed_json['action_input']['code']
169
- print(f"Extracted code using JSON parsing: {code[:100]}")
170
- elif isinstance(parsed_json['action_input'], str):
171
- # Try to parse the action_input as JSON if it's a string
172
- try:
173
- inner_input = json.loads(parsed_json['action_input'])
174
- if isinstance(inner_input, dict) and 'code' in inner_input:
175
- code = inner_input['code']
176
- print(f"Extracted nested code: {code[:100]}")
177
- except:
178
- # If parsing fails, assume the action_input itself is the code
179
- code = parsed_json['action_input']
180
- print(f"Using action_input as code: {code[:100]}")
181
- except json.JSONDecodeError:
182
- # Direct parsing failed, try alternative approaches
183
- print("JSON parsing failed, trying alternative approaches")
184
- except Exception as e:
185
- print(f"Error during code extraction: {str(e)}")
186
- # If JSON parsing fails, proceed with the original code
187
- pass
188
-
189
  print(f"Final code to execute: {code[:100]}...")
190
 
191
  # Check for potentially dangerous operations
@@ -206,7 +222,10 @@ def run_python_code(code: str):
206
  "import re", "import json", "import csv", "import numpy",
207
  "import pandas", "from math import", "from datetime import",
208
  "from statistics import", "from collections import",
209
- "from itertools import"
 
 
 
210
  }
211
 
212
  # Check for dangerous operations
@@ -225,102 +244,20 @@ def run_python_code(code: str):
225
  if not is_safe:
226
  return f"Error: Code contains potentially unsafe import: {line}"
227
 
228
- # Capture stdout to get print output
229
- import io
230
- import sys
231
- from contextlib import redirect_stdout
232
-
233
- # Create a restricted globals environment
234
- restricted_globals = {
235
- '__builtins__': {
236
- 'abs': abs, 'all': all, 'any': any, 'bin': bin, 'bool': bool,
237
- 'chr': chr, 'dict': dict, 'dir': dir, 'divmod': divmod,
238
- 'enumerate': enumerate, 'filter': filter, 'float': float,
239
- 'format': format, 'hex': hex, 'int': int, 'len': len,
240
- 'list': list, 'map': map, 'max': max, 'min': min, 'oct': oct,
241
- 'ord': ord, 'pow': pow, 'print': print, 'range': range,
242
- 'reversed': reversed, 'round': round, 'set': set, 'slice': slice,
243
- 'sorted': sorted, 'str': str, 'sum': sum, 'tuple': tuple,
244
- 'type': type, 'zip': zip,
245
- }
246
- }
247
-
248
- # Allow safe modules
249
- import math
250
- import datetime
251
- import random
252
- import statistics
253
- import collections
254
- import itertools
255
- import re
256
- import json
257
- import csv
258
-
259
- restricted_globals['math'] = math
260
- restricted_globals['datetime'] = datetime
261
- restricted_globals['random'] = random
262
- restricted_globals['statistics'] = statistics
263
- restricted_globals['collections'] = collections
264
- restricted_globals['itertools'] = itertools
265
- restricted_globals['re'] = re
266
- restricted_globals['json'] = json
267
- restricted_globals['csv'] = csv
268
-
269
- # Try to import numpy and pandas if available
270
- try:
271
- import numpy as np
272
- restricted_globals['numpy'] = np
273
- restricted_globals['np'] = np
274
- except ImportError:
275
- pass
276
-
277
- try:
278
- import pandas as pd
279
- restricted_globals['pandas'] = pd
280
- restricted_globals['pd'] = pd
281
- except ImportError:
282
- pass
283
-
284
- # Create local scope
285
- local_scope = {}
286
-
287
- # Capture stdout
288
- captured_output = io.StringIO()
289
-
290
- # Execute the entire code block at once
291
- with redirect_stdout(captured_output):
292
- # Try to evaluate as expression first (for simple expressions)
293
- lines = code.strip().split('\n')
294
- if len(lines) == 1 and not any(keyword in code for keyword in ['=', 'import', 'from', 'def', 'class', 'if', 'for', 'while', 'try', 'with']):
295
- try:
296
- result = eval(code, restricted_globals, local_scope)
297
- print(f"Result: {result}")
298
- except:
299
- # If eval fails, use exec
300
- exec(code, restricted_globals, local_scope)
301
- else:
302
- # For multi-line code, execute the entire block
303
- exec(code, restricted_globals, local_scope)
304
-
305
- # Get the captured output
306
- output = captured_output.getvalue()
307
-
308
- if output.strip():
309
- return output.strip()
310
- else:
311
- # If no output, check if there's a result from the last expression
312
- lines = code.strip().split('\n')
313
- last_line = lines[-1].strip() if lines else ""
314
 
315
- # If the last line looks like an expression, try to evaluate it
316
- if last_line and not any(keyword in last_line for keyword in ['=', 'import', 'from', 'def', 'class', 'if', 'for', 'while', 'try', 'with', 'print']):
317
- try:
318
- result = eval(last_line, restricted_globals, local_scope)
319
- return f"Result: {result}"
320
- except:
321
- pass
322
-
323
- return "Code executed successfully with no output."
324
 
325
  except SyntaxError as e:
326
  return f"Syntax Error: {str(e)}"
@@ -330,7 +267,6 @@ def run_python_code(code: str):
330
  return f"Zero Division Error: {str(e)}"
331
  except Exception as e:
332
  return f"Error executing code: {str(e)}"
333
-
334
  def scrape_webpage(url: str) -> str:
335
  """
336
  Safely scrape content from a specified URL.
@@ -961,6 +897,9 @@ def transcribe_audio(audio_path: str, file_content: Optional[bytes] = None, lang
961
  Returns:
962
  Transcribed text from the audio file
963
  """
 
 
 
964
  try:
965
  # Check for OpenAI API key
966
  openai_api_key = os.environ.get("OPENAI_API_KEY")
@@ -971,7 +910,6 @@ def transcribe_audio(audio_path: str, file_content: Optional[bytes] = None, lang
971
  openai.api_key = openai_api_key
972
 
973
  # Handle file attachment case
974
- temp_path = None
975
  if file_content:
976
  # Determine file extension from audio_path or default to .mp3
977
  if '.' in audio_path:
@@ -994,61 +932,362 @@ def transcribe_audio(audio_path: str, file_content: Optional[bytes] = None, lang
994
 
995
  print(f"Transcribing audio file: {file_path}")
996
 
997
- # Open the audio file and transcribe using Whisper
998
- with open(file_path, "rb") as audio_file:
999
- # Prepare the transcription request
1000
- transcript_params = {
1001
- "model": "whisper-1",
1002
- "file": audio_file
1003
- }
1004
-
1005
- # Add language parameter if specified
1006
- if language:
1007
- transcript_params["language"] = language
1008
-
1009
- # Call OpenAI Whisper API
1010
- response = openai.Audio.transcribe(**transcript_params)
 
 
 
 
1011
 
1012
  # Extract the transcribed text
1013
- transcribed_text = response.get("text", "")
1014
 
1015
  if not transcribed_text:
1016
  return "Error: No transcription was returned from Whisper API"
1017
 
1018
- # Clean up temporary file if we created one
1019
- if temp_path and os.path.exists(temp_path):
1020
- os.unlink(temp_path)
1021
- print(f"Deleted temporary audio file: {temp_path}")
1022
-
1023
  # Format the result
1024
  result = f"Audio Transcription:\n\n{transcribed_text}"
1025
 
1026
- # Add metadata if available
1027
- if hasattr(response, 'duration'):
1028
- result = f"Duration: {response.duration} seconds\n" + result
1029
-
1030
  return result
1031
 
1032
- except openai.error.InvalidRequestError as e:
1033
- # Clean up temporary file in case of error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1034
  if temp_path and os.path.exists(temp_path):
1035
- os.unlink(temp_path)
1036
- return f"Error: Invalid request to Whisper API - {str(e)}"
1037
- except openai.error.RateLimitError as e:
1038
- # Clean up temporary file in case of error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  if temp_path and os.path.exists(temp_path):
1040
- os.unlink(temp_path)
1041
- return f"Error: Rate limit exceeded for Whisper API - {str(e)}"
1042
- except openai.error.APIError as e:
1043
- # Clean up temporary file in case of error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  if temp_path and os.path.exists(temp_path):
1045
- os.unlink(temp_path)
1046
- return f"Error: OpenAI API error - {str(e)}"
1047
- except Exception as e:
1048
- # Clean up temporary file in case of error
1049
- if temp_path and 'temp_path' in locals() and os.path.exists(temp_path):
1050
- os.unlink(temp_path)
1051
- return f"Error transcribing audio: {str(e)}"
 
1052
 
1053
  # Define the tools configuration
1054
  tools_config = [
@@ -1091,5 +1330,15 @@ tools_config = [
1091
  "name": "transcribe_audio",
1092
  "description": "Transcribe audio files (MP3, WAV, etc.) using OpenAI Whisper. You can provide either a file path or use a file attachment. For attachments, provide base64-encoded content. Optionally specify language for better accuracy.",
1093
  "func": transcribe_audio
 
 
 
 
 
 
 
 
 
 
1094
  }
1095
  ]
 
1
  import os
2
+ import sys
3
  from dotenv import load_dotenv
4
  from typing import Dict, Any, Optional, Union, List
5
  from pathlib import Path
 
23
  import pytube
24
  from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
25
 
26
+ # Add new imports for image processing
27
+ from PIL import Image, ExifTags, ImageStat
28
+ import numpy as np
29
+ from io import BytesIO
30
+
31
  load_dotenv()
32
 
33
  def extract_python_code_from_complex_input(input_text):
 
135
  # If all else fails, return the original input
136
  return input_text
137
 
138
+ def test_python_execution(code_str):
139
+ """A simplified function to test Python code execution and diagnose issues."""
140
+ import io
141
+ import sys
142
+ import random
143
+ import time
144
+ from contextlib import redirect_stdout
145
+
146
+ # Create a simple globals environment
147
+ test_globals = {
148
+ 'random': random,
149
+ 'randint': random.randint,
150
+ 'time': time,
151
+ 'sleep': time.sleep,
152
+ '__name__': '__main__',
153
+ '__builtins__': __builtins__ # Use all built-ins for simplicity
154
+ }
155
+
156
+ # Create an empty locals dict
157
+ test_locals = {}
158
+
159
+ # Capture output
160
+ output = io.StringIO()
161
+
162
+ # Execute with detailed error reporting
163
+ with redirect_stdout(output):
164
+ print(f"Executing code:\n{code_str}")
165
+ try:
166
+ # Try compilation first to catch syntax errors
167
+ compiled_code = compile(code_str, '<string>', 'exec')
168
+ print("Compilation successful!")
169
+
170
+ # Then try execution
171
+ try:
172
+ exec(compiled_code, test_globals, test_locals)
173
+ print("Execution successful!")
174
+
175
+ # Check what variables were defined
176
+ print(f"Defined locals: {list(test_locals.keys())}")
177
+
178
+ # If the code defines a main block, try to call a bit of it directly
179
+ if "__name__" in test_globals and test_globals["__name__"] == "__main__":
180
+ print("Running main block...")
181
+ if "Okay" in test_locals and "keep_trying" in test_locals:
182
+ print("Found Okay and keep_trying functions, attempting to call...")
183
+ try:
184
+ go = test_locals["Okay"]()
185
+ result = test_locals["keep_trying"](go)
186
+ print(f"Result from keep_trying: {result}")
187
+ except Exception as e:
188
+ print(f"Error in main execution: {type(e).__name__}: {str(e)}")
189
+ except Exception as e:
190
+ print(f"Runtime error: {type(e).__name__}: {str(e)}")
191
+ # Get traceback info
192
+ import traceback
193
+ traceback.print_exc(file=output)
194
+ except SyntaxError as e:
195
+ print(f"Syntax error: {str(e)}")
196
+
197
+ # Return the captured output
198
+ return output.getvalue()
199
  def run_python_code(code: str):
200
+ """Execute Python code safely using an external Python process."""
201
  try:
202
  # Pre-process code to handle complex nested structures
 
203
  code = extract_python_code_from_complex_input(code)
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  print(f"Final code to execute: {code[:100]}...")
206
 
207
  # Check for potentially dangerous operations
 
222
  "import re", "import json", "import csv", "import numpy",
223
  "import pandas", "from math import", "from datetime import",
224
  "from statistics import", "from collections import",
225
+ "from itertools import", "from random import", "from random import randint",
226
+ "from random import choice", "from random import sample", "from random import random",
227
+ "from random import uniform", "from random import shuffle", "import time",
228
+ "from time import sleep"
229
  }
230
 
231
  # Check for dangerous operations
 
244
  if not is_safe:
245
  return f"Error: Code contains potentially unsafe import: {line}"
246
 
247
+ # Direct execution
248
+ # Use our test_python_execution function which has more robust error handling
249
+ test_result = test_python_execution(code)
250
+
251
+ # Extract just the relevant output from the test execution result
252
+ # Remove diagnostic information that might confuse users
253
+ cleaned_output = []
254
+ for line in test_result.split('\n'):
255
+ # Skip diagnostic lines
256
+ if line.startswith("Executing code:") or line.startswith("Compilation successful") or line.startswith("Execution successful") or "Defined locals:" in line:
257
+ continue
258
+ cleaned_output.append(line)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
+ return '\n'.join(cleaned_output)
 
 
 
 
 
 
 
 
261
 
262
  except SyntaxError as e:
263
  return f"Syntax Error: {str(e)}"
 
267
  return f"Zero Division Error: {str(e)}"
268
  except Exception as e:
269
  return f"Error executing code: {str(e)}"
 
270
  def scrape_webpage(url: str) -> str:
271
  """
272
  Safely scrape content from a specified URL.
 
897
  Returns:
898
  Transcribed text from the audio file
899
  """
900
+ temp_path = None
901
+ audio_file = None
902
+
903
  try:
904
  # Check for OpenAI API key
905
  openai_api_key = os.environ.get("OPENAI_API_KEY")
 
910
  openai.api_key = openai_api_key
911
 
912
  # Handle file attachment case
 
913
  if file_content:
914
  # Determine file extension from audio_path or default to .mp3
915
  if '.' in audio_path:
 
932
 
933
  print(f"Transcribing audio file: {file_path}")
934
 
935
+ # Initialize client first
936
+ client = openai.OpenAI(api_key=openai_api_key)
937
+
938
+ # Read the file content into memory - avoids file handle issues
939
+ with open(file_path, "rb") as f:
940
+ audio_data = f.read()
941
+
942
+ # Create a file-like object from the data
943
+ audio_file = BytesIO(audio_data)
944
+ audio_file.name = os.path.basename(file_path) # OpenAI needs a name
945
+
946
+ # Call OpenAI Whisper API with the file-like object
947
+ try:
948
+ response = client.audio.transcriptions.create(
949
+ model="whisper-1",
950
+ file=audio_file,
951
+ language=language
952
+ )
953
 
954
  # Extract the transcribed text
955
+ transcribed_text = response.text
956
 
957
  if not transcribed_text:
958
  return "Error: No transcription was returned from Whisper API"
959
 
 
 
 
 
 
960
  # Format the result
961
  result = f"Audio Transcription:\n\n{transcribed_text}"
962
 
 
 
 
 
963
  return result
964
 
965
+ except openai.BadRequestError as e:
966
+ return f"Error: Invalid request to Whisper API - {str(e)}"
967
+ except openai.RateLimitError as e:
968
+ return f"Error: Rate limit exceeded for Whisper API - {str(e)}"
969
+ except openai.APIError as e:
970
+ return f"Error: OpenAI API error - {str(e)}"
971
+
972
+ except Exception as e:
973
+ return f"Error transcribing audio: {str(e)}"
974
+ finally:
975
+ # Clean up resources
976
+ if audio_file is not None:
977
+ try:
978
+ audio_file.close()
979
+ except:
980
+ pass
981
+
982
+ # Clean up the temporary file if it exists
983
  if temp_path and os.path.exists(temp_path):
984
+ try:
985
+ # Wait a moment to ensure file is not in use
986
+ import time
987
+ time.sleep(0.5)
988
+ os.unlink(temp_path)
989
+ print(f"Deleted temporary audio file: {temp_path}")
990
+ except Exception as e:
991
+ print(f"Warning: Could not delete temporary file {temp_path}: {e}")
992
+
993
+ def process_image(image_path: str, image_url: Optional[str] = None, file_content: Optional[bytes] = None, analyze_content: bool = True) -> str:
994
+ """
995
+ Process an image file to extract information and content.
996
+
997
+ Args:
998
+ image_path: Path to the image file or filename for attachments
999
+ image_url: Optional URL to fetch the image from instead of a local path
1000
+ file_content: Optional binary content of the file if provided as an attachment
1001
+ analyze_content: Whether to analyze the image content using vision AI (if available)
1002
+
1003
+ Returns:
1004
+ Information about the image including dimensions, format, and content description
1005
+ """
1006
+ temp_path = None
1007
+ image_file = None
1008
+
1009
+ try:
1010
+ # Import Pillow for image processing
1011
+ from PIL import Image, ExifTags, ImageStat
1012
+ import numpy as np
1013
+ from io import BytesIO
1014
+
1015
+ # Handle image from URL
1016
+ if image_url:
1017
+ try:
1018
+ # Validate URL
1019
+ parsed_url = urlparse(image_url)
1020
+ if not parsed_url.scheme or not parsed_url.netloc:
1021
+ return f"Error: Invalid URL format: {image_url}. Please provide a valid URL."
1022
+
1023
+ print(f"Downloading image from URL: {image_url}")
1024
+ response = requests.get(image_url, timeout=10)
1025
+ response.raise_for_status()
1026
+
1027
+ # Create BytesIO object from content
1028
+ image_data = BytesIO(response.content)
1029
+ image = Image.open(image_data)
1030
+ image_source = f"URL: {image_url}"
1031
+ except requests.exceptions.RequestException as e:
1032
+ return f"Error downloading image from URL: {str(e)}"
1033
+ except Exception as e:
1034
+ return f"Error processing image from URL: {str(e)}"
1035
+
1036
+ # Handle file attachment case
1037
+ elif file_content:
1038
+ try:
1039
+ # Determine file extension from image_path
1040
+ if '.' in image_path:
1041
+ extension = '.' + image_path.split('.')[-1].lower()
1042
+ else:
1043
+ extension = '.png' # Default to PNG if no extension
1044
+
1045
+ # Create a temporary file to save the attachment
1046
+ with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as temp_file:
1047
+ temp_file.write(file_content)
1048
+ temp_path = temp_file.name
1049
+
1050
+ print(f"Saved attached image file to temporary location: {temp_path}")
1051
+ image = Image.open(temp_path)
1052
+ image_source = f"Uploaded file: {image_path}"
1053
+ except Exception as e:
1054
+ return f"Error processing attached image: {str(e)}"
1055
+ else:
1056
+ # Regular file path case
1057
+ try:
1058
+ file_path = Path(image_path).expanduser().resolve()
1059
+ if not file_path.is_file():
1060
+ return f"Error: Image file not found at {file_path}"
1061
+
1062
+ image = Image.open(file_path)
1063
+ image_source = f"Local file: {file_path}"
1064
+ except Exception as e:
1065
+ return f"Error opening image file: {str(e)}"
1066
+
1067
+ # Basic image information
1068
+ width, height = image.size
1069
+ image_format = image.format or "Unknown"
1070
+ image_mode = image.mode # RGB, RGBA, L (grayscale), etc.
1071
+
1072
+ # Extract EXIF data if available
1073
+ exif_data = {}
1074
+ if hasattr(image, '_getexif') and image._getexif():
1075
+ exif = {
1076
+ ExifTags.TAGS[k]: v
1077
+ for k, v in image._getexif().items()
1078
+ if k in ExifTags.TAGS
1079
+ }
1080
+
1081
+ # Filter for useful EXIF tags
1082
+ useful_tags = ['DateTimeOriginal', 'Make', 'Model', 'ExposureTime', 'FNumber', 'ISOSpeedRatings']
1083
+ exif_data = {k: v for k, v in exif.items() if k in useful_tags}
1084
+
1085
+ # Calculate basic statistics
1086
+ if image_mode in ['RGB', 'RGBA', 'L']:
1087
+ try:
1088
+ stat = ImageStat.Stat(image)
1089
+ mean_values = stat.mean
1090
+
1091
+ # Calculate average color for RGB images
1092
+ if image_mode in ['RGB', 'RGBA']:
1093
+ avg_color = f"R: {mean_values[0]:.1f}, G: {mean_values[1]:.1f}, B: {mean_values[2]:.1f}"
1094
+ else: # For grayscale
1095
+ avg_color = f"Grayscale Intensity: {mean_values[0]:.1f}"
1096
+
1097
+ # Calculate image brightness (simplified)
1098
+ if image_mode in ['RGB', 'RGBA']:
1099
+ brightness = 0.299 * mean_values[0] + 0.587 * mean_values[1] + 0.114 * mean_values[2]
1100
+ brightness_description = "Dark" if brightness < 64 else "Dim" if brightness < 128 else "Normal" if brightness < 192 else "Bright"
1101
+ else:
1102
+ brightness = mean_values[0]
1103
+ brightness_description = "Dark" if brightness < 64 else "Dim" if brightness < 128 else "Normal" if brightness < 192 else "Bright"
1104
+ except Exception as e:
1105
+ print(f"Error calculating image statistics: {e}")
1106
+ avg_color = "Could not calculate"
1107
+ brightness_description = "Unknown"
1108
+ else:
1109
+ avg_color = "Not applicable for this image mode"
1110
+ brightness_description = "Unknown"
1111
+
1112
+ # Image content analysis using OpenAI Vision API if available
1113
+ content_description = "Image content analysis not performed"
1114
+ if analyze_content:
1115
+ try:
1116
+ # Check for OpenAI API key
1117
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
1118
+ if openai_api_key:
1119
+ # Convert image to base64 for OpenAI API
1120
+ buffered = BytesIO()
1121
+ image.save(buffered, format=image_format if image_format != "Unknown" else "PNG")
1122
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
1123
+
1124
+ # Initialize OpenAI client
1125
+ client = openai.OpenAI(api_key=openai_api_key)
1126
+
1127
+ # Call Vision API
1128
+ response = client.chat.completions.create(
1129
+ model="gpt-4.1-nano",
1130
+ messages=[
1131
+ {
1132
+ "role": "user",
1133
+ "content": [
1134
+ {"type": "text", "text": "Describe this image in detail, including the main subject, colors, setting, and any notable features. Be factual and objective. For a chess posistion, look at the board and describe the position of the pieces accurately.Go step by step and be very detailed about the position of the pieces."},
1135
+ {
1136
+ "type": "image_url",
1137
+ "image_url": {
1138
+ "url": f"data:image/{image_format.lower() if image_format != 'Unknown' else 'png'};base64,{img_str}"
1139
+ }
1140
+ }
1141
+ ]
1142
+ }
1143
+ ],
1144
+ max_tokens=300
1145
+ )
1146
+
1147
+ # Extract the analysis
1148
+ content_description = response.choices[0].message.content
1149
+ else:
1150
+ content_description = "OpenAI API key not found. To analyze image content, set the OPENAI_API_KEY environment variable."
1151
+ except Exception as e:
1152
+ content_description = f"Error analyzing image content: {str(e)}"
1153
+
1154
+ # Format the result
1155
+ result = f"Image Information:\n\n"
1156
+ result += f"Source: {image_source}\n"
1157
+ result += f"Dimensions: {width} x {height} pixels\n"
1158
+ result += f"Format: {image_format}\n"
1159
+ result += f"Mode: {image_mode}\n"
1160
+ result += f"Average Color: {avg_color}\n"
1161
+ result += f"Brightness: {brightness_description}\n"
1162
+
1163
+ # Add EXIF data if available
1164
+ if exif_data:
1165
+ result += "\nEXIF Data:\n"
1166
+ for key, value in exif_data.items():
1167
+ result += f"- {key}: {value}\n"
1168
+
1169
+ # Add content description
1170
+ if analyze_content:
1171
+ result += f"\nContent Analysis:\n{content_description}\n"
1172
+
1173
+ # Clean up resources
1174
+ image.close()
1175
+ print(result)
1176
+ return result
1177
+
1178
+ except Exception as e:
1179
+ return f"Error processing image: {str(e)}"
1180
+ finally:
1181
+ # Clean up the temporary file if it exists
1182
  if temp_path and os.path.exists(temp_path):
1183
+ try:
1184
+ import time
1185
+ time.sleep(0.5) # Wait a moment to ensure file is not in use
1186
+ os.unlink(temp_path)
1187
+ print(f"Deleted temporary image file: {temp_path}")
1188
+ except Exception as e:
1189
+ print(f"Warning: Could not delete temporary file {temp_path}: {e}")
1190
+ # Non-fatal error, don't propagate exception
1191
+
1192
+ def read_file(file_path: str, file_content: Optional[bytes] = None, line_start: Optional[int] = None, line_end: Optional[int] = None) -> str:
1193
+ """
1194
+ Read and return the contents of a text file (.py, .txt, etc.).
1195
+
1196
+ Args:
1197
+ file_path: Path to the file or filename for attachments
1198
+ file_content: Optional binary content of the file if provided as an attachment
1199
+ line_start: Optional starting line number (1-indexed) to read from
1200
+ line_end: Optional ending line number (1-indexed) to read to
1201
+
1202
+ Returns:
1203
+ The content of the file as a string, optionally limited to specified line range
1204
+ """
1205
+ temp_path = None
1206
+
1207
+ try:
1208
+ # Handle file attachment case
1209
+ if file_content:
1210
+ try:
1211
+ # Determine file extension from file_path if available
1212
+ if '.' in file_path:
1213
+ extension = '.' + file_path.split('.')[-1].lower()
1214
+ else:
1215
+ extension = '.txt' # Default to .txt if no extension
1216
+
1217
+ # Create a temporary file to save the attachment
1218
+ with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as temp_file:
1219
+ temp_file.write(file_content)
1220
+ temp_path = temp_file.name
1221
+
1222
+ print(f"Saved attached file to temporary location: {temp_path}")
1223
+ file_to_read = temp_path
1224
+ file_source = f"Uploaded file: {file_path}"
1225
+ except Exception as e:
1226
+ return f"Error processing attached file: {str(e)}"
1227
+ else:
1228
+ # Regular file path case
1229
+ try:
1230
+ file_to_read = Path(file_path).expanduser().resolve()
1231
+ if not file_to_read.is_file():
1232
+ return f"Error: File not found at {file_to_read}"
1233
+
1234
+ file_source = f"Local file: {file_path}"
1235
+ except Exception as e:
1236
+ return f"Error accessing file path: {str(e)}"
1237
+
1238
+ # Check file extension
1239
+ file_extension = os.path.splitext(str(file_to_read))[1].lower()
1240
+ if file_extension not in ['.py', '.txt', '.md', '.json', '.csv', '.yml', '.yaml', '.html', '.css', '.js', '.sh', '.bat', '.log']:
1241
+ return f"Error: File type not supported for reading. Only text-based files are supported."
1242
+
1243
+ # Read the file content
1244
+ try:
1245
+ with open(file_to_read, 'r', encoding='utf-8') as f:
1246
+ lines = f.readlines()
1247
+
1248
+ # Handle line range if specified
1249
+ if line_start is not None and line_end is not None:
1250
+ # Convert to 0-indexed
1251
+ line_start = max(0, line_start - 1)
1252
+ line_end = min(len(lines), line_end)
1253
+
1254
+ # Validate range
1255
+ if line_start >= len(lines) or line_end <= 0 or line_start >= line_end:
1256
+ return f"Error: Invalid line range ({line_start+1}-{line_end}). File has {len(lines)} lines."
1257
+
1258
+ selected_lines = lines[line_start:line_end]
1259
+ content = ''.join(selected_lines)
1260
+
1261
+ # Add context about the selected range
1262
+ result = f"File Content ({file_source}, lines {line_start+1}-{line_end} of {len(lines)}):\n\n{content}"
1263
+ else:
1264
+ content = ''.join(lines)
1265
+ line_count = len(lines)
1266
+ # If the file is large, add a note about its size
1267
+ if line_count > 1000:
1268
+ file_size = os.path.getsize(file_to_read) / 1024 # KB
1269
+ result = f"File Content ({file_source}, {line_count} lines, {file_size:.1f} KB):\n\n{content}"
1270
+ else:
1271
+ result = f"File Content ({file_source}, {line_count} lines):\n\n{content}"
1272
+
1273
+ return result
1274
+
1275
+ except UnicodeDecodeError:
1276
+ return f"Error: File {file_path} appears to be a binary file and cannot be read as text."
1277
+ except Exception as e:
1278
+ return f"Error reading file: {str(e)}"
1279
+
1280
+ finally:
1281
+ # Clean up the temporary file if it exists
1282
  if temp_path and os.path.exists(temp_path):
1283
+ try:
1284
+ import time
1285
+ time.sleep(0.5) # Wait a moment to ensure file is not in use
1286
+ os.unlink(temp_path)
1287
+ print(f"Deleted temporary file: {temp_path}")
1288
+ except Exception as e:
1289
+ print(f"Warning: Could not delete temporary file {temp_path}: {e}")
1290
+ # Non-fatal error, don't propagate exception
1291
 
1292
  # Define the tools configuration
1293
  tools_config = [
 
1330
  "name": "transcribe_audio",
1331
  "description": "Transcribe audio files (MP3, WAV, etc.) using OpenAI Whisper. You can provide either a file path or use a file attachment. For attachments, provide base64-encoded content. Optionally specify language for better accuracy.",
1332
  "func": transcribe_audio
1333
+ },
1334
+ {
1335
+ "name": "process_image",
1336
+ "description": "Process and analyze image files. You can provide a local file path, image URL, or use a file attachment. Returns information about the image including dimensions, format, and content analysis.",
1337
+ "func": process_image
1338
+ },
1339
+ {
1340
+ "name": "read_file",
1341
+ "description": "Read and display the contents of a text file (.py, .txt, etc.). You can provide a file path or use a file attachment. Optionally specify line range to read a specific portion of the file.",
1342
+ "func": read_file
1343
  }
1344
  ]