KINTO Tech Blog
生成AI

GitHub Copilot を使った AI Agent の構築

Cover Image for GitHub Copilot を使った AI Agent の構築

はじめに

こんにちは。KINTOテクノロジーズ プラットフォームグループ Platform Engineeringチームで内製ツールの開発・運用をおこなっている山田です。

過去に書いたSpring AIとText-to-SQLの記事もぜひご覧ください!

https://blog.kinto-technologies.com/posts/2025-06-11-springAI/

https://blog.kinto-technologies.com/posts/2025-01-16-generativeAI_and_Text-to-SQL/

今回はGitHub Copilotを活用して、AWS上で構築しているプロダクトの、AWSリソースの依存関係を自動で分析・収集するAI Agentを構築したお話をしたいと思います。

背景と課題

Platform Engineeringチームでは、CMDB (Configuration Management Database) とIncident Manager (インシデント管理ツール) という2つの内製ツールを開発・運用しています。

CMDBは構成管理データベースというシステムで、社内プロダクトの構成情報を一元管理しています。CMDBにはプロダクトの担当者や担当チーム、脆弱性情報の管理などさまざまな機能があり、その一つにプロダクトに関連するAWSリソース (ECS、RDS、ALB、CloudFrontなど) のARN情報を管理する機能があります。

Incident Managerでは、インシデント発生時に迅速な原因特定と復旧をサポートするため、インシデントが発生したプロダクトのトポロジー情報 (システム構成図) と原因箇所を可視化する機能が求められていました。

しかし、トポロジー情報を可視化するためには、単にAWSリソースのARN情報を持っているだけでは不十分で、リソース間の依存関係 (例: CloudFront → ALB → ECS → RDS) を把握する必要がありました。

従来、この依存関係情報は手動で設定する必要があり、以下のような課題がありました。

  • 新しいリソースが追加されるたびに手動で依存関係を更新する必要がある
  • 複雑なシステム構成では依存関係の把握が困難
  • 人的ミスによる依存関係の設定漏れや誤り

解決アプローチ

これらの課題を解決するために、CMDBが管理しているARN情報を活用して、AWSリソースの依存関係を自動で分析・収集するAI Agentを構築することにしました。

GitHub Copilotと対話しながら実装を進めた結果、以下のような機能を持つAI Agentが完成しました。

  • CMDBから取得したARN情報を起点に、AWSリソース間の依存関係を自動で分析
  • 複数のAWS APIを呼び出して、セキュリティグループやネットワーク設定から接続関係を推論
  • 収集したノード (AWSリソース) とエッジ (依存関係) の情報をデータベースに保存
  • Incident Managerでインシデント発生時のトポロジー図表示に活用

技術スタック

  • 開発支援ツール: GitHub Copilot (Agentモード - Claude Sonnet 4.5)
  • AI Agent Framework: LangGraph
  • LLM: Amazon Bedrock (Claude Sonnet 4.5)
  • 言語: Python 3.12
  • 主要ライブラリ:
    • LangChain, LangChain-AWS
    • boto3 (AWS SDK for Python)

AI Agent構築の流れ

1. 最初のプロンプト

まず初めに、以下のプロンプトでGitHub CopilotにAI Agentを構築するための設計をお願いしました。(一部、省略・編集しています)

CMDBのARN管理テーブルから取得したAWSリソースのARN情報を使って、
リソース間の依存関係を自動で分析・収集するAI Agentを実装してください。

技術スタック:
- LangGraph、LangChain
- Amazon Bedrock (Claude Sonnet 4.5)
- AWS SDK (boto3)
- Python 3.12

機能要件:
- API Endpoint: POST /service_configurations
  - パラメータ: sid, environment (どちらも必須)
- ARN管理テーブルからsid、environmentを条件にARNを検索
- 取得したCloudFront、S3、WAF、ALB、TargetGroup、ECS、RDS、ElastiCacheのARN情報を使って、Nodes、Edges情報をLLMとAgent (LangGraph) で成形
- 収集した情報をDBに保存

依存関係の取得方法 (Few-shot Examples):

### CloudFront → S3/ALB のEdge判定方法
1. CloudFront APIでドメイン名を取得
   aws cloudfront get-distribution --id {distribution-id}
2. ビヘイビア情報からOriginを取得してEdge(紐づき)を判定
   - DomainNameにs3がある場合: CloudFront → S3
   - DomainNameにALBがある場合: CloudFront → ALB

### TargetGroup → ECS のEdge判定方法
1. elbv2のAPIでターゲットのIPアドレスを取得
   aws elbv2 describe-target-health --target-group-arn {arn}
2. ECS APIでタスクのIPアドレスと照合してEdgeを作成

### ECS → RDS のEdge判定方法 (実際のアクセスではなく、セキュリティの許可で判定)
1. ECSタスクからENI (Elastic Network Interface) のIDを取得
   aws ecs describe-tasks --cluster {cluster} --tasks {task-arn}
2. ENIからECSタスクのセキュリティグループIDを取得
   aws ec2 describe-network-interfaces --network-interface-ids {eni-id}
3. RDSのセキュリティグループを取得
   aws rds describe-db-instances --db-instance-identifier {instance-id}
4. RDSセキュリティグループのインバウンドルールを取得
   aws ec2 describe-security-groups --group-ids {sg-id}
5. ECSのセキュリティグループがRDSのインバウンドルールに含まれていればEdge作成

使用予定のAWS API:
- cloudfront:get_distribution
- cloudfront:list_distributions
- wafv2:list_web_acls
- wafv2:get_web_acl
- wafv2:get_web_acl_for_resource
- elbv2:describe_target_groups
- elbv2:describe_target_health
- elbv2:describe_load_balancers
- elbv2:describe_load_balancer_attributes
- ecs:list_clusters
- ecs:describe_tasks
- ecs:describe_task_definition
- ecs:list_tasks
- ecs:describe_services
- ec2:describe_network_interfaces
- ec2:describe_security_groups
- rds:describe_db_clusters
- rds:describe_db_instances
- elasticache:describe_replication_groups
- elasticache:describe_cache_clusters

関連するDDL:
{ARN管理テーブルのDDL}
{Node情報テーブルのDDL}
{Edge情報テーブルのDDL}

重要:
- まずは現状のリポジトリ構成を理解する
- 実装を始める前に整理して、実装方針を確認してから実装を行う
- 呼び出すAWS APIや取得するNodeは増える可能性があるため、拡張性を考慮する
- コードは簡単でシンプルな内容にする

このプロンプトに対して、GitHub Copilotはまずリポジトリ全体を分析し、既存のコードを理解した上で以下の実装方針を提案してくれました。(一部、省略・編集しています)

(Read files...)

リポジトリの理解ができましたので、設計方針をまとめます。

リポジトリ構造の理解
既存のプロジェクトは以下の構成になっています:

・FastAPIベースのAPIサーバー
・**Bedrock (Claude)**を使用したLLM機能
・MySQLデータベース接続機能(text_to_sql.pyで実装例あり)
・boto3でAWS APIを呼び出す機能(aws_diagram.pyで実装例あり)
・LangChainの依存関係あり(pyproject.tomlに記載)

実装方針
1. アーキテクチャ設計
ARN管理テーブルからARN情報取得 (DB)
    ↓
LangGraph Agent でAWS APIを呼び出し、Edges情報を収集
    ↓
Nodes/Edges情報をテーブルに保存 (DB)
    ↓
レスポンス返却

2. モジュール構成 (新規作成ファイル)
・`service_config_router.py`: FastAPIエンドポイント
・`service_config_agent.py`: LangGraphによるAgent実装
・`aws_tools.py`: AWS API呼び出しツール群
・`service_config_db.py`: DB操作 (ARN取得、Node/Edge保存)

3. 実装フェーズ
・Phase 1 (DB層): ARN取得とNode/Edge保存関数の実装
・Phase 2 (AWSツール層): CloudFront、ELB、ECS、RDS等のAPI呼び出し関数をLangChain Tool形式で実装
・Phase 3 (Agent層): LangGraphのStateGraphを構築し、LLMにツールを渡してEdge情報を推論
・Phase 4 (Router層): FastAPIエンドポイントとリクエスト検証・レスポンス整形

4. 設計の重要ポイント
・拡張性: ホワイトリスト方式で新しいAWS APIを追加しやすく
・シンプルさ: StateGraphで状態管理を明確に、各ツールは1関数=1API呼び出し
・データフロー: API → Agent → AWS APIs → DB保存という明確な流れ

5. 実装コード構造(擬似コード)
(コードのサンプル)

この設計方針で実装を進めてよろしいでしょうか?
確認いただけましたら、順次実装を開始します。

この設計時点で大きく方針がずれていなかったため、このままGitHub Copilotに実装してもらいました。

2. 繰り返し修正依頼

プロンプトに書き忘れた基本設計、すべて伝えきれていない詳細設計、プロンプトの問題など様々な要因はありますが、生成されたコードは一発で完璧に動くことはなく、GitHub Copilotと何度もやりとりをしてコードの修正を繰り返しました。

アーキテクチャのレイヤー分けをプロンプトに書き忘れたため、Controller層に大量にビジネスロジックを実装されてしまったり、

Bedrockのモデル呼び出し処理で存在しない関数呼び出しをしていたり (別バージョンの関数を利用していた)、

エラーハンドリングが足りなかったり、

動いたと思ったら取得できていないNodeとEdgeの情報があったり、

Agentのプロンプトの改善をしたり...

たくさんの問題がありましたが、数時間で想定通りの動作をするようになりました。

3. 最終的に完成したコード

9割以上をGitHub Copilotにコーディングをしてもらって最終的にどんなコードになったのか、一部重要な部分を抜粋してご紹介しようと思います。

LangGraphによるAgent実装

AI Agentの核心部分です。LangGraphを使って処理フローを定義しています。(一部、省略・編集しています)

def create_service_config_agent() -> StateGraph:
    """システム構成収集Agentを作成"""
    workflow = StateGraph(AgentState)
    
    # ノードを追加
    workflow.add_node("initialize_nodes", initialize_nodes)
    workflow.add_node("collect_edges", collect_edges_with_llm)
    
    # エントリーポイントを設定
    workflow.set_entry_point("initialize_nodes")
    
    # 条件分岐: ノードが存在すればEdge収集、なければ終了
    workflow.add_conditional_edges(
        "initialize_nodes",
        should_collect_edges,
        {
            "collect_edges": "collect_edges",
            "end": END
        }
    )
    
    workflow.add_edge("collect_edges", END)
    
    return workflow.compile()

def collect_edges_with_llm(state: AgentState) -> AgentState:
    """LLMとツールを使用してエッジ情報を収集"""
    llm = get_llm_for_agent()
    llm_with_tools = llm.bind_tools(AWS_TOOLS)
    
    prompt = f"""
    あなたはAWSリソースの依存関係を分析するエキスパートです。

    # タスク
    以下のAWSリソース (Nodes) のARNから、リソース間の接続関係 (Edges) を推測・特定してください。
    各AWSサービスの特性と一般的なアーキテクチャパターンを理解し、適切なAWS APIを呼び出して接続を確認してください。
    
    # 利用可能なNodes
    {nodes_summary}

    # 利用可能なツール
    1. **call_aws_api**: 許可されたAWS APIを呼び出せるツール
      - リソースの詳細情報、設定、関連リソースを取得できます
    
    2. **extract_resource_id_from_arn**: ARNからリソースIDやその他の情報を抽出
      - API呼び出しに必要なパラメータ (ID、名前など) を取得できます
    
    # 使用可能なAWS API (これ以外は使用できません)
    - cloudfront:get_distribution
    - wafv2:get_web_acl_for_resource
    - elbv2:describe_target_groups
    ...

    # Edge検出の方法
    以下の観点から、リソース間の接続を推測・調査してください:
    
    ## 一般的な接続パターン
    1. **フロントエンド層**: CloudFront → S3/ALB、WAF → CloudFront/ALB、Route53ドメイン → CloudFront
    2. **ロードバランサー層**: ALB → ターゲットグループ → ECS/EC2
    3. **API層**: API Gateway → Lambda
    4. **アプリケーション層**: ECS → RDS/ElastiCache (セキュリティグループ経由) 、Lambda → RDS (セキュリティグループ経由) 
    5. **データ層**: RDS、ElastiCache

    ## 接続検出の考え方
    - **設定ベース**: リソースの設定に他のリソースのARNやIDが含まれている場合 (例: CloudFrontのOrigins設定) 
    - **ネットワークベース**: セキュリティグループのインバウンドルールで許可されている場合 (例: ECS → RDS) 
    - **サービス特性**: 各AWSサービスの役割から論理的に推測できる接続 (例: TargetGroup → ECS) 

    ## 重要な調査ポイント
    - **ARNの分析**: まずextract_resource_id_from_arnでARNを解析し、リソースタイプと必要なパラメータを特定
    - **段階的調査**: 一度に全APIを呼ばず、結果を見ながら次に必要なAPIを判断
    - **セキュリティグループ**: ECS/RDS/ElastiCacheの接続はセキュリティグループのインバウンドルールで確認
    - ECSのENI → セキュリティグループID → RDS/ElastiCacheのSGインバウンドルールに含まれるかチェック
    - **エラー対応**: 許可されていないAPIやエラーが返っても、次の調査を継続
    
    # Few-shot Examples (必ず参考にすること)
    ## Example 1: ECS → RDS の調査
    1. ECSタスクのENIを取得: call_aws_api("ecs", "describe_tasks", ...)
    2. ENIからSGを取得: call_aws_api("ec2", "describe_network_interfaces", ...)
    3. RDSのSGを取得: call_aws_api("rds", "describe_db_instances", ...)
    4. SG一致確認 → Edge作成
    ...
    
    # 重要な注意事項
    - すべてのリソースの組み合わせをチェック
    - APIエラーが出ても次の調査を継続
    - RDSの snapshot, parameter group, subnet group 等は無視
    - セキュリティグループで接続可能性を判断

    # 出力形式
    調査が完了したら、以下のJSON形式で結果を返してください:
    ```json
    {{
    "nodes": [
        {{
        "service_name": "cloudfront",
        "arn": "xxx",
        "resource": ""
        }}
    ],
    "edges": [
        {{
        "from_arn": "xxx",
        "to_arn": "xxx",
        "details": "xxx"
        }}
    ]
    }}
    ```
    """
    
    messages = [HumanMessage(content=prompt)]
    
    try:
        max_iterations = 30  # 最大反復回数
        edges = []
        
        for iteration in range(max_iterations):
            response = llm_with_tools.invoke(messages)
            messages.append(response)
            
            # ツール呼び出しがあるか確認
            if hasattr(response, 'tool_calls') and response.tool_calls:
                # ツールを実行
                tool_node = ToolNode(AWS_TOOLS)
                tool_results = tool_node.invoke({"messages": messages})
                
                # ツール結果をメッセージに追加
                messages.extend(tool_results["messages"])
                
            else:
                # ツール呼び出しがない場合、最終レスポンスとして処理
                try:
                    # 構造化レスポンス用のLLMを作成
                    structured_llm = llm.with_structured_output(GraphResult)
                    
                    # 最終結果の要約プロンプトを追加
                    final_prompt = """
                        調査が完了しました。発見したすべてのエッジと新しいノード(ドメイン名など)を
                        JSON形式で返してください。
                    """
                    messages.append(HumanMessage(content=final_prompt))
                    
                    # 構造化レスポンスを取得
                    result = structured_llm.invoke(messages)
                    
                    edges = [edge.model_dump() for edge in result.edges]
                    new_nodes = [node.model_dump() for node in result.nodes]
                    
                    # LLMが返した新しいノード(ドメイン名など)を既存のノードリストに追加
                    if new_nodes:
                        state["nodes"].extend(new_nodes)
                    
                except Exception as e:
                    # エラー時のフォールバック処理
                    ...

                break
        
        state["edges"] = edges
        state["current_step"] = "edges_collected"
        state["messages"] = messages
        
    except Exception as e:
        ...
        state["edges"] = []
    
    return state

AWS API呼び出しツール

Agentが使用するツール群です。ホワイトリスト方式で実行可能なAWS APIの安全性を確保しています。(一部、省略・編集しています)

# 許可するAWS APIのホワイトリスト
ALLOWED_AWS_APIS: Set[str] = {
    # CloudFront
    "cloudfront:get_distribution",
    "cloudfront:list_distributions",
    
    # WAF
    "wafv2:list_web_acls",
    "wafv2:get_web_acl",
    "wafv2:get_web_acl_for_resource",
    ...
}


@tool
def call_aws_api(
    service_name: str,
    method_name: str,
    parameters: Dict[str, Any],
    region: str = "ap-northeast-1"
) -> Dict[str, Any]:
    """汎用的なAWS API呼び出しツール
    
    このツールは許可されたAWS APIのみを呼び出すことができます。
    Edge情報を取得するために必要なAWS APIを呼び出してください。
    
    Args:
        service_name: AWSサービス名 (小文字) 
            許可: 'cloudfront', 'wafv2', 'elbv2', 'ecs', 'ec2', 'rds', 'elasticache', 'apigateway', 'lambda'
        method_name: 呼び出すメソッド名 (boto3のメソッド名、snake_case)
            例: 'get_distribution', 'describe_target_health', 'describe_security_groups'
        parameters: メソッドに渡すパラメータの辞書
            例: {"Id": "ABC123"} や {"GroupIds": ["sg-12345"]}
        region: AWSリージョン (デフォルト: ap-northeast-1)
    
    Returns:
        API呼び出し結果の辞書
        エラーの場合は {"error": "エラーメッセージ"} を返す
    
    許可されているAPI一覧:
        - cloudfront:get_distribution
        - cloudfront:list_distributions
        - wafv2:list_web_acls
        - wafv2:get_web_acl
        - wafv2:get_web_acl_for_resource
        - ...
    
    Examples:
        # CloudFront Distribution情報を取得
        call_aws_api(
            service_name="cloudfront",
            method_name="get_distribution",
            parameters={"Id": "ABC123"}
        )
        
        # ターゲットグループのヘルス情報を取得
        call_aws_api(
            service_name="elbv2",
            method_name="describe_target_health",
            parameters={"TargetGroupArn": "arn:aws:elasticloadbalancing:..."}
        )
        
        # セキュリティグループ情報を取得
        call_aws_api(
            service_name="ec2",
            method_name="describe_security_groups",
            parameters={"GroupIds": ["sg-12345"]}
        )
        
        # ECSタスク情報を取得
        call_aws_api(
            service_name="ecs",
            method_name="describe_tasks",
            parameters={"cluster": "my-cluster", "tasks": ["arn:aws:ecs:..."]}
        )
    """
    try:
        # ステップ1: APIホワイトリストに含まれているか検証
        is_valid, error_message = validate_aws_api(service_name, method_name)
        
        if not is_valid:
            return {
                "error": error_message,
                "error_type": "unauthorized_api",
                "allowed_apis": get_allowed_apis_list()
            }
        
        # ステップ2: クライアントを取得
        client = get_aws_client(service_name, region)
        
        # ステップ3: メソッドが存在するか確認
        if not hasattr(client, method_name):
            return {"error": error_msg, "available_methods": dir(client)}
        
        # ステップ4: メソッドを取得して実行
        method = getattr(client, method_name)
        response = method(**parameters)
        
        return response
        
    except Exception as e:
        ...

以下が今回の実装で意識したポイントです。

  • ホワイトリスト方式: LLMが任意のAWS APIを呼ぶことを防ぎ、安全性を確保
  • 動的API呼び出し: Pythonのgetattr()でboto3のメソッドを動的に実行
  • 詳細なdocstring: LLMがツールの使い方を理解するため、引数の説明、使用例、許可API一覧を記載
  • 拡張性: 新しいAPIを追加する場合は、ALLOWED_AWS_APISに追加するだけ

Incident Managerでのトポロジー描画

最終的にAI Agentを使って収集したNodeとEdgeの情報で、IncidentManager上でのシステムトポロジー表示はこのようになりました。
topology

障害発生時は原因箇所が赤くなるため、ぱっと見で直感的にシステム構成と障害箇所が理解しやすいような図になったかと思います!

GitHub Copilotを使って実装してみた感想

良かった点

  • 開発時間の大幅な短縮

    • 今回の実装は約1日で完成しました。もしGitHub Copilotなしで実装していたら、LangGraphの学習から始める必要があり、少なくとも数週間はかかっていたと思います。
  • 高品質な実装計画の提案

    • まだ改善の余地はありますが最初のプロンプトで実現したいことと設計を詳細に伝えたことで、以下のような質の高い実装計画が生成されました。
      • 既存リポジトリの構造を理解した上で、一貫性のある設計を提案
      • 拡張性とシンプルさを両立した設計の提案
  • 対話的な品質改善

    • 実装途中で気になった点を指摘すると、すぐに修正してくれました。
      • アーキテクチャの改善
      • ライブラリバージョンの問題
      • エラーハンドリングの追加
      • など

大変だった点

生成されたコードの検証が必須

GitHub Copilotに限らず生成AIが生成したコードは、指示をした人が責任を持ってレビューをする必要があります。生成AIを活用したコーディングでは、レビューに一番時間がかかります。

以下のような観点でコードレビューをおこないました。

  • 既存コードの規約準拠: リポジトリの命名規則やコーディングスタイルに従っているか
  • 要件の網羅性: 指定した機能要件がすべて実装されているか、漏れがないか
  • アーキテクチャパターン: 適切なレイヤー分けがされているか、責務が明確か
  • 実装の適切性: より一般的な方法や簡単な実装方法がないか、無駄に複雑になっていないか
  • エラーハンドリング: 例外処理が適切に実装されているか、エラーメッセージは適切か
  • 動作検証: 実際にアプリを起動させて、意図通りの動作をするか

今後やりたいこと

収集するNodeとEdgeの追加

現状は最初のお試しということで、一部のAWSサービスに絞ってNodeとEdgeを取得するようにしました。

プロンプトで取得するAWSリソースを追加して、AWS API呼び出しツールの利用するホワイトリストに、Edge情報を取得するために許可するAPIを追加すれば簡単に拡張できる実装になっているため、今後少しずつ収集リソースを増やしていきたいと思います。

プロンプトテンプレートの作成

今回は最初の指示で考慮不足があったため、今後使いまわせるような新機能実装時のプロンプトテンプレートを作成して、以下の内容を含めることで最初の指示からより高品質なコード生成ができるようにしたいと思います。

  • 機能の概要
  • 技術スタック(フレームワーク、ライブラリ、言語バージョン)
  • 機能要件
  • アーキテクチャ要件(レイヤー構成、エラーハンドリングなど)
  • 非機能要件(拡張性、パフォーマンス、セキュリティ)
  • 実装前の確認事項(リポジトリ構造の理解、既存コードとの整合性確認など)
  • 重要な注意事項(プロンプトの最後に配置、絶対に守るべきルールを明記)

まとめ

今回はGitHub Copilotを活用して、システムトポロジー情報を収集するAI Agentを約1日で実装しました。

実装したAI Agentにより、従来は手動で設定していたAWSリソースの依存関係が自動収集されるようになり、Incident Manager上でインシデント発生時のトポロジー可視化が実現できました。

今後もGitHub Copilotをはじめとした生成AIを積極的に活用して、開発生産性の向上を目指していきたいと思います。

Facebook

関連記事 | Related Posts

We are hiring!

【クラウドエンジニア】Cloud Infrastructure G/東京・大阪・福岡

KINTO Tech BlogWantedlyストーリーCloud InfrastructureグループについてAWSを主としたクラウドインフラの設計、構築、運用を主に担当しています。

【クラウドプラットフォームエンジニア】プラットフォームG/東京・大阪・福岡

プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。

イベント情報