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