Skip to content

Conversation

HahaBill
Copy link

@HahaBill HahaBill commented Aug 26, 2025

Related GitHub Issue

Closes: #6372

Description

Before we were collecting sources in from the stream chunks in api/gemini.ts. I tried to resolve the issue by finding a culprit responsible for the race condition and state inconsistencies. It turns out that there were leakages from previous message to new messages. I tried very hard to resolve this but then I told myself that maybe it's better to rethink the code design pattern.

I came up with this with a help of Roo Code:

gemini-grounding-new-arch

Basically, we are decoupling gemini stream chunks and grounding metadata.

Summary:

  • adding new type ApiStreamGroundingChunk to the stream type
  • collecting sources in the Task.ts instead -> decoupling

Test Procedure

I test this with gemini-2.5-flash. For some reason, there are so many errors with gemini-2.5-pro.

  1. Go to the profile and pick Google Gemini
  2. Then pick gemini-2.5-flash
  3. Then enable Grounding with Google Search
  4. Start asking recent/latest info about some topics
  5. It should generate sources correctly without any source leakage from previous messages
  6. It works way better than before - code implementation looks cleaner

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Discord username: HaHaBill (belikebill.)


Important

Redesigns code to decouple gemini stream chunks and grounding metadata, preventing state persistence issues and improving clarity.

  • Behavior:
    • Decouples gemini stream chunks and grounding metadata in gemini.ts.
    • Moves source collection to Task.ts to prevent state persistence issues.
    • Introduces ApiStreamGroundingChunk type in stream.ts.
  • Functions:
    • extractGroundingSources() in gemini.ts now returns GroundingSource[] instead of a string.
    • extractCitationsOnly() in gemini.ts uses extractGroundingSources().
  • Classes:
    • Task in Task.ts now handles grounding sources separately and strips them from messages before saving.
  • Types:
    • Adds ApiStreamGroundingChunk and GroundingSource to stream.ts.

This description was created by Ellipsis for a250a7e. You can customize this summary. It will automatically update as commits are pushed.

- adding new type `ApiStreamGroundingChunk` to the stream type
- collecting sources in the `Task.ts` instead -> decoupling
@HahaBill HahaBill requested review from mrubens, cte and jr as code owners August 26, 2025 22:47
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Aug 26, 2025
@HahaBill HahaBill changed the title feat: Tackling Race/State condition issue by Changing the Code Design fix: Tackling Race/State condition issue by Changing the Code Design for Gemini Grounding Sources Aug 26, 2025
if (pendingGroundingSources.length > 0) {
// Remove any grounding source references that might have been integrated into the message
cleanAssistantMessage = assistantMessage
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "")
Copy link

Choose a reason for hiding this comment

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

When cleaning the assistant message to remove integrated grounding source references, the regex patterns are nontrivial. Consider adding an inline comment with example inputs to clarify what patterns are being stripped to improve future maintainability.

Suggested change
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "")
\t\t\t\t\t\t.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "") // e.g. "[1] Example Source: https://example.com"

@daniel-lxs daniel-lxs moved this from Triage to PR [Needs Prelim Review] in Roo Code Roadmap Aug 26, 2025
Copy link

@roomote roomote bot left a comment

Choose a reason for hiding this comment

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

Thank you for this well-thought-out solution to the Gemini grounding sources race condition issue! The architectural change to decouple stream chunks from grounding metadata is a solid approach that properly addresses the state persistence problems.

The implementation looks clean and follows good separation of concerns. I've left a few suggestions inline to improve maintainability and robustness, with the most important being the addition of test coverage to prevent regression of this race condition fix.

if (pendingGroundingSources.length > 0) {
// Remove any grounding source references that might have been integrated into the message
cleanAssistantMessage = assistantMessage
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "")
Copy link

Choose a reason for hiding this comment

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

I agree with the previous suggestion - these regex patterns would benefit from inline comments explaining what they match. Consider adding comments like:

Suggested change
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "")
cleanAssistantMessage = assistantMessage
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "") // e.g., "[1] Example Source: https://example.com"
.replace(/Sources?:\s*[\s\S]*?(?=\n\n|\n$|$)/g, "") // e.g., "Sources: [1](url1), [2](url2)"

return {
title,
url: uri,
}
}
return null
Copy link

Choose a reason for hiding this comment

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

Consider adding debug logging when sources are filtered out to help with troubleshooting malformed grounding metadata during development. This would make it easier to identify issues with the grounding chunks.

@@ -27,3 +32,14 @@ export interface ApiStreamUsageChunk {
reasoningTokens?: number
totalCost?: number
}

export interface ApiStreamGroundingChunk {
Copy link

Choose a reason for hiding this comment

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

Consider adding JSDoc documentation for the new grounding types to clarify their purpose. For example:

/**
 * Represents grounding metadata from search results or citations.
 * Used to decouple source information from the main content stream.
 */
export interface ApiStreamGroundingChunk {
	type: "grounding"
	sources: GroundingSource[]
}

case "grounding":
// Handle grounding sources separately from regular content
// to prevent state persistence issues - store them separately
if (chunk.sources && chunk.sources.length > 0) {
Copy link

Choose a reason for hiding this comment

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

Good separation of concerns here! The grounding sources are now properly handled independently from the assistant message content, which should prevent the race condition issues mentioned in #6372.

@@ -2086,9 +2094,30 @@
let didEndLoop = false

if (assistantMessage.length > 0) {
// Display grounding sources to the user if they exist
if (pendingGroundingSources.length > 0) {
Copy link

Choose a reason for hiding this comment

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

Nice implementation detail - storing grounding sources separately and only displaying them to the user while keeping the API history clean. This prevents the state persistence issues that were causing the race condition.

- Fixed failing test to expect grounding chunk instead of text with citations
- Added inline comments to regex patterns for source stripping in Task.ts
- Added test coverage for grounding source handling to prevent regression
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Aug 26, 2025
@daniel-lxs daniel-lxs moved this from PR [Needs Prelim Review] to PR [Needs Review] in Roo Code Roadmap Aug 26, 2025
Copy link
Collaborator

@daniel-lxs daniel-lxs left a comment

Choose a reason for hiding this comment

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

Thank you @HahaBill!

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Aug 26, 2025
@HahaBill
Copy link
Author

Thank you @HahaBill!

Anytime :)

// Display grounding sources to the user if they exist
if (pendingGroundingSources.length > 0) {
const citationLinks = pendingGroundingSources.map((source, i) => `[${i + 1}](${source.url})`)
const sourcesText = `Sources: ${citationLinks.join(", ")}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should probably internationalize "Sources"

Copy link
Author

Choose a reason for hiding this comment

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

Right, I will add it back.

if (pendingGroundingSources.length > 0) {
// Remove any grounding source references that might have been integrated into the message
cleanAssistantMessage = assistantMessage
.replace(/\[\d+\]\s+[^:\n]+:\s+https?:\/\/[^\s\n]+/g, "") // e.g., "[1] Example Source: https://example.com"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure about this. Does it have the potential to remove some content from the message unrelated to grounding? Is this a necessary thing to do in general?

Copy link
Author

Choose a reason for hiding this comment

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

I see, those are valid concerns. I personally think that it is unlikely unless users have custom prompts saying that the model should include in the response the grounding-like format content in some parts of the response.

But this might be premature optimization/edge-case handling so we can remove this for now.

Overall, these are answers to your questions:

  1. There's a small non-zero risk.
  2. No, it's not necessary. This might be a premature optimization.

@HahaBill
Copy link
Author

Alright, I made some additional changes:
commit 7e9a8c4c00741a7b3343dc8855679818218575fe

@mrubens mrubens merged commit c206da4 into RooCodeInc:main Sep 5, 2025
9 checks passed
@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Sep 5, 2025
@github-project-automation github-project-automation bot moved this from PR [Needs Review] to Done in Roo Code Roadmap Sep 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working lgtm This PR has been approved by a maintainer PR - Needs Review size:L This PR changes 100-499 lines, ignoring generated files.
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Gemini grounding sources appear as separate message after assistant's response
4 participants