Skip to main content

Google Search grounding

An agent that has to know what happened this week cannot answer from the model's training data. You can wire up a news API, parse its feed, and read each article in a separate node. Or you can let the model search Google itself. Turn on Ground with Google Search on an instructions node and the model runs the searches it needs while it answers, reads the results, and cites them. The node then saves the queries it ran and the sources it cited, with the article URLs, so the next nodes in the workflow can use them.

Find it in the agent builder: open an instructions node, scroll to Web search and turn on Ground with Google Search. In a spec file it is one line in the node's config:

{
"id": "research",
"type": "action",
"actionType": "INSTRUCTIONS",
"mode": "sync",
"outputVar": "research",
"config": {
"instructions": "Find news from the last 30 days about {{company}}. For each article, write the outlet, the date and what it says.",
"grounding": { "googleSearch": {} },
"internal": true
},
"transition": [{ "to": { "nodeId": "rank" } }]
}

excludeDomains keeps sites out of the results: "googleSearch": { "excludeDomains": ["youtube.com"] }. In the builder, type them into the Excluded domains field under the switch. A domain excludes its subdomains too.

What the node gives you

Two things come out of a grounded node: the reply, and the sources behind it.

The reply is the same as any other instructions node. Plain text goes to the user and to outputVar. With a structured output schema you get the JSON object you declared.

The sources ride on the node result, under grounding. Read them in a later JavaScript node as nodes.research.grounding, or in a template as {{nodes.research.grounding.queries}}:

{
"provider": "google_search",
"queries": ["Transborder polícia federal doleiro", "\"Transborder\" operação exchange"],
"sources": [
{
"id": 1,
"url": "https://g1.globo.com/sp/sao-paulo/noticia/2026/07/04/rede-transnacional-pf-aponta-doleiro.ghtml",
"redirectUrl": "https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH...",
"domain": "g1.globo.com",
"kind": "web",
"citedText": ["PF aponta que empresário alvo de sanções atuava como doleiro em ao menos sete países"]
}
]
}
FieldWhat's in it
queriesThe Google searches the model decided to run, in order
sources[].urlThe article URL. Google returns a redirect link, and Docana follows it once to get the real address
sources[].redirectUrlThe redirect link Google returned, kept for auditing
sources[].domainThe publisher's host, without www.
sources[].kindweb for an article, search_results_page when the link lands on a Google results page instead of a page with content
sources[].citedTextThe passages of the reply this source supports, as Google reported them

A JavaScript node can turn that into whatever your workflow needs. This one keeps articles only and tags each by publisher:

const sources = (nodes.research.grounding && nodes.research.grounding.sources) || [];
return sources
.filter((source) => source.kind === 'web')
.map((source) => ({ url: source.url, publisher: source.domain, snippet: source.citedText[0] || '' }));

Grounding with structured output

You can combine grounding with a structured output schema. The node then runs in two steps: the model searches and writes down what it found, then a second call turns those notes into your JSON. The second call gets the numbered source list, so if you want citations inside the JSON, declare a field for them:

"outputSchema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"outlet": { "type": "string" },
"publishedAt": { "type": "string" },
"sourceIds": { "type": "array", "items": { "type": "number" } }
}
}
}
}
}

sourceIds then holds the id values from grounding.sources, which is how a later node joins an item to its URL. Ask the model to copy URLs verbatim from the source list if you prefer a url field. Do not ask it to write URLs from memory: on its own the model produces plausible links that do not exist.

Grounding in a forEach body

To research a list of things, make the grounded node the body of a forEach and the sources come back attributed to each item. Each entry of the collectInto array carries the grounding of its own run, in the shape shown above:

{
"id": "fanout_partners",
"type": "forEach",
"over": "research.partners",
"as": "partner",
"each": { "nodeId": "partner_news" },
"collectInto": "partnerResults",
"transition": [{ "to": { "nodeId": "merge_evidence" } }]
}

When partner_news is an instructions node with grounding on, vars.partnerResults[2].grounding holds the queries and sources of the third partner's search, not a mix of everyone's. A JavaScript node can turn that into one evidence list:

return vars.partnerResults
.filter((entry) => entry.ok && entry.grounding)
.flatMap((entry) =>
entry.grounding.sources
.filter((source) => source.kind === 'web')
.map((source) => ({
partner: entry.item.name,
url: source.url,
publisher: source.domain,
snippet: source.citedText[0] || '',
}))
);

One thing to avoid: do not read nodes.partner_news.grounding after the loop. The body ran once per item, and that slot keeps only the last run. The per-item envelopes are the record.

Limits

Know these before you build on it:

  • Google returns the domain, not the article title or date. sources[].domain is reliable. The title and the publication date only exist in the model's text, so ask for them in the instruction ("write the outlet, the date and the title of each article") and read them from the reply or the structured output.
  • Recall varies between runs. The same question returns 3 to 6 sources per call, and the set differs from one call to the next. Treat grounding as one more search provider next to the ones you already use, not as the only one.
  • Search cannot ride a forced tool call. A node with tool access set to a single forced tool, or with "suppress text" on, cannot use grounding. The builder blocks the switch and the spec validator rejects the combination. Tool access "auto" and "allowlist" work, and "none" means the model still searches but calls no other tool.
  • Billing is per search query, not per prompt: Google charges each query the model decides to run (usually 4 to 10 per prompt) beyond the project's monthly free tier. The pages Google reads do not count as input tokens. The docana_ai_grounding_queries_total metric tracks the volume.
  • Date filters are not available. Put the time window in the instruction ("last 30 days") and filter by the dates the model reports.
  • Grounding is independent of the application's Allowed Websites list. That list controls tools that fetch a URL you (or the model) point at — the web fetch tool and the browser. Grounding never fetches a URL from Docana's side: the model sends search queries and Google reads the pages on its own servers, and Vertex offers no allowlist for it. To constrain sources, use excludeDomains, or filter sources[].domain in a JavaScript node — and if a later node fetches one of the source URLs with the web fetch tool, the Allowed Websites list applies there as usual.