0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

tree-sitterとContext Engineeringで構築するAIコーディングエージェントのコード検索最適化実践ガイド

0
Last updated at Posted at 2026-04-04

tree-sitterとContext Engineeringで構築するAIコーディングエージェントのコード検索最適化実践ガイド

この記事でわかること

  • Context Engineeringの定義とプロンプトエンジニアリングとの違い、コーディングエージェントへの適用方法
  • tree-sitterによるAST解析を活用したセマンティックコードチャンキングの実装手法
  • ベクトル埋め込みとAST構造を組み合わせたコード検索パイプラインの構築方法
  • grepベースの検索から50倍のコンテキスト削減を実現するインデックス設計
  • CocoIndex・AiDexなど実際のツールを用いた本番環境での最適化戦略

対象読者

  • 想定読者: AIコーディングエージェントを構築・運用しているMLエンジニア、コーディングエージェントのコンテキスト最適化に関心のあるソフトウェアエンジニア
  • 必要な前提知識:
    • Python 3.11+ または TypeScript の基礎文法
    • LLMのコンテキストウィンドウに関する基本理解(トークン、プロンプト構成)
    • RAG(Retrieval-Augmented Generation)の基礎概念(MLエンジニアなら前提知識として十分)

結論・成果

tree-sitterによるAST解析とベクトル埋め込みを組み合わせたコード検索パイプラインにより、以下の改善が報告されています。

指標 改善前(grep/固定サイズ) 改善後(AST+ベクトル検索) 出典
コンテキスト使用量 2,000+ トークン/クエリ 約50トークン/クエリ(50倍削減 AiDex DEV記事
コード検索Recall@5 42.4%(固定サイズ) 70.1%(AST-aware) code-chunk ベンチマーク
SWE-bench処理時間 ベースライン 40%削減 code-chunk ベンチマーク
SWE-benchトークン消費 ベースライン 44%削減 code-chunk ベンチマーク
エージェントトークン予算 ベースライン 70%削減 CocoIndex Code

コーディングエージェントの応答品質とコスト効率を同時に改善する手法として、Context Engineeringの考え方とtree-sitterベースのコード検索は2026年のエージェント開発において必須の技術要素になりつつあります。

Context Engineeringを理解する:プロンプトの先にある設計思想

Context Engineeringとは何か

Context Engineering(コンテキストエンジニアリング)は、LLMが参照する情報環境全体を設計・最適化する技術体系です。Martin FowlerはContext Engineering for Coding Agentsの中で、次のように定義しています。

Context engineering is curating what the model sees so that you get a better result.
(コンテキストエンジニアリングとは、モデルが参照する情報をキュレーションし、より良い結果を得ること)

プロンプトエンジニアリングが「個々の質問や指示をどう書くか」に焦点を当てるのに対し、Context Engineeringはプロンプト・メモリ・ツール・データの全体を対象にします。つまり、Pythonで例えると、プロンプトエンジニアリングが「関数のdocstring(= 一つの指示文)をどう書くか」なら、Context Engineeringは「モジュール全体の設計(= LLMに渡す情報環境全体)をどう構成するか」に相当します。

なぜコーディングエージェントにContext Engineeringが必要か

コーディングエージェントは、単発のプロンプト応答とは異なる課題を抱えています。

  1. コードベースの規模: 実務のリポジトリは数百〜数千ファイル。全てをコンテキストに載せることはできない
  2. 構造的な意味: コードは自然言語と異なり、関数・クラス・モジュールの階層構造を持つ
  3. セッション間の一貫性: エージェントは毎回コードベースを「初見」で理解し直す必要がある

Zylos Researchの調査によると、2025年のエンタープライズAI障害の**65%**がコンテキストドリフト(コンテキストの劣化)やメモリ損失に起因しており、コンテキストウィンドウの単純な拡大では解決できない問題です。

Context Engineering Layerの4つのサブシステム

arXivの研究によると、Claude Codeのようなターミナルベースのコーディングエージェントは、Context Engineering Layerとして以下の4つのサブシステムを持ちます。

サブシステム 役割 コード検索との関係
System Reminders 文脈に応じた行動指針の注入 検索結果のフィルタリングルール
Prompt Composer モジュラーなシステムプロンプト組立 コード検索結果のフォーマット
Memory セッション間の継続性 過去の検索パターンの記憶
Compaction トークン予算の回収 不要なコード情報の圧縮

本記事では、このうち特にPrompt ComposerCompactionに関わるコード検索の最適化に焦点を当てます。コード検索の精度が上がれば、LLMに渡す情報の質が向上し、エージェント全体の性能が改善するためです。

注意: Context Engineeringは「LLMの出力を確実に制御できる」という意味ではありません。Martin Fowlerも指摘するように、LLMは確率的なモデルであり、「幻覚を防ぐ」「必ず正しいコードを生成する」といった保証はContext Engineeringでも実現できません。あくまで「より良い結果を得る確率を高める」技術です。

tree-sitterでコード構造を解析する:セマンティックチャンキングの実装

tree-sitterの基礎:なぜAIエージェントに使われるのか

tree-sitter は、ソースコードを構文木(AST/CST)に変換するパーサジェネレータ兼インクリメンタルパーシングライブラリです。元々はテキストエディタ(Neovim、Helix、Zed)のシンタックスハイライト用に開発されましたが、2025-2026年にかけてAIコーディングエージェントの中核技術として急速に採用が進みました。

Generative Agentsの調査によると、以下の主要コーディングエージェントがtree-sitterを採用しています。

エージェント tree-sitterの用途
Aider リポジトリマップ生成(関数・クラス定義の抽出、PageRankによる重要度ランキング)
Codex シェルコマンドの構文検証、apply_patch仮想コマンドの検出
Cline コード構造の理解、セマンティックチャンキング
Claude Code コードベースの構造把握、ファイル検索の最適化

tree-sitterが選ばれる理由は3つあります。

  1. インクリメンタル解析: ファイル変更時に全体を再解析せず、変更部分のみ更新できる(大規模コードベースで高速)
  2. 多言語対応: C#、TypeScript、Python、Rust、Go、Java等28以上の言語に対応
  3. 軽量: Rustで実装されており、Python/Node.jsバインディングも高速

Pythonでtree-sitterを使ったAST解析の実装

tree-sitterのPythonバインディングを使って、コードからセマンティックな単位を抽出する実装例を見ていきましょう。

# requirements: pip install tree-sitter tree-sitter-python
# Python 3.11+, tree-sitter 0.24+ で動作確認

import tree_sitter_python as tspython
from tree_sitter import Language, Parser, Node

# パーサの初期化(事前コンパイル不要)
PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)


def extract_semantic_units(source_code: str) -> list[dict]:
    """ソースコードからセマンティックな単位(関数、クラス等)を抽出する"""
    tree = parser.parse(bytes(source_code, "utf-8"))
    units = []

    # 抽出対象のノードタイプ(Pythonの場合)
    target_types = {
        "function_definition",
        "class_definition",
        "decorated_definition",
    }

    def visit(node: Node, parent_name: str = "") -> None:
        if node.type in target_types:
            # 関数/クラス名の取得
            name_node = node.child_by_field_name("name")
            name = name_node.text.decode("utf-8") if name_node else "anonymous"

            # スコープチェーンの構築(例: "UserService > get_user")
            scope = f"{parent_name} > {name}" if parent_name else name

            units.append({
                "type": node.type,
                "name": name,
                "scope": scope,
                "start_line": node.start_point[0] + 1,
                "end_line": node.end_point[0] + 1,
                "text": node.text.decode("utf-8"),
                "byte_range": (node.start_byte, node.end_byte),
            })

            # 子ノードの探索(ネストされたクラス・関数)
            for child in node.children:
                visit(child, parent_name=scope)
        else:
            for child in node.children:
                visit(child, parent_name=parent_name)

    visit(tree.root_node)
    return units


# 使用例
sample_code = """
class UserService:
    def __init__(self, db_connection):
        self.db = db_connection

    def get_user(self, user_id: int) -> dict:
        query = "SELECT * FROM users WHERE id = ?"
        return self.db.execute(query, (user_id,))

    def update_user(self, user_id: int, data: dict) -> bool:
        # バリデーション後にDB更新
        validated = self._validate(data)
        return self.db.update("users", user_id, validated)

    def _validate(self, data: dict) -> dict:
        return {k: v for k, v in data.items() if k in ("name", "email")}
"""

units = extract_semantic_units(sample_code)
for unit in units:
    print(f"[{unit['type']}] {unit['scope']} (L{unit['start_line']}-{unit['end_line']})")

実行結果:

[class_definition] UserService (L2-14)
[function_definition] UserService > __init__ (L3-4)
[function_definition] UserService > get_user (L6-8)
[function_definition] UserService > update_user (L10-13)
[function_definition] UserService > _validate (L15-16)

なぜこの実装を選んだか:

  • tree-sitter-python パッケージを使うことで事前コンパイルが不要(Language.build_library()の手動実行が不要になった)
  • child_by_field_name("name") でノード名を取得する方式は、言語文法の構造に依存するため正確
  • スコープチェーン(UserService > get_user)を構築することで、後続のベクトル検索時にコンテキスト情報を付与できる

セマンティックチャンキング:固定サイズ分割との決定的な差

コードをLLMのコンテキストに載せるには「チャンキング」(分割)が必要です。ここで、固定サイズ分割とAST-awareチャンキングの違いを具体例で見てみましょう。

固定サイズ分割の問題:

# 500文字で分割すると...
# チャンク1:
"class UserService:\n    def __init__(self, db):\n        self.db = db\n\n    def get_user(self, user_id: int) -> dict:\n        query = \"SELECT * FROM users WHERE id = ?\"\n        return self.db.execute(query, (user_id,))\n\n    def update_user(self, user_id: int, da"
# ↑ ここで切断!update_userの引数が途中で切れる

# チャンク2:
"ta: dict) -> bool:\n        validated = self._validate(data)\n        return self.db.update(\"users\", user_id, validated)\n..."
# ↑ 関数の先頭部分が欠落

AST-awareチャンキングの結果:

# チャンク1: クラス定義 + __init__
"class UserService:\n    def __init__(self, db):\n        self.db = db"

# チャンク2: get_user(完全な関数単位)
"# scope: UserService > get_user\ndef get_user(self, user_id: int) -> dict:\n    query = \"SELECT * FROM users WHERE id = ?\"\n    return self.db.execute(query, (user_id,))"

# チャンク3: update_user + _validate(関連する関数をまとめる)
"# scope: UserService > update_user\ndef update_user(self, user_id: int, data: dict) -> bool:\n    validated = self._validate(data)\n    return self.db.update(\"users\", user_id, validated)\n\ndef _validate(self, data: dict) -> dict:\n    return {k: v for k, v in data.items() if k in (\"name\", \"email\")}"

supermemory.aiのベンチマークでは、この差が検索精度に直結することが示されています。

チャンキング手法 Recall@5 IoU@5 特徴
code-chunk(AST-aware) 70.1% 0.43 構文境界を尊重、スコープ情報付与
chonkie-code 49.0% 0.38 部分的にAST認識
固定サイズ(500文字) 42.4% 0.34 構文を無視して分割

ハマりポイント: AST-awareチャンキングでも、チャンクサイズの上限設定が重要です。上限を大きくしすぎると、巨大なクラス定義が1チャンクに収まり、ベクトル検索時にノイズが増加します。Vecta 2026ベンチマークでは、再帰的512トークン分割が69%の精度で最も高く、過大なチャンクはリランカーの精度を低下させると報告されています。推奨はチャンクサイズ500-1000文字、オーバーラップ20%です。

多言語対応の実装パターン

実務では複数言語が混在するリポジトリが一般的です。以下に、ファイル拡張子から自動で言語を検出し、適切なパーサを選択する実装パターンを示します。

# multi_lang_parser.py
from tree_sitter import Language, Parser

# 各言語パッケージをインポート
# pip install tree-sitter-python tree-sitter-javascript tree-sitter-typescript tree-sitter-rust
import tree_sitter_python as tspython
import tree_sitter_javascript as tsjavascript
import tree_sitter_typescript as tstypescript
import tree_sitter_rust as tsrust

# 言語マッピング(拡張子 → Language オブジェクト)
LANGUAGE_MAP: dict[str, Language] = {
    ".py": Language(tspython.language()),
    ".js": Language(tsjavascript.language()),
    ".ts": Language(tstypescript.language_typescript()),
    ".tsx": Language(tstypescript.language_tsx()),
    ".rs": Language(tsrust.language()),
}

# 各言語のセマンティックノードタイプ定義
SEMANTIC_NODES: dict[str, set[str]] = {
    ".py": {"function_definition", "class_definition", "decorated_definition"},
    ".js": {"function_declaration", "class_declaration", "arrow_function",
            "method_definition"},
    ".ts": {"function_declaration", "class_declaration", "interface_declaration",
            "type_alias_declaration", "method_definition"},
    ".tsx": {"function_declaration", "class_declaration", "interface_declaration",
             "type_alias_declaration", "method_definition"},
    ".rs": {"function_item", "impl_item", "struct_item", "enum_item",
            "trait_item"},
}


def get_parser(file_ext: str) -> tuple[Parser, set[str]] | None:
    """ファイル拡張子からパーサとセマンティックノード定義を取得"""
    lang = LANGUAGE_MAP.get(file_ext)
    if lang is None:
        return None  # 非対応言語はプレーンテキストフォールバック

    p = Parser(lang)
    nodes = SEMANTIC_NODES.get(file_ext, set())
    return p, nodes

なぜ言語ごとにセマンティックノードを定義するか:

tree-sitterの文法はC言語で定義されるgrammar.jsに基づいており、言語ごとにノードタイプ名が異なります。たとえばPythonのfunction_definitionはRustではfunction_item、TypeScriptではfunction_declarationです。この差異を吸収するマッピングテーブルが必要になります。

ベクトル埋め込みとAST構造を組み合わせたコード検索パイプラインの構築

パイプライン全体設計

ここまでで「コードを意味のある単位に分割する」方法を理解しました。次に、分割したチャンクをベクトル化して検索可能にするパイプラインを構築します。

CocoIndexを使ったインデックス構築の実装

CocoIndexは、tree-sitterとベクトル埋め込みを組み合わせたコードインデックスフレームワークです。Rustで実装されたコアエンジンにPythonバインディングを提供し、約50行のPythonコードでインデックスパイプラインを構築できます。

# code_indexer.py
# requirements: pip install cocoindex
# PostgreSQL + pgvector が必要(ベクトル格納先)

import cocoindex


@cocoindex.transform_flow()
def code_to_embedding(code_chunk: cocoindex.DataSlice) -> cocoindex.DataSlice:
    """コードチャンクをベクトル埋め込みに変換"""
    return code_chunk.transform(
        cocoindex.functions.SentenceTransformerEmbed(
            model="sentence-transformers/all-MiniLM-L6-v2"
        )
    )


@cocoindex.flow_def(name="CodeEmbedding")
def code_embedding_flow(flow_builder, data_scope):
    """コードベースのインデックスパイプライン定義"""

    # Step 1: ソースファイルの読み込み
    data_scope["files"] = flow_builder.add_source(
        cocoindex.sources.LocalFile(
            path="./src",  # インデックス対象ディレクトリ
            included_patterns=["*.py", "*.ts", "*.rs", "*.go"],
            excluded_patterns=["*test*", "*__pycache__*", "node_modules/*"],
        )
    )

    # Step 2: ファイル拡張子から言語を検出
    files = data_scope["files"]
    files["language"] = files["filename"].transform(
        cocoindex.functions.ExtractSuffix()
    )

    # Step 3: tree-sitterベースのセマンティックチャンキング
    files["chunks"] = files["content"].transform(
        cocoindex.functions.SplitRecursively(
            language=files["language"],  # 言語に応じたパーサ自動選択
            chunk_size=1000,             # 最大1000文字/チャンク
            chunk_overlap=300,           # 300文字のオーバーラップ
        )
    )

    # Step 4: ベクトル埋め込み生成
    chunks = files["chunks"]
    chunks["embedding"] = chunks["text"].transform(code_to_embedding)

    # Step 5: PostgreSQL(pgvector)に格納
    data_scope.add_collector(
        cocoindex.collectors.Postgres(
            table_name="code_embeddings",
            primary_key=["filename", "chunk_index"],
        )
    )

実装のポイント:

  • SplitRecursively: tree-sitterのAST/CSTを利用して構文境界でチャンク分割する。languageパラメータに拡張子を渡すと自動でパーサが選択される
  • chunk_size=1000, chunk_overlap=300: チャンクサイズ1000文字、オーバーラップ300文字(約30%)はzennの記事で推奨される20%をやや超えるが、関数境界での切断を防ぐための安全マージン
  • sentence-transformers/all-MiniLM-L6-v2: 384次元のベクトルを生成。ローカル実行でAPIキー不要。CocoIndex公式ドキュメントによると、Voyage AI等のコード特化モデルに切り替えることも可能

インクリメンタル更新の仕組み

大規模コードベースでは、毎回全ファイルを再インデックスするのは非効率です。CocoIndexはデータリネージ(系統管理)を活用したインクリメンタル処理を実装しています。

変更検出 → 差分ファイル特定 → 該当チャンクのみ再埋め込み → DB更新

CocoIndex公式ブログによると、エンタープライズ環境での典型的な日次コード変更率は約1%であり、インクリメンタル処理により日次更新は全体の1%のファイルのみが埋め込みモデルを通過します。これにより、数万ファイル規模のリポジトリでもリアルタイムに近いインデックス更新が可能になります。

トレードオフ: インクリメンタル更新はファイル単位で動作するため、ファイルAの関数名変更がファイルBの呼び出し箇所のチャンクに反映されるまでタイムラグが生じます。依存関係を跨ぐ変更の場合、関連ファイルの再インデックスを手動でトリガーする設計が必要です。

セマンティック検索の実装

インデックスが構築できたら、自然言語クエリでコードを検索する機能を実装します。

# search.py
import cocoindex
import numpy as np


def search_code(query: str, top_k: int = 5) -> list[dict]:
    """自然言語クエリでコードを検索"""

    # クエリをベクトル化(インデックス時と同じモデルを使用)
    query_embedding = cocoindex.functions.SentenceTransformerEmbed(
        model="sentence-transformers/all-MiniLM-L6-v2"
    ).embed(query)

    # PostgreSQL pgvectorでコサイン類似度検索
    results = cocoindex.query(
        table="code_embeddings",
        query_vector=query_embedding,
        metric="cosine",
        top_k=top_k,
    )

    return [
        {
            "filename": r["filename"],
            "chunk_text": r["text"],
            "score": r["similarity"],
            "start_line": r.get("start_line"),
        }
        for r in results
    ]


# 使用例
results = search_code("ユーザー情報をDBから取得する関数")
for r in results:
    print(f"[{r['score']:.3f}] {r['filename']}:{r['start_line']}")
    print(f"  {r['chunk_text'][:100]}...")

実践ツールで体験するコード検索最適化

AiDex:50倍のコンテキスト削減を実現するMCPサーバ

理論を理解したところで、実際に使えるツールを見ていきましょう。AiDex は、tree-sitterベースのコードインデックスをMCPサーバとして提供するツールです。

セットアップ:

# グローバルインストール
npm install -g aidex-mcp

# Claude Code、Cursor、Windsurf、Gemini CLI等を自動検出して設定

AiDexの記事で報告されているベンチマーク結果は以下の通りです。

項目 grepベース検索 AiDex(AST検索)
PlayerHealthの検索 200件マッチ、5ファイル読み込み 直接3箇所を特定
トークン消費 2,000+ 約50
ツール呼び出し回数 5回以上 1回
応答時間 10秒以上 3ms

アーキテクチャ:

ファイル変更 → tree-sitter AST解析 → 識別子・シグネチャ抽出
     → SQLite WALモード格納 → MCP stdio経由でエージェントに提供

AiDexの設計で注目すべき点は、ベクトル埋め込みを使わずSQLiteの全文検索に依存していることです。これは以下の設計判断に基づきます。

  • コード内の識別子(関数名、クラス名等)は自然言語と異なり、正確なテキストマッチが有効
  • ベクトル検索のセットアップコスト(埋め込みモデル、ベクトルDB)が不要
  • クエリ応答が3msと極めて高速

制約条件: AiDexは識別子のテキストマッチに特化しているため、「ユーザー情報を取得する処理」のような自然言語クエリには対応できません。セマンティック検索が必要な場合はCocoIndexやカスタムパイプラインが適しています。用途に応じた使い分けが重要です。

CocoIndex Code:70%トークン削減のCLIツール

CocoIndex Codeは、tree-sitter + ベクトル埋め込みを組み合わせたCLI/MCPサーバです。

セットアップ:

# インストール
pipx install cocoindex-code

# 初期化(設定ファイル生成)
ccc init

# インデックス構築
ccc index

# 検索(自然言語OK)
ccc search "error handling in user authentication"

特徴:

  • 28以上の言語に対応(tree-sitterで解析)
  • デフォルト埋め込みモデル all-MiniLM-L6-v2 はローカル実行、APIキー不要
  • Voyage AI、OpenAI等のコード特化モデルにも切り替え可能
  • Claude Code / Cursor / Codex とSkillまたはMCP経由で統合

Claude Codeとの統合例(Skill方式):

Claude Codeの .claude/skills/ ディレクトリにコード検索スキルを配置することで、エージェントが必要に応じて自動的にセマンティック検索を呼び出すようになります。Martin Fowlerが述べるContext Engineering for Coding Agentsの「Skill」パターンに該当し、エージェントが必要と判断した時だけ遅延ロードされる仕組みです。

ツール選定ガイド

観点 AiDex CocoIndex Code カスタムパイプライン
検索方式 テキストマッチ(SQLite FTS) セマンティック(ベクトル) 両方組み合わせ可能
自然言語クエリ 非対応 対応 対応
セットアップ npm install -g のみ pipx install + ccc init PostgreSQL + 埋め込みモデル
応答速度 1-10ms 数十ms〜数百ms 構成に依存
適用場面 識別子の正確な検索 概念ベースの検索 要件に応じたカスタム
コスト 無料、ローカルのみ 無料(デフォルトモデル) モデル・インフラに依存

選定の指針:

  • 「関数名やクラス名で素早く定義に飛びたい」 → AiDex(テキストマッチの速度が圧倒的)
  • 「このバグに関連するコードを概念で探したい」 → CocoIndex Code(セマンティック検索の柔軟性)
  • 「独自のランキングロジックやフィルタリングが必要」 → カスタムパイプライン

よくある問題と解決方法

問題 原因 解決方法
tree-sitterで特定言語のパースが失敗する 言語パッケージ未インストール pip install tree-sitter-<lang> で個別インストール。対応言語はtree-sitter公式で確認
チャンクサイズが大きすぎて検索精度が低い クラス定義全体が1チャンクに chunk_sizeを500-1000文字に制限。再帰的分割で子ノード単位に分解
ベクトル検索で関連コードがヒットしない コードと自然言語のセマンティックギャップ コード特化埋め込みモデル(Voyage Code等)に変更、またはチャンクにdocstring・コメントを含める
インクリメンタル更新で依存先の変更が反映されない ファイル単位の変更検出の限界 ccc index --full で定期的に全体再構築。CI/CDパイプラインに組み込む
大規模リポジトリ(10万ファイル超)でインデックス構築が遅い 初回の全ファイル解析負荷 対象ディレクトリを限定(included_patterns)、.gitignoreパターンを除外設定に活用

まとめと次のステップ

まとめ:

  • Context Engineering は、プロンプトエンジニアリングを包含する上位概念。LLMが参照する情報環境全体(プロンプト・メモリ・ツール・データ)を設計する技術で、コーディングエージェントの性能を左右する
  • tree-sitter によるAST解析は、コードを関数・クラス単位のセマンティックチャンクに分割する基盤技術。固定サイズ分割と比較してRecall@5で70.1% vs 42.4%と大幅に優れる
  • ベクトル埋め込みとAST構造の組み合わせにより、grepベースの検索から50倍のコンテキスト削減、70%のトークン予算削減が報告されている
  • ツール選定では、識別子の正確検索にはAiDex(テキストマッチ)、概念ベースの検索にはCocoIndex Code(セマンティック検索)が適している

次にやるべきこと:

  1. まずはAiDexを試す: npm install -g aidex-mcp でインストールし、普段のClaude CodeやCursorで効果を体感する(5分で導入可能)
  2. セマンティック検索が必要なら: CocoIndex Codeを導入し、自然言語でのコード検索を試す
  3. カスタムパイプラインの検討: 本記事のPython実装例をベースに、自社コードベースに特化したチャンキング戦略と埋め込みモデルの組み合わせを実験する

参考


注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?