🐡

Google ADK for Go で作る「アニメ聖地もわかる」日本レンタカー旅 AI Agent

に公開

【実践開発】Google ADK for Go で作る「アニメ聖地もわかる」日本レンタカー旅 AI Agent

成果物:
https://github.com/nn10n10/japan-drive-rentcar-agent/tree/main

1. 背景と動機

私は普段の旅行でレンタカーを利用することが多く、アニメも大好きです。そこで、自分のニーズに特化した AI Agent を開発することにしました。

私が必要としていたのは以下のような機能です:

  • ホテル予約は不要:基本的に日帰りか、すでに宿泊は確保していることが多いため。
  • レンタカー料金の複雑な比較:私がよく利用する2大カーシェア・レンタカーサービス(タイムズカーとdカーシェア/カリテコ)は、課金体系(時間料金 vs 距離料金)が異なり、比較計算が非常に面倒です。
  • 聖地巡礼の提案:ルート計画時に、近くにあるアニメの「聖地」を提案してほしい。

この課題を解決するため、Googleが発表したばかりの ADK for Go (Agent Development Kit) を使用し、自分専用の AI Agent を開発することにしました。

2. 技術選定:なぜ ADK for Go なのか

Python ベースのフレームワーク (LangChain/LlamaIndex) ではなく、ADK for Go を選んだ主な理由は以下の通りです:

  • コード優先:大量の YAML や JSON 設定ファイルを書くよりも、Go の堅牢な構造体とインターフェースでツールを定義する方を好むため。
  • ネイティブな並行処理:ルート検索や観光スポット検索など、複数の API を並行して呼び出す際に、Go の並行処理モデルが非常に適しているため。
  • デプロイの容易さ:シングルバイナリにコンパイルでき、Docker や Cloud Run と組み合わせたデプロイが非常に簡単で、Cloud Native なトレンドにも合致しています。

主な技術スタック:

  • フレームワーク: Google ADK for Go
  • モデル: Gemini 2.5 Flash / Pro
  • 検索: Google Custom Search JSON API
  • 設定: godotenv

3. コア機能の実装

この Agent は単なるチャットボットではありません。Go のネイティブ関数として実装された3つの主要な「ツール (Tools)」を搭載しています。

3.1 レンタカー精算ツール

これが本プロジェクトの最大のハイライトです。LLM は厳密な計算が苦手なため、比較ロジックは Go の関数として実装しました。

  • 入力: 総距離 (km), 総利用時間 (時間)
  • ロジック: タイムズカーとdカーシェアの複雑な料金体系(距離料金の発生条件、ナイトパック、夜間割引など)を比較。
  • 出力: どちらがどれくらい安いかを具体的に提示。
func calculateCarRental(ctx tool.Context, args CarRentalArgs) (*CarRentalCost, error) {
	log.Printf("Tool: %d km、%d 時間の利用でカーシェア料金を計算中... (Times vs Cariteco Compact)", args.DistanceKM, args.DurationHour)

	// --- タイムズカー料金計算(2025年12月1日~) ---
	// 時間料金: ベーシック 880円/時間 (220円/15分)
	// 距離料金: 20km超過分のみ 20円/km
	timesTimeCost := args.DurationHour * 880
	timesDistanceCost := 0
	if args.DistanceKM > 20 {
		timesDistanceCost = (args.DistanceKM - 20) * 20
	}
	timesTotalCost := timesTimeCost + timesDistanceCost
	timesBreakdown := "基本料金: 時間(" + itoa(timesTimeCost) + "円) + 距離(" + itoa(timesDistanceCost) + "円)"

	// --- dカーシェア(カリテコ・コンパクトクラス)料金計算 ---
	// 時間料金: 280円/15分 -> 1120円/時間
	// 距離料金: 18円/km (6時間以下の予約・利用で無料)
	// ※詳細化: 最安プラン(通常料金 vs パック料金)を自動適用

	// 1. 通常料金(時間課金)
	dcarTimeRateCost := args.DurationHour * 1120
	// ... (中略: 具体的な計算ロジック) ...
	
	// 推薦判定
	recommendation := ""
	diff := timesTotalCost - bestDcarCost
	if diff > 0 {
		recommendation = "dカーシェア(カリテコ)が " + itoa(diff) + "円 お得です"
	} else if diff < 0 {
		recommendation = "タイムズカーが " + itoa(-diff) + "円 お得です"
	} else {
		recommendation = "両サービス同額です"
	}

	return &CarRentalCost{
		TimesCarCost:       timesTotalCost,
		TimesCarBreakdown:  timesBreakdown,
		DCarShareCost:      bestDcarCost,
		DCarShareBreakdown: bestDcarBreakdown,
		Recommendation:     recommendation,
	}, nil
}

3.2 ルートプランニング

2地点間の運転距離と時間を計算するナビゲーション API をシミュレートしています。これが後のレンタカー比較の基礎データとなります。

func findDrivingRoute(ctx tool.Context, args RouteArgs) (*RouteInfo, error) {
	log.Printf("Tool: %s から %s への駕驶ルートを検索中...", args.Origin, args.Destination)
	// モックデータ(実際はGoogle Maps APIなどを使用)
	return &RouteInfo{
		DistanceKM:   80,
		DurationHour: 2,
		Traffic:      "順調",
		BestRoute:    "東名高速道路経由",
	}, nil
}

3.3 聖地巡礼サーチ

位置情報に基づき、周辺の観光スポットや二次元コンテンツ(アニメなど)の聖地を検索します。(後述の「ハマりポイント」で触れますが、実用化のために大きなリファクタリングを行いました)

func findAttractions(ctx tool.Context, args AttractionArgs) (*AttractionsResult, error) {
	log.Printf("Tool: %s 周辺の観光スポットを検索中(アニメ聖地含む: %v)...", args.Area, args.IncludeAnimeSpot)

	// モックデータ(実際はGoogle Places APIやアニメ聖地DBを使用)
	attractions := []AttractionInfo{
		{
			Name:        "鶴岡八幡宮",
			Type:        "神社仏閣",
			AnimeName:   "",
			Description: "鎌倉を代表する神社。源頼朝ゆかりの地。",
			Address:     "神奈川県鎌倉市雪ノ下2-1-31",
		},
		{
			Name:        "江ノ島電鉄 鎌倉高校前駅",
			Type:        "アニメ聖地",
			AnimeName:   "スラムダンク",
			Description: "オープニングで有名な踏切がある場所。",
			Address:     "神奈川県鎌倉市腰越1丁目",
		},
		// ...
	}

	return &AttractionsResult{
		Attractions: attractions,
		Area:        args.Area,
	}, nil
}

4. 開発フロー

4.1 環境準備

ADK for Go のインストール:

go mod init travel-planner
go get google.golang.org/adk

セットアップ:

  1. examples/quickstart/main.go を自分の main.go にコピー。
  2. llmagent.New 部分の llmagent.Config を修正して Agent の設定を変更。

4.2 カスタムツールの作成

tools.go ファイルを作成し、以下のツールを定義・実装します:

  • レンタカー費用計算ツール
  • ルート計画ツール
  • 聖地巡礼検索ツール

完全なツールコード(例):

https://github.com/nn10n10/japan-drive-rentcar-agent/blob/main/tools.go

4.3 Agent へのツール登録

llmagent.ConfigTools リストに、作成した新しいツールを追加します。quickstart に含まれていた GoogleSearch ツールと併用(しようとしましたが、後述の問題が発生)します。

4.4 依存関係の整理

go mod tidy

5. 開発中のハマりポイント(重要)

今回の開発で遭遇した代表的な問題と解決策を共有します。これは本プロジェクトでの最大の学びでもありました。

ハマりポイント 1:ADK 起動モードの混乱

現象:
ビルドした .exe をダブルクリックしてもすぐに終了してしまう。あるいは go run . web で起動後、ブラウザでアクセスしても真っ白な画面かエラーになる。

原因:
ADK の Launcher の仕組みが独特であるため。

  • console モード:コマンドライン対話用。
  • web モード:Web コンテナを起動するだけで、子サービスを明示的に指定する必要がある。

解決策:
正しい起動コマンドは以下の通りです。これでバックエンドAPIとフロントエンドUIの両方が立ち上がります。

go run . web api webui

ハマりポイント 2:Error 400: Tool use with function calling is unsupported

これが最も厄介な問題でした。

ニーズ:
自作した calculate_car_rental(関数ツール)と、Google 公式の geminitool.GoogleSearch(公式検索ツール)を一つの Agent で併用したい

エラー内容:

分析:
調査の結果、現在の Gemini API(特に Flash モデル等)の制限により、「公式検索ツール (geminitool.GoogleSearch)」と「Function Calling (ユーザー定義関数ツール)」を同じ Agent 設定内で混在させることができない(どちらか一方しか使えない)仕様のようです。

解決策:
この制限を回避するため、ADK 組み込みの GoogleSearch ツールを諦め、Google Custom Search JSON API を呼び出す自作の Go 関数 searchWeb を実装 しました。

  1. API Key の取得: Google Programmable Search Engine で検索エンジンID (CX) と API Key を取得。
  2. .env に設定: 取得したキーを設定。
  3. ラッパー関数の実装:
// 検索を通常の Function Tool 化する偽装工作
func searchWeb(ctx context.Context, query string) (string, error) {
    // https://www.googleapis.com/customsearch/v1 を呼び出す
    // API Key と CX をパラメータに付与
    // 結果の JSON から Snippet を抽出して返す
}

// ツールとして登録
realSearchTool, _ := functiontool.New(..., searchWeb)

結果:
Agent から見れば、この検索ツールは私が書いたレンタカー計算機と何ら変わらない「ただの関数」です。これにより、API の混在制限を完全に回避しつつ、Web 検索能力を持たせることに成功しました!

ハマりポイント 3:API Key の安全な管理

現象:
ターミナルを再起動するたびに export GOOGLE_API_KEY=... を入力するのが面倒。

解決策:
godotenv ライブラリを導入しました。

go get github.com/joho/godotenv
  1. プロジェクトルートに .env ファイル作成。
  2. .gitignore.env を追加(GitHub への流出防止)。
  3. main 関数の冒頭でロードする、標準的な構成にしました。

6. 最終成果

調整の結果、Agent のワークフローは非常にスムーズになりました。

Agent Demo

完全な結果例はこちら:

https://github.com/nn10n10/japan-drive-rentcar-agent/blob/main/例.md

7. まとめ

このプロジェクトを通じて、Google ADK for Go の強力さを実感しました。「手足(ツール)」を型安全な Go コードで構築し、「頭脳(計画と判断)」を LLM に任せる という開発体験は非常に快適です。

現時点では API レベルでのツール混在制限などが存在しますが、今回のようにカスタムツールでラップすることで柔軟に回避でき、業務要件に合った高度なエージェントを構築できることがわかりました。

今後の計画:

  • モックのルート検索 API を、実際の Google Maps Routes API に置き換える。
  • アプリケーションを Docker 化し、AWS App Runner 等へデプロイする。

Discussion