Google ADK for Go で作る「アニメ聖地もわかる」日本レンタカー旅 AI Agent
【実践開発】Google ADK for Go で作る「アニメ聖地もわかる」日本レンタカー旅 AI Agent
成果物:
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
セットアップ:
-
examples/quickstart/main.goを自分のmain.goにコピー。 -
llmagent.New部分のllmagent.Configを修正して Agent の設定を変更。
4.2 カスタムツールの作成
tools.go ファイルを作成し、以下のツールを定義・実装します:
- レンタカー費用計算ツール
- ルート計画ツール
- 聖地巡礼検索ツール
完全なツールコード(例):
4.3 Agent へのツール登録
llmagent.Config の Tools リストに、作成した新しいツールを追加します。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 を実装 しました。
- API Key の取得: Google Programmable Search Engine で検索エンジンID (CX) と API Key を取得。
- .env に設定: 取得したキーを設定。
- ラッパー関数の実装:
// 検索を通常の 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
- プロジェクトルートに
.envファイル作成。 -
.gitignoreに.envを追加(GitHub への流出防止)。 -
main関数の冒頭でロードする、標準的な構成にしました。
6. 最終成果
調整の結果、Agent のワークフローは非常にスムーズになりました。

完全な結果例はこちら:
7. まとめ
このプロジェクトを通じて、Google ADK for Go の強力さを実感しました。「手足(ツール)」を型安全な Go コードで構築し、「頭脳(計画と判断)」を LLM に任せる という開発体験は非常に快適です。
現時点では API レベルでのツール混在制限などが存在しますが、今回のようにカスタムツールでラップすることで柔軟に回避でき、業務要件に合った高度なエージェントを構築できることがわかりました。
今後の計画:
- モックのルート検索 API を、実際の Google Maps Routes API に置き換える。
- アプリケーションを Docker 化し、AWS App Runner 等へデプロイする。
Discussion