Skip to content

fix(agents): tolerate output_text parts with None text (gemini-3.6-flash run crash) - #80

Open
inf-quantavius wants to merge 1 commit into
context-labs:mainfrom
inf-quantavius:quant/gemini-none-output-text-guard
Open

fix(agents): tolerate output_text parts with None text (gemini-3.6-flash run crash)#80
inf-quantavius wants to merge 1 commit into
context-labs:mainfrom
inf-quantavius:quant/gemini-none-output-text-guard

Conversation

@inf-quantavius

@inf-quantavius inf-quantavius commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The first fixed-analysis HALO run on gemini-3.6-flash (pinned by monorepo #4289, merged 2026-07-24 10:31Z) crashed within one turn:

TypeError: sequence item 0: expected str instance, NoneType found

(dev halo_run 99790c24-…, analysis run iar_vEmBDW05ukFmvwyEofnke, smoke run 30091185355; dev Loki shows subagent sub-8e9b236a failed at depth=1: TypeError: …Emitting halo.failed.)

Root cause

Gemini via the LiteLLM Responses translation returns assistant messages whose output_text part carries no text field on tool-calls-only turns — dev ClickHouse shows both gateway calls returned HTTP 200 with content [{"type":"output_text","annotations":[]}] alongside function_call items (ids carrying Gemini __thought__ blobs). The SDK materializes these leniently as ResponseOutputText with text=None, and the mapper's

text = "".join(part.text for part in parts if isinstance(part, ResponseOutputText))

(engine/agents/openai_event_mapper.py:96) raises the exact TypeError, killing the run. gpt-5.5 and deepseek-v4-pro runs on the same project earlier the same day did not hit this — the model swap is the delta.

Change

  • _map_assistant_message: join part.text or "" so a text-less part maps to empty content (content=None downstream, same as tool-calls-only turns from other providers).
  • Regression test building the real SDK shape via model_construct (lenient parse), asserting the mapper emits a normal assistant item instead of raising.

Validation

  • pytest tests/unit: 480 passed (12 in the mapper file, incl. the new regression test)
  • ruff check + ruff format --check: clean
  • basedpyright on both touched files: 0 errors

Related: #79 handles the finalization-protocol side of the model swap; this fixes the orthogonal crash that prevents gemini runs from getting past their first tool-calling turn at all.

Provenance


Note

Low Risk
Single-line defensive join in assistant message mapping with a regression test; no auth, data, or API contract changes.

Overview
Fixes HALO run crashes on Gemini when assistant turns are tool-calls-only: LiteLLM/Gemini can return output_text parts with no text field, which the SDK parses as text=None. Joining those values in _map_assistant_message raised TypeError and aborted the run.

The mapper now joins part.text or "" so missing text becomes empty assistant content (content=None), matching other providers’ tool-only turns. A unit test covers the lenient ResponseOutputText(text=None) shape.

Reviewed by Cursor Bugbot for commit 5309501. Bugbot is set up for automated code reviews on this repo. Configure here.

…apper

gemini-3.6-flash (via the LiteLLM Responses translation) emits output_text
parts with no text field on tool-calls-only turns; the SDK materializes them
leniently with text=None and the mapper's join raised
TypeError: sequence item 0: expected str instance, NoneType found,
failing the whole HALO run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant