Gemini Enterprise で Backlog API を呼び出すカスタムエージェントを使えるようにする

Gemini Enterprise で Backlog API を呼び出すカスタムエージェントを使えるようにする

Backlog API を呼び出すカスタムエージェントを ADK で開発し、Gemini Enterprise で利用できるようにするまでの手順を検証してみました。OAuth を利用してユーザーが持つ権限範囲内で API 操作する方法についても確認していきます。
2025.12.12

はじめに

クラスメソッド Google Cloud Advent Calendar 2025 - Adventar 11日目の記事では、外部 API を呼び出すエージェントを ADK(Agent Development Kit) で開発し、Gemini Enterprise で利用できるようにするまでを検証してみることにしました。

検証の目的

本ブログでは、以下を確認する目的で検証した結果を共有します。

  • ADK で開発したエージェントを Gemini Enterprise のエージェントとして利用できるようにする方法
  • Gemini Enterprise エージェントで OAuth 2.0 を構成し、エージェントに対してユーザが持つ権限の範囲内で外部 API にアクセスさせる方法

ADK でシンプルなエージェントアプリを開発し、上記を確認していこうと思います。

今回、外部 API として Backlog API を利用します。Backlog は弊社内でも利用しており、エージェント利用のさまざまなユースケースが想定できるためです。

なお、 外部 API を利用したエージェントの効果的な活用方法や、期待するアウトプットを最大化するエージェント開発といった観点での検証は本記事の範囲外 となります。

上記の検証にあたっては以下の公式ドキュメントを参照しています。

https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent?hl=ja

エージェントを ADK で開発する

要件と実現方式

まずは、今回開発するシンプルな API 呼び出しエージェントの要件と実現方式を以下に整理します。

要件 実現方式
自然言語でエージェントに指示し、エージェントに Backlog API へコールさせる ADK の Function tools を利用
エージェントに最近見た Backlog 課題を検索してもらう Backlog API 自分が最近見た課題一覧の取得 を利用
エージェントに課題のステータスを更新してもらう Backlog API 課題情報の更新 を利用
エージェントを利用するユーザが持つ権限の範囲内で Backlog の情報にアクセスする OAuth 2.0 を実装

では、以下より作業を進めていきます。

開発環境を準備する

ADK によるエージェント開発とデプロイのベースとなる開発環境を準備していきます。

本検証ではエージェントを Python で開発します。ADK がサポートする Python バージョンは 3.10 以降ですが、エージェントのデプロイ先となる Vertex AI Agent Engine へのデプロイは 3.9-3.13 をサポート しているようです。
uv を利用して仮想環境を作ります。

% mkdir agents
% uv init
% uv sync
% source .venv/bin/activate
% python --version
Python 3.13.10

ADK をインストールします。

% uv add google-adk
% adk --version
adk, version 1.20.0

Google Cloud CLI が開発環境にインストールされていなければ、以下を参考にインストールします。

https://docs.cloud.google.com/sdk/docs/install-sdk?hl=ja

Google Cloud 各種サービスの有効化やリソースの用意

Gemini Enterprise を有効化する Google Cloud プロジェクトを用意します。
また、今回開発したエージェントは Vertex AI Agent Engine (以後 Agent Engine と表現します)にデプロイします。

Agent Engine は AI エージェントが稼働するサーバレスランタイム環境を提供するマネージドサービスです。現時点ではプレビューの機能が多いですが、エージェントの品質/評価、プレイグラウンド機能、ログ/トレースなどのオブザーバビリティ機能などエージェント開発/運用のライフサイクルをサポートするさまざまな機能を提供しています。
Gemini Enterprise は Agent Engine にデプロイしたカスタムエージェントを登録することができます。


ここから Google Cloud 上で各種サービスの有効化をしていきます。なお、本検証ではオーナーロールを使用して作業していきます。

まずは Gemini Enterprise と Agent Engine を利用する Google Cloud プロジェクトを用意します。

対象の Google Cloud プロジェクトにアクセスし、「Gemini Enterprise」と検索し、Gemini Enteprrise の管理画面に遷移します。2025/12/11時点では Gemini Enterprise Plus のサブスクリプションが 30 日間無料で利用できますので、今回の検証ではこちらを利用していきます。
中央の [30日間の無料トライアルを始める] をクリックし、API(Discovery Engine API) を有効化します。

最初に Gemini Enterprise のアプリを作成します。[アプリ] -> [新しいアプリを作成] をクリックします。

gemini-enterprise-1.png

Gemini Enterprise のアプリ名とリージョンを指定し、[作成] をクリックします。

gemini-enterprise-2.png

作成したアプリをクリックします。

gemini-enterprise-4.png

[インテグレーション] から [Google Identity を使用する] を選択し、[Workforce Identity を確認する] をクリックします。

gemini-enterprise-5.png

次に、Gemini Enterprise に紐づくサービスエージェント(サービスアカウント)に対して Vertex AI ユーザー (roles/aiplatform.user) と Vertex AI 閲覧者 (roles/aiplatform.viewer) の IAM ロールを付与します。Gemini Enterprise から Agent Engine を呼び出す際にこれらのロールに含まれる権限が必要となります。

対象となるサービスエージェントは [IAM と管理] -> [IAM] から、[Google 提供のロール付与を含める] にチェックをつけると表示されます。@gcp-sa-discoveryengine.iam.gserviceaccount.com ドメインのアカウントで ディスカバリ エンジン サービス エージェント ロールが付与されているアカウントです。

gemini-enterprise-3.png

次は開発環境のセットアップに移ります。

開発環境で gcloud CLI 実行のためのユーザー認証とプロジェクト設定、adk コマンド実行のための ADC(Application Default Credentials) 認証を行い、各種ツールから Google Cloud 環境にアクセスできるようにしておきます。

gcloud auth login
gcloud config set project <PROJECT_ID>
gcloud auth application-default login

Agent Engine を利用するために Vertex AI API と Resource Manager API を有効化します。

gcloud services enable aiplatform.googleapis.com
gcloud services enable cloudresourcemanager.googleapis.com

最後に、Agent Engine にエージェントをデプロイする際のステージングとして一時的に利用する Cloud Storage バケットを用意します。

gcloud storage buckets create gs://<BUCKET_NAME> \
--location=us-central1

OAuth 2.0 クライアントを構成する

Backlog 開発者向けページから新しいアプリケーションを登録し、AI エージェント用の OAuth 2.0 クライアントを構成していきます。

Backlogアプリケーション登録 のページにアクセスし、[アプリケーション管理ページ] をクリックします。

backlog-api-oauth-1.png

初めてアクセスした場合は以下が表示されるので [利用を開始] をクリックします。

backlog-api-oauth-2.png

[新規登録] をクリックします。

backlog-api-oauth-3.png

[Redirect URI] に https://vertexaisearch.cloud.google.com/oauth-redirect を入力します。
アプリケーション名やアプリケーションの説明、サイトURLには任意の情報を記入し、画面下部にある [作成] をクリックします。

backlog-api-oauth-4.png

表示された [Client Id] と [Client Secret] は後ほど Gemini Enterprise エージェントに設定します。

backlog-api-oauth-5.png

ADK で開発したソースコードを用意する

以下の構成でファイルを用意します。

agents
└── backlog_search_agent
    ├── __init__.py
    ├── .env
    └── agent.py

ソースコードは以下です。.envBACKLOG_AUTH_ID は現時点では空にしておきます。

__init__.py
__init__.py
from . import agent
.env
.env
# Backlog Space ID (e.g. your-space.backlog.jp -> your-space)
BACKLOG_SPACE_ID="your-space"

# Gemini Enterprise エージェントに設定する Authorization ID
# Gemini Enterprise エージェント作成後に設定する
BACKLOG_AUTH_ID=""
agent.py
agent.py
import os
import requests
from typing import Any, Optional

from google.adk.agents import Agent
from google.adk.tools import ToolContext

BACKLOG_SPACE_ID = os.getenv("BACKLOG_SPACE_ID")
AUTH_ID = os.getenv("BACKLOG_AUTH_ID")

# === Tool の定義 ===

def get_recently_viewed_issues(
    tool_context: ToolContext,
    count: Optional[int] = 20
) -> dict[str, Any]:
    """
    ユーザーが最近見た課題の一覧を取得します。

    Gemini Enterprise 経由で Backlog への OAuth による認可が完了している場合のみ実行可能です。
    認可が完了していない場合は、Gemini Enterprise が自動的に認可フローを開始します。

    Args:
        tool_context: ADK から渡されるコンテキストオブジェクト。
                      OAuth で取得したアクセストークンはここから取得します。
        count: 取得件数(1-100、デフォルト 20)

    Returns:
        成功時: {"status": "success", "count": 件数, "issues": [課題リスト]}
        失敗時: {"status": "error", "message": "エラーメッセージ"}
    """
    try:
        session_state = getattr(tool_context.session, "state", {})
        access_token = session_state.get(AUTH_ID)

        if not access_token:
            return {
                "status": "error",
                "message": "Backlog への OAuth による認可が完了していません。"
            }

        # Backlog API を呼び出し
        headers = {"Authorization": f"Bearer {access_token}"}
        params = {"count": min(max(count or 20, 1), 100)}
        api_url = f"https://{BACKLOG_SPACE_ID}.backlog.jp/api/v2/users/myself/recentlyViewedIssues"

        response = requests.get(api_url, headers=headers, params=params)
        response.raise_for_status()
        data = response.json()

        # レスポンスから必要な情報を抽出
        issues = []
        for item in data:
            if "issue" in item:
                issue = item["issue"]
                issues.append({
                    "issueKey": issue.get("issueKey"),
                    "summary": issue.get("summary"),
                    "status": issue.get("status", {}).get("name"),
                    "assignee": issue.get("assignee", {}).get("name") if issue.get("assignee") else None,
                    "updated": item.get("updated")
                })

        return {
            "status": "success",
            "count": len(issues),
            "issues": issues
        }

    except requests.exceptions.HTTPError as e:
        return {"status": "error", "message": f"API エラー: {e.response.status_code}"}
    except Exception as e:
        return {"status": "error", "message": str(e)}

def update_backlog_issue_status(
    issue_key: str,
    status_id: int,
    tool_context: ToolContext
) -> dict[str, Any]:
    """
    Backlog の課題ステータスを更新します。

    Gemini Enterprise 経由で Backlog への OAuth による認可が完了している場合のみ実行可能です。

    Args:
        issue_key: 課題キー(例: "PROJECT-123")
        status_id: 新しいステータス ID
                   - 1: 未対応
                   - 2: 処理中
                   - 3: 処理済み
                   - 4: 完了
        tool_context: ADK から渡されるコンテキストオブジェクト

    Returns:
        成功時: {"status": "success", "issue": 更新後の課題情報}
        失敗時: {"status": "error", "message": "エラーメッセージ"}
    """
    try:
        session_state = getattr(tool_context.session, "state", {})
        access_token = session_state.get(AUTH_ID)

        if not access_token:
            return {
                "status": "error",
                "message": "Backlog への OAuth による認可が完了していません。"
            }

        # Backlog API を呼び出し
        headers = {"Authorization": f"Bearer {access_token}"}
        api_url = f"https://{BACKLOG_SPACE_ID}.backlog.jp/api/v2/issues/{issue_key}"
        payload = {"statusId": status_id}

        response = requests.patch(api_url, headers=headers, json=payload)
        response.raise_for_status()
        issue = response.json()

        return {
            "status": "success",
            "issue": {
                "issueKey": issue.get("issueKey"),
                "summary": issue.get("summary"),
                "status": issue.get("status", {}).get("name")
            }
        }

    except requests.exceptions.HTTPError as e:
        return {"status": "error", "message": f"API エラー: {e.response.status_code}"}
    except Exception as e:
        return {"status": "error", "message": str(e)}

root_agent = Agent(
    name="backlog_search_agent",
    model="gemini-2.5-flash",
    description=(
        "Backlog の課題を検索・更新するエージェント。"
        "Gemini Enterprise の OAuth 認可を使用して Backlog API を実行する。"
    ),
    instruction=(
        "あなたは Backlog アシスタントです。\n"
        "ユーザーが最近見た課題を確認したい場合は get_recently_viewed_issues を使用してください。\n"
        "ユーザーが課題のステータスを更新したい場合は update_backlog_issue_status を使用してください。\n"
        "ツールの実行結果がエラーの場合は、エラー内容をユーザーに伝えてください。"
    ),
    tools=[get_recently_viewed_issues, update_backlog_issue_status],
)

設計のポイント

ルートエージェントに、LLM が呼び出し可能なツールをリストで登録します。
LLM はユーザーの質問に応じて、適切なツールを選択して呼び出します。

root_agent = Agent(
    name="backlog_search_agent",
    model="gemini-2.5-flash",
    ...
    tools=[get_recently_viewed_issues, update_backlog_issue_status],

関数には docstring を記述します。docstring の内容は LLM に渡され、ツール選択が正しいか、Args(引数)、Returns(戻り値)が何か、を LLM に判断させることができるようになります。

def get_recently_viewed_issues(
    tool_context: ToolContext,
    count: Optional[int] = 20
) -> dict[str, Any]:
    """
    ユーザーが最近見た課題の一覧を取得します。

    Gemini Enterprise 経由で Backlog への OAuth による認可が完了している場合のみ実行可能です。
    認可が完了していない場合は、Gemini Enterprise が自動的に認可フローを開始します。

    Args:
        tool_context: ADK から渡されるコンテキストオブジェクト。
                      OAuth で取得したアクセストークンはここから取得します。
        count: 取得件数(1-100、デフォルト 20)

    Returns:
        成功時: {"status": "success", "count": 件数, "issues": [課題リスト]}
        失敗時: {"status": "error", "message": "エラーメッセージ"}
    """

ツールには ToolContext が自動的に渡されます。このあとの手順で Gemini Enterprise で OAuth 認可のための情報を設定するのですが、Gemini Enterprise による OAuth 認可を完了した際にアクセストークンが tool_context.session.state に自動格納されます。

そのため、エージェント内では OAuth のリクエスト処理を実装する必要はなく、session.state.get(AUTH_ID) でトークンを取得するだけで外部 API を呼び出せます。

ざっくり言うと OAuth のアクセストークンは Gemini Enterprise が取得、ADK エージェントは Gemini Enterprise が保管したセッション情報からアクセストークンを取得して利用する ということですね。

(デバッグログからこのあたりの仕組みを紐解いていきましたが理解するのにすごく時間がかかりました。。)

def get_recently_viewed_issues(
    tool_context: ToolContext,
    count: Optional[int] = 20
) -> dict[str, Any]:

        session_state = getattr(tool_context.session, "state", {})
        access_token = session_state.get(AUTH_ID)

Agent Engine にデプロイする

ADK で開発したエージェントを Agent Engine にデプロイしていきます。デプロイは adk コマンドで実行します。
ただし、デプロイが完了してもこの時点では .envBACKLOG_AUTH_ID を設定していないため、このまま Gemini Enterprise エージェントに登録しても OAuth 認可の処理でエラーとなります。改めて Gemini Enterprise エージェントの設定が終わった後に再デプロイすることにします。

以下 adk deploy agent_engine コマンドを実行します。

adk deploy agent_engine \
--project=PROJECT_ID \
--region=us-central1 \
--staging_bucket=STAGING_BUCKET \
backlog_search_agent

--staging_bucket には先ほど作成した Agent Engine デプロイ時に利用するステージング用 Cloud Storage バケットを指定します。
最後の行はエージェントを配置したディレクトリのパスを指定します。

デプロイに4~5分程度かかります。完了すると以下のように出力されます。

Created agent engine: projects/<PROJECT_ID>/locations/us-central1/reasoningEngines/1790183050903027712

これが Agent Engine のリソースパスになります。この後、Gemini Enterprise で Agent Engine のエージェントを登録する作業がありますが、その際にここで生成されたリソースパスを参照させます。

Gemini Enterprise に Agent Engine エージェントを登録する

ここからは Cloud Console で設定をしていこうと思います。検索窓から「gemini enterprise」を検索し、[Gemini Enterprise]の画面に移ります。先ほど作成したアプリを選択します。

gemini-enterprise-agent-1.png

[エージェント] -> [エージェントを追加] をクリックします。

gemini-enterprise-agent-2.png

[Agent Engine によるカスタム エージェント] の [追加] をクリックします。

gemini-enterprise-agent-3.png

[承認を追加] をクリックし、前述した「OAuth 2.0 クライアントを構成する」の手順で Backlog に設定した OAuth 2.0 クライアントの情報を利用し、OAuth 2.0 による認可のための設定をしていきます。

Backlog アプリケーションの Client Id, Client Secret をコピーし設定します。

backlog-api-oauth-5.png

トークン URI と承認 URI は 認証と認可 | Backlog Developer API | Nulab を参照し、それぞれ以下の値にします。

トークン URI: https://<BACKLOG_SPACE_ID>.backlog.jp/api/v2/oauth2/token
承認 URI: https://<BACKLOG_SPACE_ID>.backlog.jp/OAuth2AccessRequest.action?response_type=code

認証名には任意の名前を設定しますが、設定した名前に応じた Authorization ID が自動生成されます。この Authorization ID を後ほどエージェントの環境変数 BACKLOG_AUTH_ID に設定して再デプロイを行います。Authorization ID をコピーしておきます。

gemini-enterprise-agent-4.png

[完了] -> [次へ] をクリックします。

本設定により、Gemini Enterprise のエージェントは Backlog から OAuth のアクセストークンを取得し、Gemini Enterprise 上でセッションとして保持する動作となります。このセッション上のアクセストークンを ADK エージェントから参照し、Backlog へのアクセスを実現します。

[エージェント名]、[エージェントの説明] を入力します。
[Agent Engine 推論エンジン] には前述した「Agent Engine にデプロイする」の手順で adk deploy agent_engine コマンドによるデプロイが完了したあとに出力されたリソースパスを入力します。リソースパスは以下の形式です。

projects/{project}/locations/{location}/reasoningEngines/{reasoningEngine}

gemini-enterprise-agent-5.png

なお、Authorization ID については一度 エージェントを作成してしまうと Cloud Console から参照できません。 以下のように curl で REST API を直接コールすることでエージェントの情報を確認できます。

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "X-Goog-User-Project: <PROJECT_ID>" \
"https://discoveryengine.googleapis.com/v1alpha/projects/<PROJECT_ID>/locations/global/collections/default_collection/engines/<GEMINI_ENTERPRISE_APP_ID>/assistants/default_assistant/agents"

<GEMINI_ENTERPRISE_APP_ID> には Gemini Enterprise アプリの ID を入力します。私の環境では classmethod-internal_1765429312666 という ID でした。

出力結果は以下です。

{
  "agents": [
    {
      "name": "projects/<PROJECT_NO>/locations/global/collections/default_collection/engines/classmethod-internal_1765429312666/assistants/default_assistant/agents/5507736702261310837",
      "displayName": "Backlog Search Agent",
      "description": "Backlog の課題検索とステータス変更をします",
      "createTime": "2025-12-11T14:57:27.971513Z",
      "updateTime": "2025-12-11T14:57:27.971513Z",
      "adkAgentDefinition": {
        "provisionedReasoningEngine": {
          "reasoningEngine": "projects/<PROJECT_NO>/locations/us-central1/reasoningEngines/1790183050903027712"
        }
      },
      "state": "ENABLED",
      "authorizationConfig": {
        "toolAuthorizations": [
          "projects/<PROJECT_NO>/locations/global/authorizations/backlog-oauth_1765464402744"    # Authorization ID はここ
        ]
      }
    },
...

出力結果の authorizationConfig.toolAuthorizations に表示されるパスの最後の ID が Authorization ID となります。

エージェントの Authorization ID を更新して Agent Engine に再デプロイ

エージェントの .envBACKLOG_AUTH_ID に前述の手順で取得した Authorization ID を設定します。

.env
# Gemini Enterprise エージェントに設定する Authorization ID
# Gemini Enterprise エージェント作成後に設定する
BACKLOG_AUTH_ID="backlog-oauth_1765464402744"

Gemini Enterprise エージェントが取得した OAuth のアクセストークンは、backlog-oauth_1765464402744 をキーとして tool_context.session.state に格納されます。

agent.py
def get_recently_viewed_issues(
    tool_context: ToolContext,
    count: Optional[int] = 20
) -> Dict[str, Any]:

# Gemini Enterprise エージェントで作成した Authorization ID
AUTH_ID = os.getenv("BACKLOG_AUTH_ID")

    try:
        # Gemini Enterprise で取得した OAuth アクセストークンは session.state に格納される
        # キーは Gemini Enterprise に設定する Authorization ID
        session_state = getattr(tool_context.session, "state", {})
        access_token = session_state.get(AUTH_ID)

では、Agent Engine への再デプロイを行います。再デプロイは以下のように Agent Engine のリソース ID を指定して実行します。

adk deploy agent_engine \
--project=PROJECT_ID \
--region=us-central1 \
--staging_bucket=STAGING_BUCKET \
--agent_engine_id=AGENT_ENGINE_ID \
backlog_search_agent

<AGENT_ENGINE_ID> は projects/<PROJECT_ID>/locations/us-central1/reasoningEngines/1790183050903027712 の最後の数字の部分です。

試してみる

Gemini Enterprise の対象アプリを選択し、Gemini Enterprise の Web ページの URL をクリックします。

gemini-enterprise-6.png

[エージェント] -> [すべてのエージェントを表示] から、今回作成した Backlog Search Agent を選択します。

gemini-enterprise-7.png

以下画像のように質問すると、認可を促すメッセージが出てくるので [承認] をクリックします。

gemini-enterprise-8.png

Backlog のユーザー認証と OAuth による認可のポップアップが表示されるので許可します。

gemini-enterprise-10.png

以下のように Backlog から課題の一覧を取得して出力してくれました。

gemini-enterprise-11.png

では、次にステータス更新をしてみます。

gemini-enterprise-12.png

実際に対象の Backlog 課題が更新されたことが確認できました。

gemini-enterprise-13.png

おわりに

Gemini Enteprise も Agent Engine も機能や使い勝手の面で発展途上な点もあり、仕組みや手順を理解するのに非常に苦労しました。しかしながら、Agent Engine にはデフォルトで標準出力を Cloud Logging に転送する仕様であることから、ログの追跡が非常に容易にでき、トラブルシュートしながら何とか実装にこぎつけられました。

Gemini Enteprise にカスタムエージェントが登録できることで、さまざまなユースケースやアイデアを形にできるようになりました。今回シンプルに用意した Backlog API を利用したエージェントですが、社内で活用できるよう勉強しながら機能拡充していこうと思います。

ADK による開発や Agent Engine の利用に関して新たな発見などありましたらまた情報共有してまいります。

この記事をシェアする

FacebookHatena blogX