@@ -154,9 +154,19 @@ def forward(self, question: str):
154154 total_in_tokens = 0
155155 total_out_tokens = 0
156156
157+ def _normalize_text (q : str ) -> str :
158+ return (
159+ q .replace ("\u00d7 " , "x" )
160+ .replace ("\u00f7 " , "/" )
161+ .replace ("\u2212 " , "-" )
162+ .replace ("\u2013 " , "-" )
163+ .replace ("\u2014 " , "-" )
164+ )
165+
157166 def needs_math (q : str ) -> bool :
158- ql = q .lower ()
159- if re .search (r"[0-9].*[+\-*/%]" , q ):
167+ qn = _normalize_text (q )
168+ ql = qn .lower ()
169+ if re .search (r"[0-9].*[+\-*/%]" , qn ):
160170 return True
161171 if re .search (r"\b\d+(?:\.\d+)?\s*(?:x|times|multiplied by)\s*\d+(?:\.\d+)?\b" , ql ):
162172 return True
@@ -170,7 +180,7 @@ def needs_math(q: str) -> bool:
170180 return False
171181
172182 def needs_time (q : str ) -> bool :
173- ql = q .lower ()
183+ ql = _normalize_text ( q ) .lower ()
174184 if "current time" in ql or "current date" in ql :
175185 return True
176186 return re .search (r"\b(time|date|utc|now|today|tomorrow|yesterday|timestamp|datetime)\b" , ql ) is not None
@@ -222,7 +232,8 @@ def _accumulate_usage(input_text: str = "", output_text: str = ""):
222232 pass
223233
224234 def _infer_expression (q : str ) -> str :
225- ql = q .lower ()
235+ qn = _normalize_text (q )
236+ ql = qn .lower ()
226237 # Handle "divide X by Y" and "subtract X from Y"
227238 m = re .search (r"\bdivide\s+(\d+(?:\.\d+)?)\s+by\s+(\d+(?:\.\d+)?)\b" , ql )
228239 if m :
@@ -245,11 +256,11 @@ def _infer_expression(q: str) -> str:
245256 return f"{ m .group (1 )} /{ m .group (2 )} "
246257 # Multi-number add/sum
247258 if "add" in ql or "sum" in ql :
248- nums = [n for n in re .findall (r"\b\d+\b" , q )]
259+ nums = [n for n in re .findall (r"\b\d+\b" , qn )]
249260 if len (nums ) >= 2 :
250261 return "+" .join (nums )
251262 # Fallback: longest math-like substring
252- candidates = re .findall (r"[0-9\+\-\*/%\(\)\.!\^\s]+" , q )
263+ candidates = re .findall (r"[0-9\+\-\*/%\(\)\.!\^\s]+" , qn )
253264 candidates = [c .strip () for c in candidates if any (op in c for op in ["+" ,"-" ,"*" ,"/" ,"%" ,"^" ,"(" ,")" ,"!" ])]
254265 return max (candidates , key = len ) if candidates else ""
255266
@@ -307,6 +318,14 @@ def _infer_expression(q: str) -> str:
307318 continue
308319 # Validate/execute; on validation error, record and continue planning
309320 obs = run_tool (name , args )
321+ if isinstance (obs , dict ) and "error" in obs and obs .get ("error" , "" ).startswith ("Unknown tool" ):
322+ had_validation_error = True
323+ state .append ({
324+ "tool" : "⛔️validation_error" ,
325+ "args" : {"name" : name , "args" : args },
326+ "observation" : obs ,
327+ })
328+ continue
310329 if isinstance (obs , dict ) and "error" in obs and "validation" in obs .get ("error" , "" ):
311330 had_validation_error = True
312331 state .append ({
@@ -506,6 +525,13 @@ def _infer_expression(q: str) -> str:
506525 name = str (tool_desc )
507526 args = decision .get ("args" , {}) or {}
508527 obs = run_tool (name , args )
528+ if isinstance (obs , dict ) and "error" in obs and obs .get ("error" , "" ).startswith ("Unknown tool" ):
529+ state .append ({
530+ "tool" : "⛔️validation_error" ,
531+ "args" : {"name" : name , "args" : args },
532+ "observation" : obs ,
533+ })
534+ continue
509535 if isinstance (obs , dict ) and "error" in obs and "validation" in obs .get ("error" , "" ):
510536 # second-chance: record detailed schema hint in state and continue planning
511537 schema = TOOLS .get (name ).schema if name in TOOLS else {}
0 commit comments