fix (browser-use): Gemini JSON-pure outputs + action normalization

Alessandro committed Aug 26, 2025 at 21:58 UTC a7d3fdc8e41b3685bad99e6538d4c7a8207175d9
1 file changed +58 -1
models.py
+58 -1
@@ -435,6 +435,52 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
435 def get_client(self, *args, **kwargs): # type: ignore
436 return AsyncAIChatReplacement(self, *args, **kwargs)
437
438 + # -- Gemini helper -----------------------------------------
439 + def _gemini_clean_and_conform(self, text: str):
440 + obj = None
441 + try:
442 + # dirty_json parser is robust enough to handle markdown fences
443 + obj = dirty_json.parse(text)
444 + except Exception:
445 + return None # return None if parsing fails
446 +
447 + if not isinstance(obj, dict):
448 + return None
449 +
450 + # Conform actions to browser-use expectations
451 + if isinstance(obj.get("action"), list):
452 + normalized_actions = []
453 + for item in obj["action"]:
454 + if not isinstance(item, dict):
455 + continue # Skip non-dict items
456 +
457 + action_key, action_value = next(iter(item.items()), (None, None))
458 + if not action_key:
459 + continue
460 +
461 + # Create a mutable copy of the value
462 + v = (action_value or {}).copy()
463 +
464 + if action_key in ("scroll_down", "scroll_up", "scroll"):
465 + is_down = action_key != "scroll_up"
466 + v.setdefault("down", is_down)
467 + v.setdefault("num_pages", 1.0)
468 + normalized_actions.append({"scroll": v})
469 + elif action_key == "go_to_url":
470 + v.setdefault("new_tab", False)
471 + normalized_actions.append({action_key: v})
472 + elif action_key == "done":
473 + if "text" in v and "data" not in v:
474 + t = v.pop("text", "")
475 + v["data"] = {"title": "Task result", "response": t, "page_summary": t}
476 + v.setdefault("success", True)
477 + normalized_actions.append({action_key: v})
478 + else:
479 + normalized_actions.append(item)
480 + obj["action"] = normalized_actions
481 +
482 + return dirty_json.stringify(obj)
483 +
484 async def _acall(
485 self,
486 messages: List[BaseMessage],
@@ -450,7 +496,7 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
496 model = kwargs.pop("model", None)
497 kwrgs = {**self._wrapper.kwargs, **kwargs}
498
453 - # hack from browser-use to fix json schema for gemini
499 + # hack from browser-use to fix json schema for gemini (additionalProperties, $defs, $ref)
500 if "response_format" in kwrgs and "json_schema" in kwrgs["response_format"] and model.startswith("gemini/"):
501 kwrgs["response_format"]["json_schema"] = ChatGoogle("")._fix_gemini_schema(self._wrapper.kwargs)
502
@@ -460,6 +506,17 @@ class BrowserCompatibleChatWrapper(ChatOpenRouter):
506 stop=stop,
507 **kwrgs,
508 )
509 +
510 + # Gemini: strip triple backticks and conform schema
511 + try:
512 + msg = resp.choices[0].message
513 + if self.provider == "gemini" and isinstance(getattr(msg, "content", None), str):
514 + cleaned = self._gemini_clean_and_conform(msg.content)
515 + if cleaned:
516 + msg.content = cleaned
517 + except Exception:
518 + pass
519 +
520 except Exception as e:
521 raise e
522