Reject empty Vision Model responses

Alessandro committed Aug 25, 2026 at 22:01 UTC 1d922aaab27fb583f16647e93718310de4c22b98
3 files changed +44
tests/test_vision_load_image_refs.py
+41
@@ -242,6 +242,47 @@ async def test_vision_model_sends_multiple_images_once_and_keeps_history_text_on
242 assert tool_results[0][1]["_responses_output_item"]["output"] == response.message
243
244
245 +@pytest.mark.anyio
246 +async def test_vision_model_empty_response_is_reported_as_error(monkeypatch):
247 + _install_tool_stub(monkeypatch)
248 + import tools.vision_load as vision_load_module
249 +
250 + class FakeVisionModel:
251 + async def unified_call(self, **kwargs):
252 + return "", ""
253 +
254 + monkeypatch.setattr(
255 + vision_load_module,
256 + "build_vision_model",
257 + lambda _agent: FakeVisionModel(),
258 + )
259 + monkeypatch.setattr(
260 + vision_load_module,
261 + "get_vision_model_config",
262 + lambda _agent: {"provider": "test", "name": "vision", "max_embeds": 10},
263 + )
264 +
265 + agent = SimpleNamespace(
266 + context=SimpleNamespace(id="", get_data=lambda _key: ""),
267 + last_user_message=SimpleNamespace(output_text=lambda: "Inspect the image."),
268 + read_prompt=lambda _name, request: request,
269 + )
270 + tool = vision_load_module.VisionLoad(
271 + agent=agent,
272 + name="vision_load",
273 + method=None,
274 + args={"paths": ["data:image/png;base64,AA=="]},
275 + message="",
276 + loop_data=None,
277 + )
278 +
279 + response = await tool.execute(paths=["data:image/png;base64,AA=="])
280 +
281 + assert response.message == (
282 + "Image analysis error: Vision Model returned an empty response."
283 + )
284 +
285 +
286 @pytest.mark.anyio
287 async def test_parallel_worker_consumes_parent_ephemeral_image(monkeypatch, tmp_path):
288 _install_tool_stub(monkeypatch)
tools/vision_load.py
+2
@@ -116,6 +116,8 @@ class VisionLoad(Tool):
116 response, _ = await build_vision_model(self.agent).unified_call(
117 messages=[HumanMessage(content=content)],
118 )
119 + if not str(response or "").strip():
120 + raise RuntimeError("Vision Model returned an empty response.")
121 return str(response)
122
123 def _store_ephemeral_image(self, image: ephemeral_images.EphemeralImage) -> str:
tools/vision_load.py.dox.md
+1
@@ -27,6 +27,7 @@
27 - Direct parallel workers inherit the parent's model override generically. This tool uses their recorded parent context only to resolve ephemeral refs and durable chat media.
28 - `max_embeds` comes from the model that actually receives the images.
29 - Vision Model calls use the selected model's Advanced `kwargs`; this tool does not impose a separate timeout or output-token limit.
30 +- An empty Vision Model response is reported as an image-analysis error instead of a successful empty capsule.
31 - Update this file whenever tool arguments, output shape, `break_loop` behavior, intervention handling, prompt instructions, or side effects change.
32 - `VisionLoad` is a `Tool`.
33 - `VisionLoad` defines `execute(...)`.