fix: add missing error handling (#405)

Co-authored-by: Rafael Uzarowski <uzarowski.rafael@proton.me>

ehl0wr0ld committed May 30, 2025 at 22:01 UTC 640e9afdda69717c82be09bdb67ccf159395747a
5 files changed +102 -18
python/extensions/monologue_end/_50_memorize_fragments.py
+30 -1
@@ -45,7 +45,36 @@ class MemorizeMemories(Extension):
45 background=True,
46 )
47
48 - memories = DirtyJson.parse_string(memories_json)
48 + # Add validation and error handling for memories_json
49 + if not memories_json or not isinstance(memories_json, str):
50 + log_item.update(heading="No response from utility model.")
51 + return
52 +
53 + # Strip any whitespace that might cause issues
54 + memories_json = memories_json.strip()
55 +
56 + if not memories_json:
57 + log_item.update(heading="Empty response from utility model.")
58 + return
59 +
60 + try:
61 + memories = DirtyJson.parse_string(memories_json)
62 + except Exception as e:
63 + log_item.update(heading=f"Failed to parse memories response: {str(e)}")
64 + return
65 +
66 + # Validate that memories is a list or convertible to one
67 + if memories is None:
68 + log_item.update(heading="No valid memories found in response.")
69 + return
70 +
71 + # If memories is not a list, try to make it one
72 + if not isinstance(memories, list):
73 + if isinstance(memories, (str, dict)):
74 + memories = [memories]
75 + else:
76 + log_item.update(heading="Invalid memories format received.")
77 + return
78
79 if not isinstance(memories, list) or len(memories) == 0:
80 log_item.update(heading="No useful information to memorize.")
python/extensions/monologue_end/_51_memorize_solutions.py
+39 -4
@@ -24,8 +24,8 @@ class MemorizeSolutions(Extension):
24 heading="Memorizing succesful solutions...",
25 )
26
27 - #memorize in background
28 - asyncio.create_task(self.memorize(loop_data, log_item))
27 + # memorize in background
28 + asyncio.create_task(self.memorize(loop_data, log_item))
29
30 async def memorize(self, loop_data: LoopData, log_item: LogItem, **kwargs):
31 # get system message and chat history for util llm
@@ -44,7 +44,36 @@ class MemorizeSolutions(Extension):
44 background=True,
45 )
46
47 - solutions = DirtyJson.parse_string(solutions_json)
47 + # Add validation and error handling for solutions_json
48 + if not solutions_json or not isinstance(solutions_json, str):
49 + log_item.update(heading="No response from utility model.")
50 + return
51 +
52 + # Strip any whitespace that might cause issues
53 + solutions_json = solutions_json.strip()
54 +
55 + if not solutions_json:
56 + log_item.update(heading="Empty response from utility model.")
57 + return
58 +
59 + try:
60 + solutions = DirtyJson.parse_string(solutions_json)
61 + except Exception as e:
62 + log_item.update(heading=f"Failed to parse solutions response: {str(e)}")
63 + return
64 +
65 + # Validate that solutions is a list or convertible to one
66 + if solutions is None:
67 + log_item.update(heading="No valid solutions found in response.")
68 + return
69 +
70 + # If solutions is not a list, try to make it one
71 + if not isinstance(solutions, list):
72 + if isinstance(solutions, (str, dict)):
73 + solutions = [solutions]
74 + else:
75 + log_item.update(heading="Invalid solutions format received.")
76 + return
77
78 if not isinstance(solutions, list) or len(solutions) == 0:
79 log_item.update(heading="No successful solutions to memorize.")
@@ -61,7 +90,13 @@ class MemorizeSolutions(Extension):
90 rem = []
91 for solution in solutions:
92 # solution to plain text:
64 - txt = f"# Problem\n {solution['problem']}\n# Solution\n {solution['solution']}"
93 + if isinstance(solution, dict):
94 + problem = solution.get('problem', 'Unknown problem')
95 + solution_text = solution.get('solution', 'Unknown solution')
96 + txt = f"# Problem\n {problem}\n# Solution\n {solution_text}"
97 + else:
98 + # If solution is not a dict, convert it to string
99 + txt = f"# Solution\n {str(solution)}"
100 solutions_txt += txt + "\n\n"
101
102 # remove previous solutions too similiar to this one
python/helpers/dirty_json.py
+13 -3
@@ -34,9 +34,19 @@ class DirtyJson:
34 def parse(self, json_string):
35 self._reset()
36 self.json_string = json_string
37 - self.index = self.get_start_pos(
38 - self.json_string
39 - ) # skip any text up to the first brace
37 +
38 + # Add bounds checking to prevent IndexError
39 + if not json_string:
40 + # Return None for empty strings
41 + return None
42 +
43 + self.index = self.get_start_pos(self.json_string)
44 +
45 + # Ensure index is within bounds
46 + if self.index >= len(self.json_string):
47 + # If start position is beyond string length, return None
48 + return None
49 +
50 self.current_char = self.json_string[self.index]
51 self._parse()
52 return self.result
python/helpers/extract_tools.py
+13 -6
@@ -6,10 +6,17 @@ import regex
6 from fnmatch import fnmatch
7
8 def json_parse_dirty(json:str) -> dict[str,Any] | None:
9 - ext_json = extract_json_object_string(json)
9 + if not json or not isinstance(json, str):
10 + return None
11 +
12 + ext_json = extract_json_object_string(json.strip())
13 if ext_json:
11 - data = DirtyJson.parse_string(ext_json)
12 - if isinstance(data,dict): return data
14 + try:
15 + data = DirtyJson.parse_string(ext_json)
16 + if isinstance(data,dict): return data
17 + except Exception:
18 + # If parsing fails, return None instead of crashing
19 + return None
20 return None
21
22 def extract_json_object_string(content):
@@ -29,10 +36,10 @@ def extract_json_object_string(content):
36 def extract_json_string(content):
37 # Regular expression pattern to match a JSON object
38 pattern = r'\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\]|"(?:\\.|[^"\\])*"|true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?'
32 -
39 +
40 # Search for the pattern in the content
41 match = regex.search(pattern, content)
35 -
42 +
43 if match:
44 # Return the matched JSON string
45 return match.group(0)
@@ -77,4 +84,4 @@ def load_classes_from_folder(folder: str, name_pattern: str, base_class: Type[T]
84 if one_per_file:
85 break
86
80 - return classes
\ No newline at end of file
87 + return classes
python/tools/browser_agent.py
+7 -4
@@ -5,7 +5,7 @@ from agent import Agent, InterventionException
5
6 import models
7 from python.helpers.tool import Tool, Response
8 -from python.helpers import files, defer, persist_chat
8 +from python.helpers import files, defer, persist_chat, strings
9 from python.helpers.browser_use import browser_use
10 from python.extensions.message_loop_start._10_iteration_no import get_iter_no
11 from pydantic import BaseModel
@@ -184,10 +184,13 @@ class BrowserAgent(Tool):
184 result = await task.result()
185 answer = result.final_result()
186 try:
187 - answer_data = DirtyJson.parse_string(answer)
188 - answer_text = strings.dict_to_text(answer_data) # type: ignore
187 + if answer and isinstance(answer, str) and answer.strip():
188 + answer_data = DirtyJson.parse_string(answer)
189 + answer_text = strings.dict_to_text(answer_data) # type: ignore
190 + else:
191 + answer_text = str(answer) if answer else "No result returned"
192 except Exception as e:
190 - answer_text = answer
193 + answer_text = str(answer) if answer else f"Error processing result: {str(e)}"
194 self.log.update(answer=answer_text)
195 return Response(message=answer, break_loop=False)
196