Skip to content

fix: preserve tool messages in history to prevent Gemini ordering errors#251

Open
elpiarthera wants to merge 1 commit intoget-convex:mainfrom
elpiarthera:fix/issue-200-gemini-tool-history
Open

fix: preserve tool messages in history to prevent Gemini ordering errors#251
elpiarthera wants to merge 1 commit intoget-convex:mainfrom
elpiarthera:fix/issue-200-gemini-tool-history

Conversation

@elpiarthera
Copy link
Copy Markdown

@elpiarthera elpiarthera commented Apr 9, 2026

Summary

  • Fixed docsToModelMessages to preserve tool-role messages even when content appears empty — prevents Gemini's strict functionCall → functionResponse ordering from breaking
  • Added console.warn in filterOutOrphanedToolMessages when tool messages are dropped, logging the message ID, its toolCallIds, and available toolCallIds for diagnostics

Root cause

When tool messages have empty content after orphan filtering, docsToModelMessages filtered them out via .filter(m => !!m.content.length). This left gaps in the conversation history (missing functionResponse after functionCall), which Gemini rejects with "Please ensure that function call turn comes immediately after a user turn or after a function response turn."

Test plan

  • npm run build passes
  • npm run lint passes (0 errors)
  • npm test passes (262/262)

Fixes #200

Orchestrator: Zeta — VantageOS Team Dev | 2026-04-09

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced the message processing pipeline to correctly handle and retain tool-related messages during filtering operations, including proper handling of messages with minimal or empty content
    • Improved system diagnostics and troubleshooting capabilities by adding detailed warning logs that identify which messages are filtered out and display relevant context information

docsToModelMessages filtered out messages with empty content, which
could drop tool-role messages after orphan filtering. This breaks
Gemini's strict functionCall -> functionResponse ordering requirement.

Also adds console.warn when filterOutOrphanedToolMessages drops tool
messages, making it easier to diagnose missing tool results.

Fixes get-convex#200
Comment thread src/mapping.ts
.map((m) => m.message)
.filter((m) => !!m)
.filter((m) => !!m.content.length)
.filter((m) => m.role === "tool" || !!m.content.length)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we passing through all tools here just to filter them out elsewhere?

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13a0a819-4e0e-425f-a8ef-ff8799f8ebcd

📥 Commits

Reviewing files that changed from the base of the PR and between 2bab89c and 06c6795.

📒 Files selected for processing (2)
  • src/client/search.ts
  • src/mapping.ts

📝 Walkthrough

Walkthrough

Changes add logging when tool messages are dropped in search filtering and modify message retention logic to preserve tool-role messages with empty content, addressing Gemini model validation failures caused by corrupted tool call history.

Changes

Cohort / File(s) Summary
Tool Message Filtering & Logging
src/client/search.ts
Added else if branch to log warnings when tool messages are dropped, including dropped document ID, message toolCallIds, and available toolCallIds for debugging orphaned tool message issues.
Message Content Filtering
src/mapping.ts
Modified docsToModelMessages filter condition to retain tool-role messages even when content is empty, preventing valid tool results from being filtered out during message conversion.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • ianmacartney

Poem

🐰 A tool message lost in the flow,
No log to show where messages go,
Now warnings chirp when orphans fall,
Empty content keeps it all,
Gemini's order restored with care! 🛠️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: preserving tool messages to prevent Gemini ordering errors.
Linked Issues check ✅ Passed The PR addresses primary objectives from issue #200: filtering out orphaned tool messages with logging and preserving tool-role messages in docsToModelMessages.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing tool message preservation; no unrelated modifications detected in the two modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@elpiarthera
Copy link
Copy Markdown
Author

Re: @ianmacartney comment on line 138 — good point.

The intent was defensive: if a tool message ends up with empty content after filterOutOrphanedToolMessages partially strips it, the old filter would drop it entirely, breaking Gemini's strict functionCall → functionResponse ordering.

But you're right that the better fix is upstream — filterOutOrphanedToolMessages should either keep the full tool message or drop it cleanly (not leave an empty shell). The console.warn in this PR helps diagnose when that happens.

If you'd prefer, I can move the fix to filterOutOrphanedToolMessages instead — make it either keep all parts of a tool message or drop the entire message (no partial stripping). That way docsToModelMessages doesn't need the special case. Let me know which approach you prefer.

Orchestrator: Zeta — VantageOS Team Dev | 2026-04-10

@ianmacartney
Copy link
Copy Markdown
Member

ianmacartney commented Apr 10, 2026 via email

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.

Gemini Tool Call History Corruption

2 participants