梦兽编程
AI_SUITE

Grounding: Search-Enhanced Generation for More Reliable Answers

Detailed explanation of the Grounding mechanism in ADK Go—using Google Search Grounding to let Agent answers based on real search results, reducing hallucinations.

The core strength of a large language model (LLM) is generating coherent, plausible text based on its training data. However, its inherent knowledge cutoff and hallucination problems are unacceptable in serious production environments. When a user asks “What are the latest features of the Go release in 2025?” or “What is the latest earnings report for a given company?”, the model either gives a wrong answer based on outdated knowledge or simply fabricates information that looks reasonable but is false. Grounding (search-enhanced generation) was designed specifically to solve this fundamental problem: it forces the Agent to fetch real-time, verifiable facts from external authoritative sources before generating an answer, anchoring the generation process to the real world.

Why Grounding Is a Must-Have for Production Agents

Before diving into the technical implementation, we need to understand the strategic role of Grounding in AI application architecture. An LLM application without Grounding is essentially a closed system whose output quality is bounded by the limits of its pre-training data. This causes serious issues in the following scenarios:

1. Time-sensitive domains Financial analysis, news summaries, and technical documentation queries require the latest information. A model trained on early-2024 data will inevitably have blind spots when discussing Go 1.24 features in 2025.

2. Domains with high factual-accuracy requirements In medical, legal, and engineering-standard queries, factual errors can have serious consequences. Grounding introduces traceable search results that provide an evidence chain for the answer.

3. Dynamic knowledge-base scenarios Internal company documents, product manuals, and inventory data change daily. Grounding allows the Agent to query these dynamic data sources in real time instead of relying on static training knowledge.

From an architectural perspective, Grounding realizes the paradigm shift from pure generation to Retrieval-Augmented Generation (RAG). The Google Search Grounding provided by ADK Go is a specific implementation whose principle is consistent with the general RAG architecture: Retrieve → Augment → Generate.

Core Architecture and Data Flow

Understanding the complete data flow of Grounding is essential for debugging and optimizing in production. A typical Grounding request inside ADK Go goes through the following stages:

User input
[Intent analysis] — the model decides whether external information is needed
[Query generation] — extract or reformulate search queries from the input
[Search execution] — call the Google Search API to get real-time results
[Result filtering] — relevance ranking, deduplication, truncation
[Context assembly] — inject search results into the prompt context
[Answer generation] — generate the final response based on the augmented context
[Citation annotation] — mark information sources in the answer (if supported)

Every step in this pipeline can become a performance bottleneck or quality degradation point. For example, if the keywords extracted during query generation are too broad, many irrelevant results will be returned and dilute the useful context; if the truncation strategy in result filtering is poor, key information may be lost; if the injected text in context assembly is too long, it may exceed the model context-window limit.

Enabling Grounding in ADK Go

ADK Go’s grounding package provides out-of-the-box Google Search Grounding. Below is a complete, production-tested configuration example:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/grounding"
    "google.golang.org/adk/llm"
)

func main() {
    ctx := context.Background()

    // Initialize the model
    model, err := llm.NewGeminiModel(ctx, llm.GeminiConfig{
        APIKey:   os.Get...Y"),
        Model:    "gemini-2.0-flash",
        // Reserve enough context window for Grounding
        MaxTokens: 8192,
    })
    if err != nil {
        log.Fatalf("Failed to initialize model: %v", err)
    }

    // Configure Grounding options
    grounder, err := grounding.NewGoogleSearchGrounder(
        grounding.WithMaxResults(5),           // Use at most 5 search results
        grounding.WithResultTimeout(10*time.Second), // 10-second search timeout
        grounding.WithLanguageHint("zh-CN"),   // Prefer Chinese results
    )
    if err != nil {
        log.Fatalf("Failed to initialize Grounding: %v", err)
    }

    // Create the Agent
    agent, err := agent.New(agent.Config{
        Name:  "grounded-research-agent",
        Model: model,
        Instruction: `You are a research assistant. When answering user questions, you must:
1. Prioritize facts from search results
2. If search results are insufficient, clearly state "Based on the available search results, I cannot determine..."
3. Cite information sources in your answer
4. For time-sensitive information, note the timestamp`,
        Grounding: grounder,
        // Enable citation annotations so users can trace back to original sources
        EnableCitations: true,
    })
    if err != nil {
        log.Fatalf("Failed to create Agent: %v", err)
    }

    // Execute the query
    resp, err := agent.Run(ctx, "What are the important new features of the latest Go release in 2025?")
    if err != nil {
        log.Fatalf("Execution failed: %v", err)
    }

    fmt.Println(resp.Text)
    // The output may contain citation markers such as [1] corresponding to search results
}

Key Configuration Parameters

ParameterPurposeProduction Recommendation
MaxResultsControls the number of search results injected into context3-5 is appropriate; too many dilute attention and increase token consumption
ResultTimeoutTimeout for the search API5-15 seconds; design with the overall request timeout in mind
LanguageHintSearch language preferenceSet according to the user base; dynamically adjust for multilingual scenarios
EnableCitationsWhether to add citation markers in the answerRecommended for credibility

Advanced Pattern: Hybrid Grounding Strategy

In production, a single Google Search Grounding is often not enough to cover all scenarios. A more robust architecture is hybrid Grounding—dynamically selecting an information source based on the query type:

type HybridGrounder struct {
    webGrounder     *grounding.GoogleSearchGrounder
    internalRAG     *InternalRAGClient      // internal enterprise document retrieval
    knowledgeBase   *KnowledgeBaseClient    // structured knowledge base
}

func (h *HybridGrounder) Ground(ctx context.Context, query string) (*grounding.Result, error) {
    // 1. Classify the query to decide which information source to use
    category := classifyQuery(query)

    switch category {
    case CategoryInternalDoc:
        // Internal document queries: prefer enterprise RAG
        return h.internalRAG.Search(ctx, query)
    case CategoryStructuredData:
        // Structured data queries: use knowledge base
        return h.knowledgeBase.Query(ctx, query)
    case CategoryGeneralKnowledge:
        // General knowledge queries: use Google Search
        return h.webGrounder.Ground(ctx, query)
    default:
        // Default strategy: query all sources in parallel, merge and deduplicate
        return h.fallbackGround(ctx, query)
    }
}

func classifyQuery(query string) QueryCategory {
    // Route based on keywords or a small classifier
    // For example: contains "internal", "manual" -> CategoryInternalDoc
    // contains "stock price", "price" -> CategoryStructuredData
    // otherwise -> CategoryGeneralKnowledge
    // In production, use a lightweight classifier or rule engine
    lower := strings.ToLower(query)
    if strings.Contains(lower, "internal") || strings.Contains(lower, "manual") {
        return CategoryInternalDoc
    }
    if strings.Contains(lower, "stock price") || strings.Contains(lower, "price") {
        return CategoryStructuredData
    }
    return CategoryGeneralKnowledge
}

The core advantage of this hybrid strategy is that different information sources have different latency, cost, and accuracy characteristics. For example, internal RAG based on a vector database has latency usually under 100 ms but limited coverage; Google Search covers the whole web but may take 1-3 seconds and is billed per call. Intelligent routing balances effectiveness, cost, and performance.

Pitfalls and Best Practices in Production

Pitfall 1: Grounding Causes Latency to Spiral Out of Control

Grounding introduces an external network call (search API), which can become a bottleneck under high concurrency. We once saw a customer-service Agent project where P99 latency spiked from 2 seconds to 15 seconds during peak hours because the search API concurrent quota was exhausted and requests started queuing.

Solution:

type CachedGrounder struct {
    inner   grounding.Grounder
    cache   *ristretto.Cache  // high-performance local cache
    ttl     time.Duration
}

func (c *CachedGrounder) Ground(ctx context.Context, query string) (*grounding.Result, error) {
    // Cache based on a normalized query
    normalized := normalizeQuery(query)

    if val, found := c.cache.Get(normalized); found {
        return val.(*grounding.Result), nil
    }

    result, err := c.inner.Ground(ctx, query)
    if err != nil {
        return nil, err
    }

    // Cache TTL should not be too long; the value of Grounding lies in freshness
    c.cache.SetWithTTL(normalized, result, 1, c.ttl)
    return result, nil
}

func normalizeQuery(q string) string {
    // Remove extra spaces, unify case, remove punctuation
    // Increase cache hit rate while avoiding over-normalization that loses semantics
    q = strings.ToLower(strings.TrimSpace(q))
    q = regexp.MustCompile(`\s+`).ReplaceAllString(q, " ")
    return q
}

A cache TTL of 5-15 minutes is recommended to relieve pressure from hot queries without making information too stale.

Pitfall 2: Search Result Quality Is Uncontrollable

Search engines do not always return reliable results. Low-quality websites, outdated pages, and SEO spam can all be injected into the context and pollute the model.

Solution: Implement multi-layer quality filtering

type ResultFilter struct {
    blockedDomains map[string]bool
    minContentLen  int
    maxAge         time.Duration
}

func (f *ResultFilter) Filter(results []grounding.SearchResult) []grounding.SearchResult {
    var filtered []grounding.SearchResult
    for _, r := range results {
        // Domain blacklist filter
        if f.blockedDomains[r.Domain] {
            continue
        }
        // Content length filter (exclude empty pages or pure navigation pages)
        if len(r.Snippet) < f.minContentLen {
            continue
        }
        // Freshness filter
        if time.Since(r.LastModified) > f.maxAge {
            continue
        }
        filtered = append(filtered, r)
    }
    return filtered
}

Pitfall 3: Context Window Overflow

Grounding appends search results to the prompt. If the results are too long, they may exceed the model context-window limit and cause the request to fail or early context to be truncated.

Solution: Implement smart truncation and summarization

func truncateResults(results []grounding.SearchResult, maxTokens int) []grounding.SearchResult {
    // Rough estimate: 1 Chinese character ≈ 1.5 tokens, English word ≈ 1.3 tokens
    currentTokens := 0
    var truncated []grounding.SearchResult

    for _, r := range results {
        estimatedTokens := len(r.Snippet) * 3 / 2  // conservative estimate
        if currentTokens+estimatedTokens > maxTokens {
            break
        }
        truncated = append(truncated, r)
        currentTokens += estimatedTokens
    }
    return truncated
}

It is recommended to reserve no more than 30%-40% of the total context window for Grounding, leaving enough room for conversation history and system instructions.

Pitfall 4: Cost Loss of Control

Google Search Grounding is usually billed per call. In high-traffic scenarios, costs can accumulate rapidly. An unprotected Agent facing malicious traffic or abnormal spikes can generate huge bills.

Solution:

  1. Rate limiting: Implement rate limiting at the Agent entry point.
  2. Query deduplication: Reuse cached results for identical or highly similar queries.
  3. Degradation strategy: When Grounding cost or latency is too high, allow falling back to pure model generation (clearly inform the user).
type RateLimitedGrounder struct {
    inner    grounding.Grounder
    limiter  *rate.Limiter  // golang.org/x/time/rate
}

func (r *RateLimitedGrounder) Ground(ctx context.Context, query string) (*grounding.Result, error) {
    if !r.limiter.Allow() {
        return nil, fmt.Errorf("grounding rate limit exceeded")
    }
    return r.inner.Ground(ctx, query)
}

The Relationship Between Grounding and RAG

Many developers confuse Grounding and RAG. Simply put:

  • Grounding is a specific implementation of RAG that uses a search engine (such as Google Search) to obtain real-time web information to enhance generation.
  • RAG is a broader architectural pattern whose information source can be a search engine, vector database, relational database, API, or any other external data source.

In the ADK Go context, grounding.NewGoogleSearchGrounder() provides the former. If your application needs to retrieve internal enterprise documents, you need to implement your own vector-database-based RAG pipeline and integrate it into the Agent as a Tool or custom grounder.

Debugging and Observability

In production, debugging Grounding is often more complex than debugging pure model generation because failure points can be distributed across search, result processing, context assembly, and other stages. Implement the following observability measures:

// Inject logging and metrics inside the Grounder
func (g *ObservableGrounder) Ground(ctx context.Context, query string) (*grounding.Result, error) {
    start := time.Now()

    result, err := g.inner.Ground(ctx, query)

    duration := time.Since(start)

    // Record metrics
    metrics.RecordHistogram("grounding_latency_ms", float64(duration.Milliseconds()))
    metrics.IncrementCounter("grounding_total")

    if err != nil {
        metrics.IncrementCounter("grounding_errors")
        log.Printf("[Grounding] query failed | query=%s | error=%v | duration=%v", query, err, duration)
        return nil, err
    }

    log.Printf("[Grounding] query succeeded | query=%s | results=%d | duration=%v",
        query, len(result.Snippets), duration)

    return result, nil
}

Key monitoring metrics include:

  • Grounding call latency: P50, P95, P99
  • Search result count distribution: Detect large numbers of empty results
  • Grounding failure rate: Distinguish network timeout, quota exhaustion, API errors, and other causes
  • Cache hit rate: Evaluate the effectiveness of the caching strategy

Next Steps

Grounding solves the problem of what the Agent knows—using real-time search to anchor the Agent’s answers to the real world. Next we will explore Artifacts—how to make the Agent generate structured, reusable content outputs, which is crucial for code generation, document writing, and similar scenarios.

Cross-Language | Artifacts →


Want to learn more Go ADK hands-on? Follow the “Full-Stack Peak — Mengshou Programming” WeChat account for weekly Go / AI programming practice updates.

Frequently Asked Questions

What is Grounding and why is it important for production Agents?

Grounding (search-enhanced generation) forces the Agent to query external authoritative sources before generating an answer, anchoring the generation process to real-time, verifiable facts and reducing LLM hallucinations and knowledge-cutoff problems.

How do I enable Google Search Grounding in ADK Go?

Turn on the grounding option in the model or Agent configuration and correctly set up the search tool parameters so retrieval is triggered automatically before the model is called.

What are the key Grounding configuration parameters?

Common ones include the retrieval source, number of returned results, confidence threshold, fallback strategy, and whether to force the use of retrieved results.

What is a hybrid Grounding strategy?

A hybrid strategy flexibly combines patterns such as retrieve-then-generate, interleaved retrieval and generation, or multi-source result fusion according to the question type to balance cost and quality.

What pitfalls should I watch out for when using Grounding in production?

Pay attention to latency fluctuations, search result quality, cost overhead, privacy compliance, and reasonable caching and degradation strategies.