browser-use/browser-use · error · ModelProviderError

Failed to parse structured output from model response

Error message

Failed to parse structured output from model response

What it means

OpenRouter chat wrapper's structured-output path: after a successful completion, if `response.choices[0].message.content is None` it raises ModelProviderError (500) because there is no JSON text to feed `output_format.model_validate_json`. OpenRouter routes to many upstream providers, and some return null content (reasoning-only, tool-call-only, or upstream errors mapped to empty content).

Source

Thrown at browser_use/llm/openrouter/chat.py:189

				}

				# Return structured response
				response = await self.get_client().chat.completions.create(
					model=self.model,
					messages=openrouter_messages,
					temperature=self.temperature,
					top_p=self.top_p,
					seed=self.seed,
					response_format=ResponseFormatJSONSchema(
						json_schema=response_format_schema,
						type='json_schema',
					),
					extra_headers=extra_headers,
					**(self.extra_body or {}),
				)

				if response.choices[0].message.content is None:
					raise ModelProviderError(
						message='Failed to parse structured output from model response',
						status_code=500,
						model=self.name,
					)
				usage = self._get_usage(response)

				parsed = output_format.model_validate_json(response.choices[0].message.content)

				return ChatInvokeCompletion(
					completion=parsed,
					usage=usage,
				)

		except RateLimitError as e:
			raise ModelRateLimitError(message=e.message, model=self.name) from e

		except APIConnectionError as e:
			raise ModelProviderError(message=str(e), model=self.name) from e

View on GitHub (pinned to 6c73fced2f)

Solutions

  1. Switch the OpenRouter model slug to one with native structured-output/JSON support (e.g. major frontier models) and re-run
  2. Retry once — transient null-content responses from upstream providers are common on OpenRouter
  3. Check the OpenRouter dashboard/logs for the upstream finish_reason (filter, length, tool_calls) for that request
  4. Avoid `:free` variants for structured output; use the paid tier of the same model

Example fix

# before
llm = ChatOpenRouter(model='some-model:free')
# after
llm = ChatOpenRouter(model='openai/gpt-4.1-mini')
Defensive patterns

Strategy: retry

Try / catch

try:
    out = await llm.invoke(msgs, output_format=Schema)
except ModelProviderError as e:
    if 'parse structured output' in e.message:
        llm.model = FALLBACK_MODEL  # e.g. a frontier model with native JSON mode
        out = await llm.invoke(msgs, output_format=Schema)
    else:
        raise

Prevention

When it happens

Trigger: Calling ChatOpenRouter with an `output_format` where the routed model returns null content — common with free-tier or non-JSON-native models, models that answer via tool_calls, or upstream providers whose JSON mode is unimplemented. Also occurs when OpenRouter routes to a model that spent its output on hidden reasoning.

Common situations: Using `:free` OpenRouter models that don't support structured output; picking a model variant without JSON-mode support; hitting an upstream content filter; upstream provider flakiness returning empty completions.

Understand the failure class

Related errors


AI-assisted analysis of browser-use/browser-use@6c73fced2f (2026-08-14). Data as JSON: /api/errors/cd545e1835204823. Report an issue: GitHub.