🔥

GoにおけるGemini APIでの検索グラウンディングの活用

に公開

はじめに


こんにちは!サロンスタッフ予約サービス「minimo」でAI推進チームに所属している洗川です。

近年はLLM(大規模言語モデル)の進化が目覚ましく、多くのサービスで活用されています。しかし、LLMには「学習データに含まれない最新の情報は知らない」「事実に基づかない内容を生成することがある(ハルシネーション)」といった課題もあります。

今回ご紹介するGemini APIの検索グラウンディングは、これらの課題に対する非常に強力な機能です。Google検索の結果をリアルタイムに参照し、回答の精度と鮮度を向上させることができます。

本記事では、この検索グラウンディング機能をGoで実装する方法と、その際に少し工夫が必要な「出典URLの取り扱い」について解説します。

Gemini APIの検索グラウンディングとは?


検索グラウンディング(Grounding with Google Search)は、Geminiがユーザーからの質問に答える際に、Google検索の結果をリアルタイムに参照する機能です。

これにより、以下のようなメリットがあります。

  • 最新情報の取得: モデルの学習データに含まれていない、最新のニュースや出来事に関する質問にも答えられるようになる
  • ハルシネーションの抑制: 回答がWeb上の情報に基づいているため、より正確で信頼性の高い回答が期待できる
  • 出典の明記: 回答の根拠となったWebページのURLを提示してくれるため、ユーザーは情報の真偽を自分で確認できる

    例えば、「最近発表されたオリンピックの情報は?」のような、特定の時点での最新情報が必要な質問に対して、非常に有効です。

Goでの実装方法


それでは早速、Goで検索グラウンディングを実装する方法を見ていきましょう。公式のGoライブラリ (google.golang.org/genai) を使うと、簡単に実装できます。

実装コード


以下が、検索グラウンディングを有効にしてGemini APIを呼び出すサンプルコードです。

package main

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

	"google.golang.org/api/option"
	"google.golang.org/genai"
)

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

	client, err := genai.NewClient(ctx, &genai.ClientConfig{
		Project:  "your-project",
		Location: "some-gcp-location",
		Backend:  genai.BackendVertexAI,
	})
	if err != nil {
		log.Fatalf("NewClientに失敗しました: %v", err)
	}
	defer client.Close()

	model := client.GenerativeModel("gemini-2.5-flash")

	// --- 検索グラウンディングを有効にする ---
	model.Tools = []*genai.Tool{
		{GoogleSearch: &genai.GoogleSearch{}},
	}

	// 必要に応じてモデルの振る舞いを指示
	model.SystemInstruction = &genai.Content{
		Parts: []genai.Part{
			genai.Text("あなたは礼儀正しい日本語アシスタントです。50文字以内で答えてください。"),
		},
	}

	// ユーザーからの質問
	prompt := genai.Text("最近発表されたオリンピックの情報は?出典もほしい")

	// --- API呼び出し ---
	resp, err := model.GenerateContent(ctx, prompt)
	if err != nil {
		log.Fatalf("GenerateContentに失敗しました: %v", err)
	}

	// レスポンスのテキスト部分を出力
	for _, part := range resp.Candidates[0].Content.Parts {
		if txt, ok := part.(genai.Text); ok {
			fmt.Println(txt)
		}
	}

	// レスポンスのメタデータから出典情報を取得
	if resp.Candidates[0].GroundingMetadata != nil {
		fmt.Println("\n--- 出典 ---")
		for _, citation := range resp.Candidates[0].GroundingMetadata.Citations {
			fmt.Printf("URI: %s\n", citation.URI)
			fmt.Printf("Title: %s\n", citation.Title)
		}
	}
}


※ Vertex AIモデルを利用するため、GCPプロジェクトの設定などが必要です。今回は gemini-2.5-flash を利用する例にしています。

コードのポイント


実装のポイントは、model.Tools&genai.Tool{GoogleSearch: &genai.GoogleSearch{}} を設定する箇所です。これにより、Geminiは質問の内容に応じて自動的にGoogle検索を行い、その結果を考慮して回答を生成してくれます。

APIのレスポンスには、生成されたテキストだけでなく、GroundingMetadata が含まれており、ここから出典元のURLやタイトルを取得できます。

出典URLのリダイレクトを解決する


検索グラウンディングで取得できる出典URLですが、一つ注意点があります。返却されるURLは、https://vertextaisearch.google.com/... のような、Googleの検索結果を指す一時的なURLになっているようです。

このURLは、最終的な出典元のページにリダイレクトされるようになっています。そのため、ユーザーに提示する前に、このリダイレクトを解決して最終的なURLを取得する必要があります。

リダイレクト解決コード


Goの標準ライブラリ net/http を使うことで、リダイレクト先のURLを簡単に取得できます。

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"
)

// 与えられたURLを元に最終的なURLを返す
func resolveFinalURL(ctx context.Context, url string) (string, error) {
	// リクエストのタイムアウトを設定
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return "", fmt.Errorf("NewRequestWithContextに失敗しました: %w", err)
	}

	// User-Agentは適当なものを指定
	req.Header.Set("User-Agent", "resolve-client")
	// 転送量を最小化するための対応
	req.Header.Set("Range", "bytes=0-0")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("DefaultClient.Doに失敗しました: %w", err)
	}
	defer resp.Body.Close()

	// レスポンスオブジェクトのRequestフィールドには、最終的なリクエスト情報が格納されている
	return resp.Request.URL.String(), nil
}

func main() {
	// Gemini APIから返されるURLの例(ダミー)
	// 実際にはAPIのレスポンスからこのURLを取得する
	originalURL := "http://example.com/redirect"

	fmt.Printf("元のURL: %s\n", originalURL)

	finalURL, err := resolveFinalURL(context.Background(), originalURL)
	if err != nil {
		log.Fatalf("resolveFinalURLに失敗しました: %v", err)
	}

	fmt.Printf("解決後のURL: %s\n", finalURL)
}

コードのポイント


Goの http.DefaultClient は、デフォルトでリダイレクトを自動的に追跡してくれるため、特別な設定は不要です。http.Client.Do() を実行した後のレスポンス resp に含まれる resp.Request.URL を見るだけで、リダイレクト後の最終的なURLを取得できます。
また、Rangeヘッダーを指定することで、転送量を最小化しています。

この一手間を加えることで、ユーザーが直接アクセスできる正しい出典情報を提供できます。

まとめ


本記事では、Gemini APIの検索グラウンディング機能をGoで実装する方法と、その際の出典URLの扱い方について解説しました。

  • 検索グラウンディングは、ToolsGoogleSearch を追加すれば有効化できる
  • 最新情報に基づいた、信頼性の高い回答を生成するのに非常に役立つ
  • 出典URLはリダイレクトされるため、net/http を使って最終的なURLを取得する必要がある

    本記事が、Gemini APIの活用を検討している方の参考になれば幸いです。

最後に

minimoでは、サービス開発に一緒に挑戦してくれる仲間を募集しています!ご興味のある方は、ぜひ採用ページをご覧ください。

https://mixigroup-recruit.mixi.co.jp/jobs/
https://mixigroup-recruit.mixi.co.jp/recruitment-category/career/12083/?minimo

MIXI DEVELOPERS Tech Blog

Discussion