TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

576

エピソード紹介 Ep.1 – クリーンアーキテクチャとは ← 今回はこちら Ep.2 – 認証方式の実践的な紹介 Ep.3 – ER設計と監査ログ Ep.4 – RepoScanner の実装とテスト Ep.5 – Copilot プロンプトを効率化 こんな方へ特におすすめ クリーンアーキテクチャが何かイメージを掴みたい方 概要 こんにちは。サイオステクノロジーのはらちゃんです! フロントエンドを開発していたとき、アトミックデザインという手法を知りました。ざっくり言うと小さな部品を用意して、使いまわせるようにする設計です。 バックエンドを開発するときに、同じような設計手法があるはずだと気になりました。 そこで知った手法こそ、クリーンアーキテクチャです。 — 本シリーズでは、Copilotを活用しつつ、クリーンアーキテクチャに沿って小規模なプロダクト「RepoScanner」を設計・実装した経緯をまとめます。 シリーズの前提知識として、このエピソードでは、クリーンアーキテクチャの基本を簡潔に説明します。 定義 はじめに、どのような方針であるのかイメージを掴みましょう。 クリーンアーキテクチャは、ソフトウェアを複数の関心ごとに分離し、変更に強くテストしやすい構造を保つ設計原則です。中心にビジネスルールとなるドメインを置き、外側にインフラやUIを置くことで、依存の向きを守ります。 クリーンアーキテクチャの解説でよくみられるイラストです。 主なレイヤー 全体は以下のような層に分かれています。 src/ ├── domain/ # 【最内部】ビジネスルール │ ├── entities/ # ここにエンティティを置く │ └── repositories/ # リポジトリインターフェースを定義する ├── use-cases/ # 【内側】アプリケーション固有の手順 │ └── createUser.ts # ユースケースの実装をする ├── infrastructure/ # 【外側】技術固有の実装 │ └── persistence/ # 永続化の実装をする └── functions/ # 【最外部】エントリポイント └── http/ # ここで依存性注入を行う 具体的にどのような役割なのか見ていきましょう。 Domain Domain層は、「コードでビジネスを記述する」場所であり、技術的な詳細(データベースやWebフレームワーク)とは無関係に、ビジネスの正解を定義する最も重要な層です。 エンティティ(概念) 「何ができるか」の定義。型やNullを許容するかなどのビジネスルールを持つ。 具体的には、以下のような定義をします。ここではユーザー情報を用意しています。 Python @dataclass(slots=True) class User: user_id: UUID display_name: str | None created_at: datetime last_login_at: datetime | None リポジトリ(ふるまい) 「どのように取得 / 保存するか」の定義。エンティティの操作を抽象化する。 具体的には、以下のような定義をします。 定義したエンティティを用いて、「ログイン時に得られた情報を元に、ユーザーを作成 / 更新し、結果として User エンティティを返す」という処理をしています。 リポジトリのメソッド引数で再び型を明記することで、Pythonの型ヒントを機能させる意味もあります。 Python class UserRepository(Protocol): def upsert_login_user(self, display_name: str | None) -> User: ... Use Cases Use Cases層は、どのようにエンティティやリポジトリを組み合わせて業務フローにするかを表現する層です。 司令塔としての役割があり、受け取った入力に対しリポジトリを使ってデータを取得 / 更新します。 例えば、以下のような具体的な機能を実現しています。 トークンでユーザー情報を取得して情報を更新する DBから項目を受け取ってデータを返す Infrastructure Infrastructure層は、Use Cases層とさらに外側の層を繋ぐための層です。 データの変換と橋渡しを行う、アダプターという役割を担って外部ライブラリやDB を利用します。 例えば、以下のようにデータの相互変換や外側への具体的なアクセス実装をしています。 db.py ファイルでSQL に接続する SQL文で操作を指定する functions Functions層はビジネスルールを組み合わせ、外側から受け取ったリクエストを処理する層です。 これにより、UIの変更(HTML→JSON)や、データベースの変更(MySQL→PostgreSQL)が、Domain層に一切影響を与えないように保護しつつ、具体的な技術要素との連携を実現しています。 例えば、以下のような具体的な機能を実現をしています。 HTTP API のスキーマを定義する main.py ファイルでエントリポイントの指定をする Pydantic を利用してバリテーションをする 依存性ルール(外側→内側) コードの依存は常に外側から内側へ向かいます。内側の層が外側の具体実装を import してはいけません。 依存性逆転の原則に従い、ドメインやユースケースがインターフェースを定義し、インフラがそれを実装します。 functions(最外部) -> infrastructure(外側実装) -> use-cases(アプリ手順) -> domain(最内部ルール) 依存を分けるメリット 大きく2つ考えられます。 影響範囲が狭い ドメイン / ユースケースを外部変更から隔離でき、要件変更など対応しやすい。 依存性注入(DI)とテスト ユースケースはリポジトリなど依存をコンストラクタやファクトリで注入して受け取る。 テストではその注入ポイントにモックやインメモリ実装を差し替えることで、ユースケース単体で即テストできる。 依存を分けるデメリット こちらも、大きく2つ考えられます。 初期コスト インターフェース定義やDIの仕組み、アダプター実装が増える。 実装の一貫性維持 境界のインターフェース設計を誤ると、後続実装で整合性を保つのが難しい。 層をまたぐ設計と抽象が増え、設計理解の学習コストが上がる。 三層構造との違い ここまで学んで、依存を分けるなら三層構造でもやっていなかったか?と疑問に思いました。 クリーンアーキテクチャと三層構造は、どちらもソフトウェアの「関心事を分離する」ための設計手法ですが、最大の違いは「依存関係の方向」と「システムにおいて何を中心と捉えるか」にあります。 三層構造は、システムを役割ごとに「上から下へ」3つの層に分けるアーキテクチャです。 プレゼンテーション層 ビジネスロジック層 データアクセス層 ここでは、プレゼンテーション層はビジネスロジック層に依存し、ビジネスロジック層はデータアクセス層に依存します。つまり、システムの中心がデータベースになることが多いです。 システムの中心がビジネスロジックであるクリーンアーキテクチャとは依存関係の方向が違うことがわかりました。 まとめ 今回は、クリーンアーキテクチャとは何かをご紹介しました。 依存は外側→内側。インフラ実装を内側に漏らさない。 エンティティは何ができるか、リポジトリはどのようにするか。 DI を使えばユースケースはテストしやすくなる。 ユースケース単体と境界・統合を優先して整備する。 これからクリーンアーキテクチャの思想に則った設計を行うのでお楽しみに! 参考 The Clean Architecture ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot × Clean Architecture | クリーンアーキテクチャとは first appeared on SIOS Tech Lab .
アバター
PSSLの佐々木です。 E2Eテストは重要だとわかっていても、Playwrightのコードを書くのが面倒で後回しにしていませんか? 本記事では、 Markdownファイルに日本語で操作手順を書くだけで、Playwrightが自動実行してくれるE2Eテストフレームワーク の作り方を、実際のプロダクション事例をもとに解説します。 この記事でわかること Markdownシナリオ駆動のE2Eテストの全体アーキテクチャ 自然言語ステップをPlaywrightアクションに変換する仕組み フォーム入力の多段フォールバック戦略 動画記録・スクリーンショットによるデバッグ支援 pre-commitフックとの連携による開発フロー統合 なぜMarkdownでE2Eテストを書くのか 従来のE2Eテストには3つの問題がありました。 テストコードが仕様と乖離する — Playwrightのコードを読んでも、何のシナリオをテストしているのかひと目でわからない 非エンジニアがレビューできない — PMやデザイナーがテストケースを確認・追加できない メンテナンスコストが高い — セレクタの変更ひとつで大量のテストが壊れる Markdownシナリオ駆動テストなら、こう書けます: ## シナリオ: 管理者ログイン成功 1. <http://localhost:8055/login/> にアクセスする 2. メールアドレスに「admin@example.com」を入力する 3. パスワードに「admin123」を入力する 4. 「ログイン」ボタンをクリックする 5. 「ダッシュボード」というテキストが画面に表示されていることを確認する 6. URLに「/admin/dashboard」が含まれることを確認する 誰が読んでも何をテストしているかわかります。 全体アーキテクチャ フレームワークは4つのコンポーネントで構成されます。 Markdownシナリオファイル (.md) | [Parser] Markdownを構造化データに変換 | [Step Mapper] 自然言語 → Playwrightアクション | [Runner] ブラウザ操作の実行・動画記録・レポート 各コンポーネントのコード量は驚くほど小さく、Parser約90行、Step Mapper約280行、Runner約280行で実現できます。 それぞれ解説していきます。 Step 1: Markdownシナリオのフォーマットを定義する まず、テストシナリオを記述するMarkdownのフォーマットを決めます。 # 認証機能テスト ## 前提条件 - テスト用管理者が存在する(email: admin@example.com, password: admin123) - テスト用代理店ユーザーが存在する(email: user@example.com, password: user123) ## シナリオ: 管理者ログイン成功 → ログアウト 1. <http://localhost:8055/login/> にアクセスする 2. メールアドレスに「admin@example.com」を入力する 3. パスワードに「admin123」を入力する 4. 「ログイン」ボタンをクリックする 5. 「ダッシュボード」というテキストが画面に表示されていることを確認する 6. URLに「/admin/dashboard」が含まれることを確認する 7. ログアウトする ## シナリオ: パスワード間違い 1. <http://localhost:8055/login/> にアクセスする 2. メールアドレスに「admin@example.com」を入力する 3. パスワードに「wrongpassword」を入力する 4. 「ログイン」ボタンをクリックする 5. 「メールアドレスまたはパスワードが正しくありません」というテキストが表示されることを確認する ルールはシンプルです: 要素 記法 例 テストファイルのタイトル # タイトル # 認証機能テスト 前提条件 ## 前提条件 + 箇条書き - テスト用ユーザーが存在する シナリオ ## シナリオ: 名前 + 番号リスト ## シナリオ: ログイン成功 ステップ 1. 操作内容 1. 「ログイン」ボタンをクリックする 1ファイルに複数シナリオを書けます。前提条件セクションはドキュメントとして機能し、シードデータの仕様を明示する役割を果たします。 Step 2: Markdownパーサーを実装する Markdownファイルを解析して構造化データに変換するパーサーを作ります。 # parser.py import re from dataclasses import dataclass, field from pathlib import Path @dataclass class Scenario: name: str steps: list[str] = field(default_factory=list) @dataclass class ScenarioFile: path: Path title: str preconditions: list[str] = field(default_factory=list) scenarios: list[Scenario] = field(default_factory=list) def parse_scenario_file(filepath: Path) -> ScenarioFile: """Markdownシナリオファイルを解析する""" text = filepath.read_text(encoding="utf-8") lines = text.splitlines() title = "" preconditions: list[str] = [] scenarios: list[Scenario] = [] current_section = None current_scenario: Scenario | None = None for raw_line in lines: line = raw_line.strip() # トップレベルタイトル if line.startswith("# ") and not line.startswith("## "): title = line[2:].strip() continue # セクションヘッダー if line.startswith("## "): header = line[3:].strip() if "前提条件" in header: current_section = "preconditions" current_scenario = None continue # シナリオ検出 m = re.match(r"^シナリオ[::]\\s*(.+)", header) if m: current_scenario = Scenario(name=m.group(1).strip()) scenarios.append(current_scenario) current_section = "scenario" continue # 箇条書き(前提条件) m_bullet = re.match(r"^[-*]\\s+(.+)", line) if m_bullet: content = m_bullet.group(1).strip() if current_section == "preconditions": preconditions.append(content) elif current_section == "scenario" and current_scenario: current_scenario.steps.append(content) continue # 番号付きリスト(シナリオステップ) m_num = re.match(r"^\\d+\\.\\s+(.+)", line) if m_num and current_section == "scenario" and current_scenario: current_scenario.steps.append(m_num.group(1).strip()) return ScenarioFile( path=filepath, title=title or filepath.stem, preconditions=preconditions, scenarios=scenarios, ) ポイントは番号付きリストから番号プレフィックスを除去してステップ文字列だけを抽出していることです。 1. 「ログイン」ボタンをクリックする → 「ログイン」ボタンをクリックする Step 3: ステップマッパーを実装する(コア部分) ここがこのフレームワークの心臓部です。自然言語のステップを正規表現でパターンマッチし、対応するPlaywrightアクションを実行します。 基本構造 # step_mapper.py import re from playwright.sync_api import Page, expect def execute_step(page: Page, step: str, base_url: str, timeout: int = 30000): """自然言語ステップを解釈してPlaywrightアクションを実行する""" # --- ナビゲーション --- m = re.search(r"(https?://\\S+)\\s*(?:に|へ)アクセスする", step) if m: page.goto(m.group(1), timeout=timeout) return m = re.search(r"(/.+?)\\s*(?:に|へ)(?:アクセス|遷移|移動)する", step) if m: page.goto(base_url + m.group(1), timeout=timeout) return # ... 他のパターンが続く 対応するステップパターン一覧 フレームワークが認識するステップパターンを紹介します。 ナビゲーション # 絶対URL # 例: "<http://localhost:8055/login/> にアクセスする" m = re.search(r"(https?://\\S+)\\s*(?:に|へ)アクセスする", step) if m: page.goto(m.group(1), timeout=timeout) return # 相対パス # 例: "/agency/dashboard にアクセスする" m = re.search(r"(/.+?)\\s*(?:に|へ)(?:アクセス|遷移|移動)する", step) if m: page.goto(base_url + m.group(1), timeout=timeout) return リンク・ボタンのクリック # リンクをクリック # 例: 「物件管理」リンクをクリックする m = re.search(r"[「「](.+?)[」」](?:リンク|メニュー)をクリックする", step) if m: text = m.group(1) page.get_by_role("link", name=text).first.click(timeout=timeout) page.wait_for_load_state("networkidle") return # ボタンをクリック # 例: 「ログイン」ボタンをクリックする m = re.search(r"[「「](.+?)[」」]ボタンを(?:クリック|押)する", step) if m: text = m.group(1) # submit ボタンを優先的に探す submit_buttons = page.locator("button[type='submit']:visible") if submit_buttons.count() > 0: for i in range(submit_buttons.count()): btn = submit_buttons.nth(i) if text in (btn.inner_text() or ""): btn.click(timeout=timeout) page.wait_for_load_state("networkidle") return # テキスト完全一致がなければ最初のsubmitボタン submit_buttons.first.click(timeout=timeout) else: page.get_by_role("button", name=text).first.click(timeout=timeout) page.wait_for_load_state("networkidle") return # テキスト要素をクリック(テーブル行など) # 例: 「SKR-001」テキストをクリックする m = re.search(r"[「「](.+?)[」」](?:テキスト|文字|項目|行)をクリックする", step) if m: page.get_by_text(m.group(1), exact=False).first.click(timeout=timeout) return フォーム入力 # テキスト入力 # 例: メールアドレスに「admin@example.com」を入力する m = re.search( r"[「「]?(.+?)[」」]?(?:欄|フィールド)?に\\s*[「「](.+?)[」」]\\s*(?:を入力|と入力)する", step ) if m: label, value = m.group(1), m.group(2) _fill_by_label(page, label, value, timeout) return # セレクトボックス # 例: 「ステータス」で「通電中」を選択する m = re.search(r"[「「](.+?)[」」](?:で|から)\\s*[「「](.+?)[」」]\\s*を選択する", step) if m: label, value = m.group(1), m.group(2) page.get_by_label(label).select_option(label=value, timeout=timeout) return # 日付入力 # 例: 希望日に「2026-12-01」を入力する m = re.search( r"[「「](.+?)[」」](?:欄|フィールド)?に\\s*(\\d{4}[-/]\\d{1,2}[-/]\\d{1,2})\\s*を(?:入力|設定)する", step ) if m: label = m.group(1) date_str = m.group(2).replace("/", "-") page.get_by_label(label).fill(date_str, timeout=timeout) return アサーション(検証) # テキストが表示されていることを確認 # 例: 「ダッシュボード」というテキストが画面に表示されていることを確認する m = re.search( r"[「「](.+?)[」」].*(?:表示されている|表示される|見える|確認する|含まれる|ある)", step ) if m: text = m.group(1) locator = page.locator( f":not(option):not(select):visible:has-text('{text}')" ).first expect(locator).to_be_visible(timeout=timeout) return # URLの確認 # 例: URLに「/admin/dashboard」が含まれることを確認する m = re.search(r"URLに\\s*[「「](.+?)[」」]\\s*が含まれる", step) if m: expect(page).to_have_url(re.compile(re.escape(m.group(1))), timeout=timeout) return # テキストが表示されていないことを確認 # 例: 「エラー」というテキストが表示されていないことを確認する m = re.search(r"[「「](.+?)[」」].*(?:表示されていない|表示されない|見えない)", step) if m: expect( page.get_by_text(m.group(1), exact=False).first ).not_to_be_visible(timeout=timeout) return その他 # ログアウト if re.search(r"ログアウトする", step): page.context.clear_cookies() page.goto(base_url + "/login/") page.wait_for_load_state("networkidle") return # 待機 # 例: 3秒待つ m = re.search(r"(\\d+)秒(?:待つ|待機する)", step) if m: page.wait_for_timeout(int(m.group(1)) * 1000) return # マッチしなかった場合 raise ValueError(f"未対応のステップ:{step}") フォーム入力の多段フォールバック戦略 フォーム入力は最もハマりやすいポイントです。実際のHTMLは <label> がない場合、 placeholder で代用している場合、CSSフレームワーク特有のマークアップなど、多様です。 そこで、 5段階のフォールバック戦略 を実装します。 def _fill_by_label(page: Page, label: str, value: str, timeout: int): """多段フォールバックでフォームフィールドを特定して入力する""" # Level 1: aria-label / <label> による特定 try: loc = page.get_by_label(label, exact=False).locator("visible=true") if loc.count() > 0: loc.first.fill(value, timeout=timeout) return except Exception: pass # Level 2: placeholder による特定 try: loc = page.get_by_placeholder(label, exact=False).locator("visible=true") if loc.count() > 0: loc.first.fill(value, timeout=timeout) return except Exception: pass # Level 3: label要素のDOM構造から辿る try: labels = page.locator(f"label:visible:has-text('{label}')") for i in range(labels.count()): label_elem = labels.nth(i) parent = label_elem.locator("..") inp = parent.locator("input:visible, textarea:visible, select:visible") if inp.count() > 0: inp.first.fill(value, timeout=timeout) return except Exception: pass # Level 4: name属性による特定(日本語ラベル → HTMLのname属性マッピング) field_map = { "メールアドレス": "email", "パスワード": "password", "物件名": "name", "郵便番号": "postal_code", "都道府県": "prefecture", "市区町村": "city", "町名番地": "address", "建物名": "building_name", "部屋番号": "room_number", "管理コード": "external_key", "メモ": "memo", # 必要に応じて追加 } name_attr = field_map.get(label) if name_attr: try: loc = page.locator(f"input[name='{name_attr}']:visible, textarea[name='{name_attr}']:visible") if loc.count() > 0: loc.first.fill(value, timeout=timeout) return except Exception: pass # Level 5: type属性による特定 type_map = {"メールアドレス": "email", "パスワード": "password"} type_attr = type_map.get(label) if type_attr: try: loc = page.locator(f"input[type='{type_attr}']:visible") if loc.count() > 0: loc.first.fill(value, timeout=timeout) return except Exception: pass raise ValueError(f"入力フィールドが見つかりません:{label}") この多段フォールバックにより、ほとんどのHTMLフォームに対応できます。 レベル 方法 対応するケース 1 get_by_label 正しく <label> がマークアップされたフォーム 2 get_by_placeholder placeholder 属性で入力ヒントを持つフォーム 3 DOM構造を辿る <label> がinputと同じ親要素内にあるフォーム 4 name 属性マッピング <label> がないがname属性は一貫しているフォーム 5 type 属性 email/passwordなど型で一意に特定できるフィールド Step 4: テストランナーを実装する シナリオファイルの解析とステップ実行を統合するランナーを作ります。 # runner.py import signal import socket import subprocess import sys import time from dataclasses import dataclass from pathlib import Path from playwright.sync_api import sync_playwright from config import APP_DIR, BASE_URL, BROWSER, HEADLESS, SCENARIOS_DIR, TIMEOUT, VIDEO_DIR from parser import parse_scenario_file from step_mapper import execute_step @dataclass class TestResult: scenario_file: str scenario_name: str passed: bool error: str = "" video_path: str = "" screenshot_path: str = "" def start_django_server(port: int = 8055): """Djangoサーバーを起動する(既に起動中ならスキップ)""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect(("localhost", port)) sock.close() print(f"ポート{port} は既に使用中です。既存のサーバーを使用します。") return None except ConnectionRefusedError: sock.close() proc = subprocess.Popen( [sys.executable, str(APP_DIR / "manage.py"), "runserver", str(port), "--noreload"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) # サーバーの起動を待機 for _ in range(30): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", port)) s.close() print(f"Django開発サーバーがポート{port} で起動しました") return proc except ConnectionRefusedError: time.sleep(1) raise RuntimeError("Djangoサーバーの起動がタイムアウトしました") def stop_django_server(proc): """Djangoサーバーを停止する""" if proc: proc.send_signal(signal.SIGTERM) try: proc.wait(timeout=5) except subprocess.TimeoutExpired: proc.kill() def ensure_seed_data(): """テスト用シードデータを投入する""" subprocess.run( [sys.executable, str(APP_DIR / "manage.py"), "seed", "--reset"], check=True, capture_output=True, ) print("シードデータを投入しました") def run_scenarios(scenario_files: list[Path], base_url: str, timeout: int) -> list[TestResult]: """シナリオファイルを実行して結果を返す""" results = [] with sync_playwright() as p: browser = getattr(p, BROWSER).launch(headless=HEADLESS) for scenario_path in scenario_files: sf = parse_scenario_file(scenario_path) print(f"\\n{'='*60}") print(f"実行:{sf.title} ({scenario_path.name})") print(f"{'='*60}") # ファイル単位でブラウザコンテキスト(動画記録)を作成 video_dir = VIDEO_DIR / scenario_path.stem video_dir.mkdir(parents=True, exist_ok=True) context = browser.new_context( record_video_dir=str(video_dir), record_video_size={"width": 1280, "height": 720}, viewport={"width": 1280, "height": 720}, ) page = context.new_page() for scenario in sf.scenarios: print(f"\\n シナリオ:{scenario.name}") passed = True error_msg = "" screenshot_path = "" for i, step_text in enumerate(scenario.steps, 1): try: print(f" ステップ{i}:{step_text} ... ", end="", flush=True) execute_step(page, step_text, base_url, timeout) print("OK") except Exception as e: print(f"FAILED:{e}") passed = False error_msg = f"ステップ{i}:{step_text} ->{e}" # 失敗時のスクリーンショット ss_path = video_dir / f"{scenario.name}_fail.png" try: page.screenshot(path=str(ss_path)) screenshot_path = str(ss_path) except Exception: pass break results.append(TestResult( scenario_file=scenario_path.name, scenario_name=scenario.name, passed=passed, error=error_msg, screenshot_path=screenshot_path, )) # コンテキストを閉じて動画を確定 video_path_raw = page.video.path if page.video else None page.close() context.close() # 動画をリネーム if video_path_raw: final_video = video_dir / f"{scenario_path.stem}.webm" try: Path(video_path_raw).rename(final_video) for r in results: if r.scenario_file == scenario_path.name: r.video_path = str(final_video) except Exception: pass browser.close() return results def print_summary(results: list[TestResult]): """テスト結果のサマリーを表示する""" print(f"\\n{'='*60}") print("テスト結果サマリー") print(f"{'='*60}") for r in results: icon = "PASS" if r.passed else "FAIL" print(f" [{icon}]{r.scenario_file} >{r.scenario_name}") if r.video_path: print(f" 動画:{r.video_path}") if r.error: print(f" エラー:{r.error}") if r.screenshot_path: print(f" スクリーンショット:{r.screenshot_path}") total = len(results) passed = sum(1 for r in results if r.passed) failed = total - passed print(f"\\n合計:{total} 成功:{passed} 失敗:{failed}") def main(): import argparse parser = argparse.ArgumentParser(description="Markdown E2Eテストランナー") parser.add_argument("scenarios", nargs="*", help="実行するシナリオファイル") parser.add_argument("--base-url", default=BASE_URL) parser.add_argument("--no-headless", action="store_true") parser.add_argument("--no-server", action="store_true") parser.add_argument("--no-seed", action="store_true") parser.add_argument("--timeout", type=int, default=30) args = parser.parse_args() global HEADLESS if args.no_headless: HEADLESS = False # シナリオファイルを取得 if args.scenarios: files = [Path(s) for s in args.scenarios] else: files = sorted(SCENARIOS_DIR.glob("*.md")) if not files: print("シナリオファイルが見つかりません") sys.exit(1) # テスト実行 if not args.no_seed: ensure_seed_data() server_proc = None if not args.no_server: server_proc = start_django_server() try: results = run_scenarios(files, args.base_url, args.timeout * 1000) print_summary(results) sys.exit(0 if all(r.passed for r in results) else 1) finally: stop_django_server(server_proc) if __name__ == "__main__": main() 動画記録のポイント Playwrightの動画記録は ブラウザコンテキスト単位 で行われます。1つのシナリオファイル内の全シナリオが1本の動画にまとまるため、テスト失敗時のデバッグが容易です。 # ファイルごとにコンテキストを作成 → 1ファイル = 1動画 context = browser.new_context( record_video_dir=str(video_dir), record_video_size={"width": 1280, "height": 720}, ) Step 5: シナリオファイルを書く ここまでのフレームワークを使って、実際のシナリオを書いてみましょう。 ファイル命名規則 e2e/scenarios/ ├── 01_authentication.md # 認証 ├── 02_agency_property.md # 代理店 物件管理 ├── 03_agency_request.md # 代理店 申請フロー ├── 04_admin_dashboard.md # 管理者 ダッシュボード ├── 05_admin_request.md # 管理者 申請処理 └── 06_admin_agency.md # 管理者 代理店管理 番号プレフィックスで実行順序を制御します。認証テストを最初に実行し、前提となる機能を先に検証する構成です。 シナリオの書き方のコツ 1. 1シナリオに詰め込みすぎない <!-- BAD: 長すぎるシナリオ --> ## シナリオ: ログイン → 物件登録 → 申請 → 承認 → ログアウト 1. ... (50ステップ) <!-- GOOD: 論理的なまとまりで分割 --> ## シナリオ: 物件登録 1. ... (15ステップ) ## シナリオ: 通電申請 1. ... (12ステップ) ただし、1ファイル内のシナリオは同じ動画に記録されるため、 関連する操作フロー は同じファイルにまとめると良いでしょう。 2. セレクタではなくユーザーが見えるテキストを使う <!-- BAD: 実装依存 --> 1. #login-btn をクリックする <!-- GOOD: ユーザー視点 --> 1. 「ログイン」ボタンをクリックする 3. アサーションは具体的に <!-- BAD: 曖昧 --> 1. ページが表示されることを確認する <!-- GOOD: 具体的 --> 1. 「ダッシュボード」というテキストが画面に表示されていることを確認する 2. URLに「/admin/dashboard」が含まれることを確認する 対応しているステップ表現のリファレンス カテゴリ ステップ例 ナビゲーション http://... にアクセスする 、 /path にアクセスする リンククリック 「メニュー名」リンクをクリックする ボタンクリック 「送信」ボタンをクリックする テキストクリック 「SKR-001」テキストをクリックする タブ切替 「タブ名」タブをクリックする テキスト入力 項目名に「値」を入力する セレクト 「項目名」で「値」を選択する 日付入力 「項目名」に 2026-01-01 を入力する テキスト表示確認 「テキスト」というテキストが表示されていることを確認する テキスト非表示確認 「テキスト」が表示されていないことを確認する URL確認 URLに「/path」が含まれることを確認する ログアウト ログアウトする 待機 3秒待つ Step 6: pre-commitフックで開発に組み込む 変更したファイルに応じて関連するシナリオだけを自動実行するpre-commitフックを設定できます。 #!/bin/bash # e2e/hooks/pre-commit-e2e.sh CHANGED_FILES=$(git diff --cached --name-only) SCENARIOS_TO_RUN="" for file in $CHANGED_FILES; do case "$file" in *auth* | *login* | *middleware*) SCENARIOS_TO_RUN="$SCENARIOS_TO_RUN e2e/scenarios/01_authentication.md" ;; *property*) SCENARIOS_TO_RUN="$SCENARIOS_TO_RUN e2e/scenarios/02_agency_property.md" ;; *request*) SCENARIOS_TO_RUN="$SCENARIOS_TO_RUN e2e/scenarios/03_agency_request.md" SCENARIOS_TO_RUN="$SCENARIOS_TO_RUN e2e/scenarios/05_admin_request.md" ;; *agency*) SCENARIOS_TO_RUN="$SCENARIOS_TO_RUN e2e/scenarios/06_admin_agency.md" ;; # コアモデル変更時はフルリグレッション *models* | *enums* | *config/*) python e2e/runner.py exit $? ;; esac done if [ -n "$SCENARIOS_TO_RUN" ]; then # 重複除去して実行 UNIQUE=$(echo "$SCENARIOS_TO_RUN" | tr ' ' '\\n' | sort -u | tr '\\n' ' ') python e2e/runner.py $UNIQUE exit $? fi echo "E2Eテスト対象の変更なし、スキップします" exit 0 .git/hooks/pre-commit にシンボリックリンクを貼るか、 pre-commit フレームワークで管理します。 プロジェクト構成まとめ e2e/ ├── config.py # 環境設定(URL、タイムアウト、テストアカウント等) ├── parser.py # Markdownパーサー(~90行) ├── step_mapper.py # ステップマッパー(~280行) ├── runner.py # テストランナー(~280行) ├── requirements.txt # playwright>=1.40.0 ├── hooks/ │ └── pre-commit-e2e.sh ├── scenarios/ │ ├── 01_authentication.md │ ├── 02_agency_property.md │ └── ... └── test-results/ # 動画・スクリーンショット出力先 全体で約650行のPythonコードです。 実行方法 # Playwrightのインストール pip install playwright playwright install chromium # 全シナリオ実行 python e2e/runner.py # 特定シナリオのみ python e2e/runner.py e2e/scenarios/01_authentication.md # ブラウザを表示して実行(デバッグ用) python e2e/runner.py --no-headless # サーバーが既に起動している場合 python e2e/runner.py --no-server --no-seed 実行結果: シードデータを投入しました Django開発サーバーがポート 8055 で起動しました ============================================================ 実行: 認証機能テスト (01_authentication.md) ============================================================ シナリオ: 管理者ログイン成功 → ログアウト → パスワード間違い ステップ 1: <http://localhost:8055/login/> にアクセスする ... OK ステップ 2: メールアドレスに「admin@example.com」を入力する ... OK ステップ 3: パスワードに「admin123」を入力する ... OK ステップ 4: 「ログイン」ボタンをクリックする ... OK ステップ 5: 「ダッシュボード」というテキストが表示されている ... OK ... ============================================================ テスト結果サマリー ============================================================ [PASS] 01_authentication.md > 管理者ログイン成功 動画: test-results/01_authentication/01_authentication.webm 合計: 1 成功: 1 失敗: 0 従来のE2Eテストとの比較 項目 Playwrightコード直書き Markdownシナリオ駆動 可読性 エンジニアのみ 誰でも読める 記述量 多い(セレクタ指定等) 少ない(自然言語) メンテナンス テストごとに修正 Step Mapperの1箇所を修正 柔軟性 無制限 パターン定義内に限定 デバッグ ステップ単位で追跡可能 同左 + 動画記録 学習コスト Playwright APIの理解が必要 日本語テンプレに沿うだけ CI統合 標準的 同左 拡張のアイデア 新しいステップパターンの追加 step_mapper.py に正規表現と実行ロジックを追加するだけです。 # 例: チェックボックスの操作 m = re.search(r"[「「](.+?)[」」]チェックボックスをチェックする", step) if m: page.get_by_label(m.group(1)).check() return # 例: ファイルアップロード m = re.search(r"[「「](.+?)[」」]に\\s*[「「](.+?)[」」]\\s*をアップロードする", step) if m: page.get_by_label(m.group(1)).set_input_files(m.group(2)) return 多言語対応 パターン定義を外部ファイル(YAML等)に切り出せば、英語版も容易に作れます。 # patterns_en.yaml navigation: - pattern: 'navigate to "(.*)"' action: goto link_click: - pattern: 'click "(.*)" link' action: click_link テストデータのパラメータ化 前提条件セクションからテストデータを動的に生成する仕組みを追加することもできます。 まとめ Markdownシナリオ駆動のE2Eテストは、 仕様とテストを一体化 させるアプローチです。 Markdownで書いた操作手順がそのままテストになる 非エンジニアでもテストケースをレビュー・追加できる Step Mapperの修正1箇所で全テストの挙動を変更できる 動画記録により失敗時のデバッグが直感的 全体650行程度のコードで実現可能 Playwrightの柔軟なロケーター戦略( get_by_role , get_by_label , get_by_text )と正規表現ベースのパターンマッチの組み合わせにより、少ないコード量で実用的なフレームワークを構築できます。 テストが仕様書と乖離する問題に悩んでいるなら、ぜひ試してみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Markdownで書くE2Eテスト:自然言語シナリオをPlaywrightで自動実行する方法 first appeared on SIOS Tech Lab .
アバター
ども!最近Playwrightの話をいろんな文脈で聞くようになって、ちょっと混乱していた龍ちゃんです。 この記事を読むと: Playwright の3パッケージ(テストランナー / MCP / CLI)の使い分けがわかる コマンド3つで CLI が動くようになる chromium vs chrome のハマりポイントを踏まずに済む 「Playwright MCP」「Playwright CLI」「Playwright テスト」…全部Playwrightなんですけど、 それぞれ別パッケージで用途が違う んですよね。僕自身、チーム内で話していて「あれ、どのPlaywrightの話してる?」ってなったことがあったので、今回はまずそこを整理します。 んで、整理したうえで 一番お手軽な「Playwright CLI」のセットアップ を最小手順で紹介していきます。 Playwright の3つの顔 — まず整理しよう ちょっと内部で話していて混乱したので、ここで整理していきます。 テストランナーとしての Playwright 開発者が一番馴染みのあるやつです。E2Eテストを書いて自動実行する、テストフレームワークとしてのPlaywright。 npx playwright test パッケージは @playwright/test 。「Playwrightって何?」って聞かれたら、多くの人がこれを思い浮かべるんじゃないでしょうか。 MCP としての Playwright AIエージェントにブラウザ操作をさせるためのプロトコルです。JSON-RPCでツール定義をやり取りする仕組みで、ChatGPTやClaude Desktopみたいな チャット型AI 向け。 パッケージは @playwright/mcp 。 以前の記事 でセットアップ方法を紹介しました。便利なんですけど、 トークン消費が重い という課題もあるんですよね。 CLI としての Playwright (今回の主役) シェルコマンドでブラウザを直接操作するやつです。Claude CodeやCursorみたいな AIコーディングエージェント 向け。ファイルシステムにアクセスできる環境が前提になっています。 パッケージは @playwright/cli 。MCPと比べて圧倒的に手軽で、コマンド1発でスクショが撮れます。 整理するとこう 種類 パッケージ 用途 誰向け テストランナー @playwright/test E2Eテストの自動実行 テストエンジニア MCP @playwright/mcp AIにブラウザ操作させる チャット型AI CLI @playwright/cli シェルからブラウザ操作 コーディングエージェント 全部「Playwright」だけど別パッケージ。 テストランナーを入れてもCLIは動きません 。ここ、地味にハマるポイントです。開発者目線でいえば、CLI一択ですね。(MCPとCLI論争がありましたが、再現性とかの観点でも開発系ならCLI一択だと思っています…) 最小セットアップ — これだけで動く セットアップ手順(コマンド3つ) Node.js v18以上が入っている環境なら、以下の3つで動きます。 # 1. ブラウザインストール(※ chromium ではなく chrome) npx playwright install chrome # 2. Skill インストール(Claude Code 用の SKILL.md が配置される) npx @playwright/cli install --skills # 3. 動作確認 npx @playwright/cli open https://example.com 成功すれば、こんな出力が返ってきます。 ### Browser `default` opened with pid XXXXX. - default: - browser-type: chrome - user-data-dir: <in-memory> - headed: false --- ### Page - Page URL: https://example.com/ - Page Title: Example Domain ### Snapshot - [Snapshot](.playwright-cli/page-YYYY-MM-DDTHH-MM-SS.yml) ここまで出ればOK。 もしエラーが出たら、次のセクションを確認してください。 エラーが出たら 「Browser “chrome” is not installed」系のエラー CLIのデフォルトブラウザは chrome (Google Chrome Stable)です。テストランナーでよく使う chromium とは別物で、 npx playwright install chromium で入るブラウザはCLIからは参照されません 。 # NG: テストランナー用のChromiumが入る。CLIでは使えない npx playwright install chromium # OK: Google Chrome Stableがインストールされる npx playwright install chrome テストランナーのPlaywrightに慣れてる人ほど chromium でいけると思ってハマるので、ここだけ注意です。 「Failed to move to new namespace」エラー(DevContainer / Docker 環境) コンテナ環境ではChromeのnamespace作成に権限が足りず、起動に失敗することがあります。プロジェクトルートに .playwright/cli.config.json を作成して --no-sandbox を設定すれば解決します。 { "browser": { "launchOptions": { "args": ["--no-sandbox", "--disable-setuid-sandbox"] } } } これはDevContainerやDocker環境に限った話なので、ローカル環境で動かしてる人は気にしなくて大丈夫です。 基本操作 — snapshot 駆動でページを操る CLI はセッションベースで動きます。 open でブラウザを開いて、操作して、 close で閉じる。この間、ブラウザの状態は保持されます。 npx @playwright/cli open https://example.com # セッション開始 # ... ここで色々操作 ... npx @playwright/cli close # セッション終了 では実際にやってみます。 ページ取得 — snapshot でページ構造を見る open した状態で snapshot を実行すると、ページの構造がYAML形式で返ってきます。 npx @playwright/cli snapshot - generic [ref=e2]: - heading "Example Domain" [level=1] [ref=e3] - paragraph [ref=e4]: This domain is for use in documentation examples... - paragraph [ref=e5]: - link "Learn more" [ref=e6] [cursor=pointer]: - /url: https://iana.org/domains/example 各要素に ref=e2 、 ref=e6 みたいな参照IDが振られていますよね。これがCLIの核心機能です。 ref で操作する snapshotで取得した参照IDを使って、要素を直接操作できます。 # "Learn more" リンク(ref=e6)をクリック npx @playwright/cli click e6 ### Ran Playwright code await page.getByRole('link', { name: 'Learn more' }).click(); ### Page - Page URL: https://www.iana.org/help/example-domains - Page Title: Example Domains コマンド1発でページが遷移しました。 snapshot → ref で操作、この繰り返しが CLI の基本です。 スクリーンショットを撮る スクリーンショットも1コマンドで撮れます。 npx @playwright/cli screenshot --filename=screenshot.png ひと通り操作が終わったら close でセッション終了です。 snapshot で構造を見て、 ref で操作して、 screenshot で結果を確認。 この3ステップが CLI の基本ワークフロー です。 Claude Code の Skill として使う セットアップの手順2で npx @playwright/cli install --skills を実行していれば、もう準備完了です。 .claude/skills/playwright-cli/ にSKILL.mdとreferencesディレクトリ(7ファイル)が自動配置されています。 これで「スクショ撮って」「Webページ開いて」みたいな指示をClaude Codeに出すだけで、CLIを呼んでくれるようになります。 前回の記事 で「CLI + Skills」パターンを提唱しましたが、あのとき自作していたものが 公式サポートになった 形ですね。Microsoft が Skill を用意してくれるので、メンテナンスも楽になりました。 まとめ 今回は、Playwrightの3つの顔を整理したうえで、CLI の最小セットアップ手順を紹介しました。 Playwrightは「テストランナー」「MCP」「CLI」の3つがあって全部別パッケージ。AIコーディングエージェントで使うなら CLI が一番お手軽です。セットアップは playwright install chrome → install --skills → open のコマンド3つだけ。 ハマりポイントは chromium じゃなくて chrome が必要なところ。テストランナーに慣れてる人ほど引っかかるので、ここだけ覚えておけば大丈夫です。あと install --skills で Claude Code 用の SKILL.md が公式から自動配置されるのも地味にありがたい。 MCPとCLIのトークン消費比較や、ユースケース別の使い分けについては別記事で詳しく書く予定です。コメント欄で気軽に話しかけてください。 参考リンク GitHub – microsoft/playwright-cli @playwright/cli – npm Playwright MCP GitHub 前回記事: Playwright MCPで始めるブラウザ自動化|環境別セットアップガイド 前回記事: Claude Code MCP が遅い・重い問題、CLI + Skills で解決 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Playwright MCP より手軽な公式 CLI — コマンド3つでセットアップ first appeared on SIOS Tech Lab .
アバター
ども!気になった単語や技術をAIにぶち込み続けている龍ちゃんです。 Xを眺めていると、知らない単語が流れてきますよね。「ふーん、なんか面白そう」で終わって、次の瞬間には忘れている。みんなもそういう経験ないですか? 自分もずっとそうで、Draw.ioのMCPが1ヶ月ほど「気になるリスト」に眠ったままでした。 ある日、Claude Codeで検索のSKILLを作ったんです。そしたら効率が爆上がりです。やったことは「気になった単語をぶち込んだ」だけなのに。 実際にやっている手法の紹介です。 Claude Code版とGemini版、どちらもすぐ試せるようにプロンプト付きで載せておきますね。 最初の一歩は小さくていい 「ちゃんと調べなきゃ」と思うから腰が重くなるんですよね。ブラウザ開いて、検索して、記事をいくつか読んで、要点をまとめて……って考えるだけで面倒ですよね。 でもAIにぶち込んでおけば、あとは出てきた結果を読むだけです。概要が分かれば「もっと知りたい」か「今は要らない」かの判断フェーズに代わります。 完璧な調査じゃなくていい。最初の一歩が踏めれば、あとは手を動かすだけです。 逆に一歩だけ踏み出しておくと、「動けばいい」になるので気になっちゃって試さずにいられないんです! Claude Code版 — 1ファイルで試せる /research Claude Codeを使っている人なら、 .claude/commands/research.md にファイルを1つ置くだけで試せます。 今だと SKILL として設定したほうが良いですね。commandsで紹介しているブログなので、コマンドで紹介しておきます。(今から作るならSKILL一択ですよ。) 以下が簡易版のコマンドファイルです。これを置けば /research [気になったこと] で即実行できます。 # /research コマンド(簡易版) トピック: $ARGUMENTS ## 調査手順 1. 以下の観点でWeb検索を実行してください - 公式ドキュメント・リファレンス - 最新の技術ブログ・記事(6ヶ月以内を優先) - GitHub リポジトリ・Issues 2. 調査結果を以下のフォーマットで `docs/research/` に保存してください ## 出力フォーマット ### 概要 (3〜5行で要点をまとめる) ### 詳細 (調査結果を構造化して記載) ### 情報源 (参照したURLをリストアップ) ### 所感 (調査から得られた気づき・次のアクション候補) とりあえず、出力される成果物を確認すると指定した範囲から検索した何かしらのドキュメントが出力されます。指定すれば、公式ドキュメントも呼んでくれますし、比較表も出力してくれます。 「Playwrightのテスト設計パターン」でも「OpenTelemetryの導入ステップ」でも、とりあえずぶち込めば最初の地図が手に入るんですよね。 もっとガッツリ使いたい人向けに、調査深度の選択やソース評価(CRAAP Test)を組み込んだ版や、バックグラウンドで並列実行する構成も作っています。 2026-01-19 Claude Codeの調査品質がバラバラ?:/researchで解決する方法 2026-02-05 Claude Code /research の待ち時間をゼロに:Skill × サブエージェント構成 Gemini版 — ブラウザで今すぐ試せるリサーチGem Claude Codeを使っていなくても、ブラウザだけで同じことができます。 Google Workspaceを契約されているから、Geminiは使えるけど…使ってないって方いるんじゃないでしょうか? Geminiの リサーチGem は、よく使う調査の指示を保存しておいて、選ぶだけで実行できるんですよね。 Gemの作成画面 で以下のカスタム指示を貼り付ければ、リサーチ専用のGemが完成します。 あなたは最新情報を専門に扱うリサーチアシスタントです。 実行前に情報の深度を確認してください。 - クイック:概要 - ディープ:深掘検索 ディープの場合であれば、一回の調査で終了せずにレポート内で重要な発見・不足している情報などを判断して再帰的に検索をお願いします。 検索に関しては、本日から6カ月以内の情報ソースもしくは、公式リファレンスの情報をGoogle検索によって取得して回答を生成してください。 回答の最後には、参照したソースのリンクをリストアップしてください。 Xで見かけた単語をそのままコピペして投げると、1分くらいで概要・関連する文脈・自分に関係あるかの判断材料が出てきます。「MCPって何?」「DORAメトリクスって聞いたけど」みたいなやつを、ひとまず処理するのにちょうどいいです。僕は出先で気になったことはこっちで調べますね。 あとから調べた資料をClaude Codeで再度精査しています。 Geminiに検索させるコツやGemの作り方はこちらで詳しく書いています。 2026-01-28 Geminiに検索させるプロンプト術|リサーチGemの作り方 実践 — 「ぶち込んだ」先にあったもの 最初の一歩さえ踏めれば、そこから転がっていくんですよね。自分の実例を3つ紹介します。 Draw.ioのMCP → ブログ2本 冒頭でも触れたDraw.ioのMCPの話をもう少し詳しく書きますね。 Xで「Draw.ioが 公式のMCPサーバー を出した」というポストを見かけたんですよね。「へー、面白そう」とは思ったものの、業務に直結しないので「時間できたら触ろう」リストに入れてそのまま1ヶ月。 ある日、別の調査をしているときに思い出して /research Draw.io MCP Server とぶち込んでみました。5分くらいで「Mermaid・XML・CSVの3フォーマットに対応していて、Apache 2.0ライセンスで公開されている」という概要と、公式リポジトリのリンクが返ってきたんですよね。 概要を読んだら「あ、これ仕様書×AI の文脈で使えるかも」となって、実際にセットアップして検証してみました。結果、MCP検証記事とSkill化記事の2本に育ちましたね。放置していた1ヶ月が嘘みたいです。実際に1時間で検証までスタートして、ブログにスムーズに移行しましたね。 Draw.io公式MCPでできること・できないこと Draw.io公式Skillで設計図をGitHub管理 専門外の知識をAIに持たせる → 「一般論」が消えた AIに「レビューして」と投げると、一般論しか返ってこないことってありませんか?あれ、AIに観点がないから起きるんですよね。/research で専門外の知識を調査して、構造化してSkillやAgentに注入してみたら、出力が変わりました。どの分野でも同じパターンが使えます。 調査→構造化→注入の設計パターン 要求分析・プレゼン心理学 → 業務の専門外をカバー 最近は「要求分析の手法」「仕様書作成のベストプラクティス」「プレゼンで使える心理学」あたりも調査してSkill化しています。業務に関連しているけど自分の専門外、というやつですね。一度調べてしまえば、次からAIがその知識を持った状態で動いてくれます。 ここはまとまったらブログにします。 まとめ — 調査の質より「動けること」 AIリサーチの価値って、「調査の質が上がる」ことじゃないと思っていて。 「気になった瞬間に動ける自分になれる」 ことなんですよね。 最初の一歩を踏めれば、深掘りするかやめるかを自分で判断できます。でも放置しているかぎりは判断すらできないので、放置が一番もったいないんですよね。 ぶち込むだけでいい。さぼり癖がある人・ずぼらな人ほど、AIリサーチとの相性がいいです。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 「あとで調べよう」が一生来ない人へ|AIにぶち込むだけの処方箋 first appeared on SIOS Tech Lab .
アバター
ども! HTML図解の自動生成シリーズ を書いている龍ちゃんです。 AIコーディングでデザインの修正依頼を出すとき、「なんか違う」を言葉にするのに苦労していませんか? 僕もずっとそうでした。出来上がった図解を 自分のイメージ通りに仕上げたい んだけど、テキストだけだと伝わらない。 画像を一緒に渡すだけで、これが解決しました 。マルチモーダルな入力で言語化できなかった感覚がそのままAIに伝わって、総当たりの修正ループがディスカッションに変わります。 今回の内容です。 レビューは人間の感覚が起点。でも言葉にするとあいまいになる 画像を加えると自分のイメージがそのまま伝わる実例 イメージが共有されるとAIがディスカッション相手になる 順番に見ていきます。 前提:HTMLで図解を作っている まず前提として、僕はブログ用の図解をHTML + Tailwind CSSで作っています。AIに指示を出すとHTMLコードが生成されて、それをブラウザでレンダリングしてPNGに変換するという流れですね。作り方の詳細はこのシリーズで紹介しています。 Claude Code SkillでHTML図解を自動生成する 図解作成、AIに丸投げしたら「たまに自分より上手い」件 MermaidをTailwindで設計書品質に仕上げる ここで厄介なのが、 ソースがコードなので、見た目がどうなっているか書いた時点ではわからない ということです。 gap-6 や text-sm といったクラス名を見ても、実際にレンダリングしたときのバランスや印象はコードからは読み取れないんですよね。 だからPNGに変換して初めて「あ、なんか違うな」と気づくし、その修正をAIに伝えるときにも同じギャップが生まれる。AIもコードは読めるけど、レンダリング結果の「見た目の印象」まではコードだけじゃ判断できないんです。 これがこの記事の出発点です。 レビューの起点は人間の感覚。でも言葉はあいまい 図解を生成した後、デザインを自分のイメージに近づける作業が必ず発生しますよね。その出発点は「なんか詰まってる」「バランス悪い」という視覚的な違和感です。この感覚は正しくて、人間の目が最終的な品質の判断基準なんですよね。 でも、その感覚を言葉にした瞬間に問題が起きます。解釈の幅が生まれてしまう。 「スカスカ」→ 余白の話?情報量の話?文字サイズの話? 「詰まりすぎ」→ 行間?padding?要素数? 人間同士でも言葉のズレは起きるのに、AIにはなおさら伝わりにくいんですよね。 試しに、テキストだけでこういう修正依頼を出してみました。 右側が詰まりすぎて左側がスカスカに見える。バランスを整えてほしい AIの応答はこうでした。「スカスカ」を余白の問題と解釈して、幅・行間・gap の3箇所を同時に変更してきたんです。 でも僕が感じていたのは「のっぺりして見える」という感覚だったんですよね。言葉に変換した時点で、別の問題として伝わってしまった。 感覚が言語化を経由する瞬間に、AIの解釈が複数の方向に分岐してしまうんです。これがテキストだけでデザインレビューを進めるときの根本的な難しさですね。 画像を加えると自分のイメージがそのまま伝わる 同じ図解に、同じ修正依頼をした。でも今度は 画像も一緒に渡しました 。 するとAIの応答がガラッと変わったんですよね。 テキストだけのときは「スカスカ」という言葉から総当たりで修正してきたのに、画像ありのときはこう返してきました。 「右カラムが全部 text-sm(14px) で、のっぺりした灰色テキストの塊に見えます。レイアウトはそのまま、文字サイズの比率だけ調整するのはどうでしょう?」 これです。僕が感じていた「のっぺりして見える」という感覚の本質を、AIが画像から直接読み取ってくれたんですよね。言語化を経由していないから、あいまいさが入り込まなかった。修正箇所も1箇所に絞られました。 実際にこの記事用の図解でも、まさに同じことが起きています。さっきセクション1で見せた「言葉のあいまいさ」の図、あれは修正後なんですよね。修正前はこうでした。 「左寄り」「右に囲みがない」「下半分が空白」という問題があるんですが、僕は「左側に全体寄っているかな」「枠線がないのが気になる」くらいの感覚だったんです。AIは画像を見て、こう返してきました。 左のカードが左端に寄っていて、右側に大きな余白がある。 justify-center がない 左と中央はカードで囲まれているのに、右は3枚のカードが浮いている。グループ感がない 情報の広がり(1→1→3分岐)に対して高さが対応していない 僕が言語化しきれなかった「下半分が空白」という問題まで、AIが画像から補完してくれたんですよね。 なぜこんなに違いが出るのか、整理するとこうなります。 観点 コード+言葉 コード+言葉+画像 自分のイメージの伝わり方 言葉に変換 → あいまいになる 画像で直接共有 → そのまま伝わる AIの問題特定 あいまいな言葉から推測 → 総当たり 画像から視覚的に特定 → 的を絞れる 修正の方向性 複数箇所を同時に微調整 原因を絞って1箇所を修正 画像は言語化を経由しないから、あいまいさが入り込む余地がないんですよね。「自分が見ているこれ」をそのままAIと共有できる。 イメージが共有されるとディスカッションになる ここが一番面白い変化で。自分のイメージがAIに伝わっている状態になると、やり取りの性質が変わります。 テキストだけのとき: 人間が「右が詰まりすぎ」と指示 → AIが推測で3箇所修正 → 「なんか違う」→ 再指示 → …という一方通行のループになりがちです。 画像ありのとき: AIが画像から問題を特定 → 「ここがこうなってるからこう直す」と提案 → 人間が判断する、という双方向のやり取りになります。 実際にこういうやり取りがありました。上の図(テキストのみ vs 画像あり)を作ったときの話です。修正前は線が薄くて見えないし、文字は小さいしで、全体的にぼんやりした印象でした。 その修正前の画像を見せながら「全体的に色味が薄いのと、テキストサイズをもう少し上げたほうが読みやすいと思う。他に問題点があれば画像から教えて」と伝えたところ、AIはこう返してきました。 左パネルのボーダー(slate-200)がほぼ見えない。右もblue-300で弱い カード内のテキストが text-sm(14px)で、PNGとして見ると読みづらい 上部のラベルと最初のカードの間のスペースがありすぎる アイコンが text-2xl(24px)で誰が誰か識別しづらい 左(NG)側がもっとネガティブに見えていい。右と構造が似すぎている 左のラベルが地味で、右の青ラベルと比べて存在感が薄い 僕は「全体的に色が薄い」という感覚しか持っていなかったのに、AIが画像から6つの具体的な問題を特定してくれたんですよね。人間は「そう、それ!」と判断するだけでいい状態になっていました。 修正内容はこうなりました。 対象 修正前 修正後 ボーダー色 slate-200, blue-300 slate-400, blue-400 ラベル 薄い背景色ピル 白抜き文字の濃色ピル(slate-600, blue-600) テキストサイズ text-sm(14px) text-base(16px) アイコンサイズ text-2xl(24px) text-3xl(30px) 左の末尾カード 通常ボーダー 破線ボーダー(「まだ続く」感) AIの提案カード(右) bg-white bg-blue-100(強調) ループ/ディスカッション表示 テキストのみ 背景色付きピル(red-100, blue-200) イメージが共有されているから、修正後のPNGを再生成してまた渡せば、次のサイクルでもズレない。理想に向かって改善が積み上がっていくんですよね。AIが「指示待ちの修正マシン」から「一緒にデザインを詰めるディスカッション相手」に変わった感覚がありました。 Claude Code・Codex CLI・Gemini CLIでの画像の渡し方 ソースから見た目が生成されるものなら、どのツールにも使えます。HTMLに限らず、SVG、CSSだけのスタイル調整、Reactコンポーネントなんかも同じ考え方で動きます。 手順はシンプルです。 ソース(HTML / CSSなど)を作る PNGに変換する(幅800px以上推奨。それ以下だとAIが細かい文字を読み取れないことがある) ソース+修正の言葉+画像をまとめてAIに渡す 僕が使っているのは Claude Code で、チャット入力欄に画像をドラッグ&ドロップするか、画像パスを貼り付けるだけです。 Codex CLI や Gemini CLI もマルチモーダル入力に対応しているので、同じ考え方で使えるはずです。渡し方の詳細は各ツールの公式ドキュメントを確認してみてください。 一点だけ注意があって、「どの画像のどこが問題か」を言葉で明示しておくと精度が上がります。「右カラムの文字が読みづらい」くらい絞り込んでおくのがコツですね。 あと補足なんですが、この記事では「なんか違う」という感覚ベースのレビューを中心に紹介しました。でも、感覚をさらっと伝えるだけが正解ってわけじゃないんですよね。「左カラムのpadding-xを16pxから24pxに広げて、右カラムのtext-smをtext-baseに変えてほしい」みたいに、詳細にプロンプトを書くのも当然効果があります。画像+感覚ベースの指示と、画像+詳細な指示、どっちも使えるのがマルチモーダルの強みです。自分の中で問題が明確なら詳細に書けばいいし、「なんか違うけど何が違うか分からない」ときは感覚のまま画像と一緒に投げればいい。場面によって使い分けてみてください。 まとめ デザインが成果物のとき、レビューの判断基準は人間の感覚です。でもその感覚を言葉にすると、どうしてもあいまいさが入り込む。 画像なら「自分が見ているもの」をそのままAIと共有できて、言語化のロスがなくなります。AIが問題を自分で特定して提案してくれる状態になると、やり取りが一方通行の指示ループから双方向のディスカッションに変わるんですよね。 次回はMarp CLIでスライドをPNG化して、専門AIレビューを適用する方法を紹介します。 ほなまた〜 関連ブログ 今回の記事はHTML図解シリーズの一部です。図解の作り方から気になる方はこちらからどうぞ。 Claude Code SkillでHTML図解を自動生成する — シリーズの出発点。Skillを使ってHTML + Tailwind CSSの図解をコマンド一発で作る仕組みを紹介しています 図解作成、AIに丸投げしたら「たまに自分より上手い」件 — 実際に丸投げで作った図解の実例集。AIに任せたほうがいいケースと、人間が手を入れるべきケースの見極め方も書いています MermaidをTailwindで設計書品質に仕上げる — Mermaidのフロー図やシーケンス図を、Tailwindで見た目を整えて設計書にそのまま使えるレベルにする方法です PlantUMLをTailwindで設計書品質に仕上げる — PlantUML版。クラス図やアクティビティ図をTailwindで仕上げるパターンを紹介しています ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIに「なんか違う」が伝わらない?画像を渡して指示ループから抜け出す first appeared on SIOS Tech Lab .
アバター
対象読者 copilot-instructions.md や SKILL.md をすでに書いていて、ある程度動かせている人 「毎回同じ指示を繰り返してるな」と薄々気づいている人 Instructions / SKILL をもっと賢くしたいが、何をどう変えればいいか分からない人 Agent Skills の基本(ディレクトリ構造、SKILL.md の書き方)は「 【2026年版】Agent Skills 入門 」を、設定ファイルの全体像は「 GitHub Copilot の設定ファイル5種 」を参照してください。 チャットの履歴にすべて答えがある ども!龍ちゃんです。 僕は普段、Claude Code と GitHub Copilot を用途に応じて使い分けていて、セッションの長さや制限状況によって切り替える感じで運用しています。両方使い込んでいくと気づくことがあって、 セッションをまたいで同じ指示を繰り返してる自分 に気づくんですよね。 「このプロジェクトでは uv を使ってるので〜」とか、「コミットメッセージは日本語で〜」とか。モデルが変わっても関係なく、自分の癖として毎回同じことを最初に説明してしまっています。 んで気づいたときに設定ファイルだったり、SKILL化したりしています。 ただ忙しいときって、時間をとって分析しようとはなかなかならないんですよね。日常の開発を止めてまでやることでもないし、「まあこんなもんかな」で流してしまう。 でも、そういう「こんなもんかな」が積み重なると、毎回毎回同じ摩擦が続くことになります。一発でそのループを断ち切りたい、というのが今回の話の出発点です。 この記事では、チャット履歴から改善点を見つけて copilot-instructions.md / SKILL.md に反映する具体的なフローを紹介していきますね。 今回の内容です。 チャット履歴から改善点を見つける3つの方法 方法ごとの使い分けと、それぞれの特性 見つけた改善点を Instructions と SKILL.md にフィードバックするフロー チャット履歴から改善点を見つける3つの方法 3つの方法を紹介します。「UI で見返す」「セッション内でポストモーテムする」「エクスポートして横断分析する」と、だんだん深掘りが増えていく構成です。どれか1つだけやるというより、状況に応じて使い分けるイメージで見てもらえると。 方法1: VS Code の UI でチャット履歴を見返す 一番シンプルな方法です。Copilot の UI でセッションを行き来して、「自分が何を言ってたか」を見返すだけですね。 GitHub Copilot はチャット履歴が UI 上で見えるので、過去のセッションに遡りやすいのが利点です。「先週のあのやりとり」みたいな感じで、セッション間を移動しながら確認できます。どういう変更が加えられたか、自分がどう指示したかを単純に追いたいときはこれが一番手軽です。 まずこれだけでもやる価値があるんですが、「セッション横断でパターンを見つける」という目的には正直向いてないですね。次の方法がそこをカバーします。 方法2: セッション内で lessons ファイルにまとめる(ポストモーテム) 僕が Claude Code で一番使っている方法で、GitHub Copilot でも同じようにできます。 やり方はシンプルで、 セッション中に意図と違う挙動が起きて、修正が終わったタイミングで振り返る ことです。「なぜこう解釈したのか」「自分はこう意図したけど伝わらなかったのはなぜか」をそのセッション内で詰めていきます。 Copilot に投げかけるのはこういう感じです: この問題をまとめて lessons に入れて なんでこうなった? 次どうする? 正直わりとパワハラ気味に詰める感じなんですが、この詰めが大事で。原因と対策をセットで残してもらうことが目的です。 lessons/2026-03-04.md のようなファイルが生成されて、再発防止策のドキュメントが溜まっていきます。 AI コーディングエージェントを使ったポストモーテム(ふりかえり) 、というイメージで使っています。(んでもちろん自分の入力が悪いと顧みるときもあります…) この方法のカバー範囲は「そのセッション内で気づいた問題」なんですよね。セッションをまたいだパターン、たとえば「3週間ずっと同じ前提を最初に説明してた」みたいなことは気づけません。それが次の方法です。 方法3: チャットエクスポートでセッション間の横断分析 ここが記事の本題です。GitHub Copilot ならではの手法なんですよね。 VS Code には「 Chat: Export Chat… 」という機能があって、セッションを JSON データとしてダウンロードできます。この JSON には、自分がどう指示して、AI がどういう挙動をしたかまでセットで入っているんですよね。 エクスポートの手順はこうです。 VS Code で Copilot Chat パネルを開く コマンドパレット( Ctrl+Shift+P / Cmd+Shift+P )を開く 「 Chat: Export Chat… 」を選択 保存先を指定 → JSON ファイルが出力される 操作自体はこれだけです。ただ、出力される JSON はそのまま読める量じゃないんですよね。6ターンの会話で 20,000行超になったりします。この変換の話はあとで出てきます。 1週間分まとめてエクスポートして、全セッションを横断的に分析すると、 セッションをまたいで共通するパターン が見えてきます。そのパターンが、Instructions や SKILL 化の目処になっていく感じです。 ここで少し前振りを入れておくと、Claude Code も拡張機能を使えばチャット履歴は見れます。ただ、GitHub Copilot はいろんなモデルを切り替えながら使えるので、 モデルごとの差分が出るのが面白い んですよね。同じ指示をしても、claude-sonnet-4.5 と GPT-5-mini では挙動が違ったり、Thinking の詳細度が変わったりします。その差分もエクスポートデータに残るので、「このモデルはこういう指示の方が伝わりやすい」みたいな知見が蓄積されていきます。 エクスポートデータを使った横断分析の実践 ここからは方法3の具体的な分析フローです。 まず、エクスポートした JSON をそのまま分析しようとすると、6ターンの会話が 20,000行超の JSON になっているので人間が読むのは無理なんですよね。前の記事で紹介した copilot-chat-converter を使って Markdown に変換すると一気に読みやすくなります(20,000行 → 720行くらいに)。 このあたりの変換手順は前の記事「 Copilot Chat の会話履歴を Markdown で保存するパイプラインを作った話 」で詳しく書いているので、そちらも見てみてください。 変換した Markdown を1週間分並べて、AI に投げます: この1週間のチャット記録を分析して。 セッションをまたいで繰り返し出てくる指示パターンがあれば抽出して。 対象:自分が Copilot に毎回同じ前提を説明している箇所、手動で繰り返している手順。 自分で分析しても構いませんが、AI に任せた方が速いですね。人間が見るだけでは「なんとなく同じことを言ってるな」で終わってしまうところを、パターンとして整理してもらえます。 僕が実際に分析して見つかったパターンの話をすると、一番驚いたのが SKILL の description 問題でした。 あるスキルが全然発火しないので何度も「スキル使って」と手動で言い直していたんですよね。この繰り返しがログに残っていたので、「なんでこのスキルだけ発火しないんだろう」と原因を掘り下げたら、 description: | (YAML の複数行記法)が原因だと分かって。複数行記法だと各行が別の属性として誤解釈されて、description が空になってしまうという問題でした。 「 Claude CodeからGitHub Copilotへ移植したらAgent Skillsが動かない?原因と解決策 」で検証結果と回避策を詳しく書いているんですが、ざっくり言うと description: "..." の単一行に書き直すだけで解決しました。この問題、横断分析しなかったら「なんか発火しにくいな」で終わっていたと思います。ログがあって、繰り返しパターンとして可視化されたから気づけた体験でした。 他にも「プロジェクトで使用できるCLIツール」という前提を毎回説明していたことも見つかりました。これも横断で見ないと気づきにくいんですよね。セッション単体では「毎回説明するのが当たり前」になっていて、問題と認識していなかったので。 方法2と方法3の使い分けを整理するとこうです。 方法2(lessons ポストモーテム) 方法3(エクスポート横断分析) 対象 セッション内の問題 セッション間の繰り返しパターン タイミング 問題が起きた直後 定期的(週1など) 主な発見 「なぜこの挙動が起きたか」 「毎回同じ摩擦が起きている」 見つけた改善点を Instructions と SKILL.md にフィードバックする 方法1〜3で見つけたパターンを、どう Instructions / SKILL.md に落とし込むかです。 SKILL テスト → ログ → フィードバックのサイクル 僕が一番使っているのが、 SKILL 構築のテスト結果をログとして残してフィードバックするサイクル です。 SKILL を作ったら、まずテストします。そのとき「うまく発火しない」「意図と違う挙動が出る」ことは結構あるんですよね。その結果を lessons ファイルに残して、ログを見ながら SKILL.md の description や手順を修正して、再テストして改善を確認する、というサイクルを回します。 さっきの description の | 問題がまさにこのサイクルの産物で、テストで発火しないことを確認 → ログに残す → 原因を特定( description: | の複数行記法) → 単一行 description: "..." に修正 → 再テストで発火を確認、という流れで解決しました。 このサイクルが回り始めると SKILL の精度がどんどん上がっていくんですよね。最初は「なんか動く」レベルでも、繰り返すうちに「言えば確実に動く」に変わっていきます。 SKILL.md の書き方の基本は「 【2026年版】Agent Skills 入門 」に詳しくまとめているので、まだ読んでいない方はそちらから。 繰り返し指示 → copilot-instructions.md に集約 SKILL テストの過程でも、「毎回同じ前提を説明してる」パターンは見つかります。モデルに関係なく同じ指示を繰り返していたなら、それは copilot-instructions.md に1行追加するだけで以降の暗黙の前提にできます。 さっきの「使用できるCLIツールは~~」の例なら、instructions に書いてしまえばセッションの最初に説明しなくて済むようになります。 copilot-instructions.md と AGENTS.md の使い分け(どちらに書くべきか、スコープの違い)については「 copilot-instructions.md と AGENTS.md の使い分け 」で整理しているので参考にしてみてください。 手動ワークフロー → SKILL.md 化 複数のセッションで同じ手順を説明していたら、SKILL 化の候補です。 前の記事で紹介した copilot-chat-converter (JSON → Markdown 変換ツール)も、最初は「このコマンドを実行して、出力はここに保存して」と毎回説明していたんですよね。横断分析でその繰り返しに気づいて、SKILL.md に落とし込んで「チャット変換して」の1行で動くようにしました。 また、チャット内で便利なコードを作ってもらってそこからそのやっていることをエクスポートとしてCLIツールに昇華したりSKILLとしてまとめたりしています。 「手順の説明を何回かやったな SKILL にするか~」くらいの感覚でやってます。 まとめ:「3回繰り返したら仕組みにする」 ここまでの流れをまとめると、こういうサイクルです。 シンプルなルールとして意識しているのは、 「同じ指示を3回したら Instructions に書く」「手順を3回説明したら SKILL にする」 ということですね。最初から完璧を目指さなくていいんですよ。気づいたときに1つ直す、の積み重ねでどんどん使いやすくなっていく感じです。(この辺はCopilotを育成ゲームだと思うと楽しいです) このサイクルを回していくと、副次的な効果も出てきます。自分の Copilot 環境が育つだけじゃなくて、 チャット履歴を見返す習慣自体が「自分がどうコードを書いてるか」のメタ認知になる んですよね。「自分はこういう前提をよく省略するな」とか「このパターンの説明が苦手だな」みたいなことが見えてくる。AI ツールの設定を改善しているつもりが、実は自分のコミュニケーションの癖を棚卸ししている感じです。 チームへの展開もできます。後輩の Copilot の使い方をレビューするのにこのフローは結構使えて、エクスポートしてもらって横断分析すれば「ここ毎回手動でやってるから SKILL にしたら?」みたいな具体的なアドバイスができるんですよね。コードレビューはするのに、AI との対話のレビューはしてない、というチームは多いんじゃないかと。自分の tips を Markdown で共有して、チームの copilot-instructions.md に反映する流れを作ると、チーム全体の Copilot 活用レベルが底上げされていきます。 チャット履歴を分析するにあたって、JSON のままでは読めないので Markdown に変換するツールを前の記事「 Copilot Chat の会話履歴を Markdown で保存するパイプラインを作った話 」で紹介しています。横断分析をやってみたい方は、まずエクスポートして変換するところから始めてみてください。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot チャット履歴から copilot-instructions.md と SKILL.md を育てる方法 first appeared on SIOS Tech Lab .
アバター
対象読者 Copilot の Agent Mode を日常的に使っている人 デバッグで「とりあえずエラーを貼って聞く」止まりの人 Copilot が的外れなコードを読みに行って消耗した経験がある人 Copilot の設定ファイルの全体像は「 GitHub Copilot の設定ファイル5種 」を参照してください。 デバッグ調査をチャットから始めてないか ども!龍ちゃんです。 バグ報告が来て、いつもみたいに Agent Mode のチャットに「このバグ調べて」と投げました。Copilot がファイルを次々読み始めて、待つ。関係なさそうなファイルまで読んでる。返ってきた分析は的外れだった。 「違う、そこじゃない」と追加で指示を出す。また読みに行く。また待つ。3ターン経っても全然進まない。結局自分でログ読んだ方が速かったんじゃないかと。精度を上げるためにPlanエージェントを使う手もあるんですけども、断続感がすごくて。 この体験、覚えがある人は多いんじゃないかなと思います。 なぜこうなるかというと、Plan エージェントがコードベースを広範囲に探索するからです。関係ないファイルまで読んでコンテキストウィンドウが埋まって、後半さらに精度が落ちる。「情報が足りないから広く探す」→「無駄な情報でコンテキストが埋まる」→「精度が落ちる」の悪循環です。 これ、Copilot が悪いんじゃなくて、渡し方の問題 なんですよね。 じゃあどうするか。 Copilot に投げる前に Issue.md を1枚書く 。問題・仮説・関連パスを整理してから渡すだけで、探索範囲が絞られて初手から的確になります。 この記事では、Issue.md を起点にした Copilot デバッグワークフローを実践ベースで紹介していきます。 今回の内容です。 Issue.md が効く2つの理由(思考の外部化とファイル起点のアクション) Issue.md を起点にしたデバッグの4ステップ 仮想シナリオで4ステップを走る実践例 人間がやること vs Copilot に任せることの役割分担 Issue.md でデバッグが変わる理由とフローの全体像 Issue.md とは バグ発生時に、プロジェクトルートに一時的な調査ファイル( Issue.md )を作ります。 Copilot に渡す前に、人間が先にコンテキストを整理する のがポイントです。 書くのは3つだけです。 発生している問題 (何が起きているか、再現手順) 仮説 (自分なりの原因予想) 関係するファイルのパス (Copilot の探索範囲を絞る) テンプレートはこんな感じです。 # Issue: [バグの概要] ## 発生している問題 - 現象:[何が起きているか] - 再現手順:[どうやったら起きるか] ## 仮説 - [自分なりの原因予想。わからなければ「わからないが○○が怪しい」でもOK] ## 関係するファイル - `src/components/XXX.tsx` - `src/hooks/useXXX.ts` 3行・パス2〜3個で十分です。30秒で書ける量がちょうどいいです。 もちろん、詳しければ詳しいほど良いです! 仮説が浮かばないときは「わからないが、ここが怪しい」だけでも書いてください。完璧な仮説を書くのが目的じゃなくて、 書くことで頭が整理されるのが本質 です。「原因は○○かもしれない」と書こうとする過程で「いや、そもそも再現条件って何だっけ」みたいに思考が動き出す。それだけで Copilot への指示の質が変わります。 なぜこれが効くか 効く理由は2つあります。 理由1: 思考の外部化 チャットに「このバグ何?」と投げるのは、思考をスキップしてます。Issue.md を書く過程で「何が起きてるのか」「どこが怪しいのか」を自分の頭で一度整理する。この整理があるだけで、Copilot への指示の質が変わります。 「書くこと」が目的であって、「完璧なドキュメントを作ること」が目的じゃないです。 最終的に Issue.md に原因と対策までまとまるので、レポートを別途作る必要がない。振り返りもしやすいです。 理由2: ファイル起点で Copilot のアクションが変わる チャットで口頭説明するのと、MD ファイルとして渡すのでは Copilot の挙動が違います。 チャットで「このバグ調べて」と言うと、Plan エージェントは「まずコードベースを探索しよう」から始めます。ファイルとして渡すと「このドキュメントに書かれたパスを読もう」から始まる。この初手の違いがでかいんですよね。 ファイルに関連パスが書いてあると、Copilot はそのパスから読み始める。探索範囲が絞られるので、コンテキストウィンドウを無駄に消費しない。後半まで精度が持続します。 このワークフロー自体は汎用的で、Claude Code でも同じパターンは使えます。ただ、Copilot の Agent Mode は Plan エージェントがファイル探索の範囲を広く取るため、スコープを絞る効果が特に大きいです。もちろんPlanエージェントを使わないパターンでも読み込ませることで精度が継続します。 Before/After: チャットで聞く vs Issue.md を渡す 具体的にどう変わるか、比較してみます。 Before(チャットで聞く): あなた: APIリクエストが多重に走ってるバグを調べて → Copilot がコードベース全体を探索 → 関係ないファイルまで読む → コンテキスト圧迫 → 的外れな分析が返ってくる After(Issue.md を渡す): あなた: @Issue.md このドキュメントを元に調査して → Copilot が Issue.md に書かれた関連パスから読み始める → 初手から的を絞った分析が返ってくる 同じバグの調査なのに、初手の精度が全然違います。この差は Issue.md があるかないかだけで生まれます。 デバッグフローの全体像 Issue.md を起点にしたデバッグは、4ステップで進めます。 ステップ やること 担当 1. Issue.md を書く 問題・仮説・関連パスを整理 人間 2. Copilot に調査を依頼 Issue.md ごと渡して的を絞った調査 Copilot 3. 仮説→検証→フィードバック 人間が検証、Copilot が分析、を繰り返す 人間 + Copilot 4. 「なぜ直ったか」を確認 修正後に原因を言語化して次に備える 人間 + Copilot 次のセクションで、このフローを仮想シナリオで具体的に走らせてみます。 実践:仮想シナリオで4ステップを走る ここからは、実際のバグ調査セッション(33ターン)を元に、仮想シナリオに置き換えて紹介していきます。ワークフローのパターンを伝えることが目的なので、プロジェクトの詳細は出しません。 仮想シナリオ: API リクエストの多重発火 ダッシュボード画面を開くと、同じ API リクエストが3〜4回飛んでいる。 DevTools のネットワークタブ で確認すると、同じエンドポイントへの GET リクエストが重複している。ユーザーから「画面の表示が遅い」と報告があった。 フロントエンドあるあるですね。 useEffect の依存配列 ミスで同じ API が複数回飛ぶやつ。 ステップ1: Issue.md を書く(問題整理) バグ報告を受けたら、まず Issue.md を書きます。 # Issue: ダッシュボードの API 多重リクエスト ## 発生している問題 - ダッシュボード画面を開くと同じ API が3〜4回飛ぶ - DevTools のネットワークタブで GET /api/dashboard が重複しているのを確認 - ユーザーから「表示が遅い」と報告あり ## 仮説 - useEffect の依存配列に不要な値が入っていて再実行されているのでは - または、コンポーネントのマウント/アンマウントが繰り返されている可能性 ## 関係するファイル - `src/pages/Dashboard.tsx` - `src/hooks/useDashboardData.ts` - `src/components/DashboardWidgets.tsx` 書くのに30秒もかかりません。ポイントは、 Copilot に投げる前に自分の頭で一度整理すること です。「DevTools で見た」「3〜4回飛んでる」「依存配列かマウントが怪しい」ーーこれだけ書いておくと、Copilot の初手が全然違ってきます。 ちなみにバックエンドの場合も同じです。関連パスがサービス層や DB アクセス層になるだけで、Issue.md のフォーマットはそのまま使えます。 裏付けとして、ログファイルなどを与えると仮説の質や検討の精度がぐっと上がります。 ステップ2: Copilot に調査を依頼する Issue.md ごと Copilot に渡します。 @Issue.md このドキュメントを元に調査して。 関連ファイルを読んで、問題の原因候補を分析してほしい。分析結果は Issue.md に追記して。 Issue.md に仮説と関連パスが書いてあるので、Copilot はコードベース全体を探索せずに的を絞った調査を始めます。 Dashboard.tsx と useDashboardData.ts を読み込んで、 useEffect の依存配列を確認して、原因候補を分析してくれる。 何も書かずに「このバグ直して」だと、ここで大量のファイルを読みに行ってコンテキストが溢れます。 Issue.md がスコープの制約になっている わけです。 ここで「分析結果は Issue.md に追記して」と指示しているのがポイントです。Copilot の分析結果もチャットで受け取るんじゃなくて、最初から Issue.md に集約させる。こうすると調査の経緯がすべて1ファイルにまとまります。 Copilot が Issue.md に追記する内容はこんなイメージです: ## 調査結果(Copilot 追記) - `useDashboardData.ts` の `useEffect` 内で `fetchData()` を呼んでいる - 依存配列に `filters` オブジェクトが含まれている - `filters` が毎回新しいオブジェクト参照を生成しているため、レンダリングごとに `useEffect` が再実行 - → API リクエストが多重に発生している原因 仮説で「依存配列が怪しい」と書いておいたおかげで、Copilot がまさにそこにフォーカスして調査してくれたパターンですね。 ステップ3: 仮説→検証→フィードバックのサイクル ここから人間と Copilot のキャッチボールが始まります。実際のセッションでは一番ターン数が多かったフェーズです。 人間が仮説を投げる: filters の参照が変わってるのは分かった。 これって特定のウィジェットだけの問題?それとも全体で起きてる? 手動検証して、結果を Issue.md に追記: ここが大事なポイントです。検証結果をチャットに直接書くんじゃなくて、Issue.md に追記して Copilot に渡す。こうすると Copilot が Issue.md を起点に読み直してくれるので、調査のコンテキストが途切れません。 ## 検証結果(追記) - DevTools で確認、全ウィジェット共通で発生 - filters をコンソールで見たら毎回新しいオブジェクトが作られていた - useMemo でメモ化すれば直る可能性あり ---copilot指示--- @Issue.md 検証結果を追記した。これを踏まえて原因と対策を分析して。分析結果も Issue.md に追記して。 Copilot が分析結果を Issue.md に追記: Copilot の分析結果もチャットで受け取るんじゃなくて、Issue.md に書かせます。こうすると調査の経緯がすべて1ファイルにまとまるので、後から振り返りやすい。 ## 分析結果(Copilot 追記) - [`useMemo`](https://react.dev/reference/react/useMemo) で `filters` をメモ化するのが正攻法 - ただし `DashboardWidgets.tsx` で `filters` を props として渡している箇所も確認が必要 - (コード差分を提示) 人間がスコープを絞る: サーバーサイドキャッシュの追加は今回は不要。 クライアント側の多重リクエスト防止だけでいい。 ポイントは、人間が「ここを見ろ」「これは対応不要」を言うだけで Copilot の無駄な探索が激減することです。「DevTools で確認した結果はこうだった」「全ウィジェット共通だった」というランタイムデータは Copilot が自分では取れない。人間がデータを集めて、Copilot が分析する。この分業が回ると調査がスムーズに進みます。 原因が特定できたら、対応方針を決めるのは人間の仕事です。「useMemo でいく」「カスタムフックに切り出す」みたいに、2〜3パターンの実装案を出させて比較するのがお勧めです。 ステップ4: 「なぜ直ったか」を確認する 修正を適用して、リクエストが1回に収まったことを確認。ここで終わりにせずに、もう1ステップ入れます。 自分の理解が合ってるか確認したい。 filters オブジェクトが毎回新しい参照を生成していたので、 useEffect の依存配列チェックで「変わった」と判定されて再実行されていた。 useMemo でメモ化して参照を安定させたことで、不要な再実行がなくなった。 この理解で合ってる? 自分の言葉で原因を言語化して投げて、Copilot に補足・整理してもらいます。 このステップを入れることで、「なんとなく直った」で終わらなくなります。(まさにAIを自分のために使うってフェーズですね。) 「修正して終わり」にしないのが大事です。「なぜ直ったか」を言語化することで、次の類似バグに備えられます。「 Copilot チャット履歴から Instructions と SKILL.md を改善する3つの方法 」で紹介した lessons ポストモーテムと同じ発想ですね。この理解確認を lessons ファイルに残しておくと、同じパターンのバグに二度ハマらなくなります。 人間がやること vs Copilot に任せること ここまでの実践を踏まえて、役割分担を整理しておきます。33ターンのデバッグセッションから抽出した実績ベースの分担です。 人間がやるべきこと やること 実践での例 初期ドキュメント作成 ステップ1で Issue.md に問題・仮説・関連パスを記載 スコープの制限 「サーバーサイドキャッシュは不要、クライアント側だけで」 手動検証 DevTools でネットワークタブを確認、コンソールで filters の参照を確認 ランタイムデータの取得 HAR ファイル、ネットワークログ、ブラウザ上の実行時データ 方針決定 対策案の選択(useMemo でいく) Copilot に任せること やること 実践での例 ドキュメントを元にしたコード調査 ステップ2で Issue.md の関連パスからコードを読み込み 複数ファイルの一括読み込み Dashboard.tsx、useDashboardData.ts、DashboardWidgets.tsx を一度に確認 対策案の複数提示・比較 useMemo、useCallback、カスタムフックへの切り出しを比較提示 コード差分の生成 修正前/修正後の diff を出力 理解の補足・整理 ステップ4で人間の言語化を検証・補足 特に重要なのは、AI が取得できないデータを人間が補完すること です。ブラウザ上のランタイムデータ、認証が必要な API レスポンス、ネットワークログーーこれらは Copilot が直接アクセスできません。人間が取得して Issue.md に貼ることで、初めて Copilot の分析対象になります。 「人間がデータを集めて、AI が分析する」。この役割分担を意識するだけで、デバッグセッション全体の効率が変わります。 まとめ:Issue.md 1枚で変わること 「エラー貼って聞く」を「Issue.md 書いて渡す」に変えるだけで、Copilot の初手精度が上がります。 大事なのは3つだけです。 問題を書く (何が起きているか) 仮説を書く (わからなくてもいい、書こうとすることが大事) 関連パスを書く (Copilot の探索範囲を絞る) 30秒で書ける量で十分です。完璧なドキュメントを作ることが目的じゃなくて、自分の頭を整理して Copilot に的確な起点を渡すことが目的なので。 調査が終わった Issue.md は .gitignore に入れるか削除するか、運用は好みで構いません。コミットに残して調査経緯を追えるようにする派もいますし、一時ファイルとして使い捨てにする派もいます。 「修正して終わり」にしないことも大事です。「なぜ直ったか」まで確認して次に活かす。Issue.md → lessons → copilot-instructions.md / SKILL.md の流れが回ると、同じバグに二度ハマらなくなっていきます。この改善サイクルの詳細は「 Copilot チャット履歴から Instructions と SKILL.md を改善する3つの方法 」で紹介しているので、合わせて見てもらえると。 まずは次のバグで Issue.md を1枚書いてみてください。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Copilot デバッグの精度を上げるにはドキュメント1枚を先に書く first appeared on SIOS Tech Lab .
アバター
対象読者 Copilot Chat(特に Agent Mode )を日常的に使っている人 やりとりの記録を残したい・チームで共有したい人 Agent Skills の実践的な使い方を知りたい人 Agent Skills の基本(ディレクトリ構造、SKILL.md の書き方)は「 【2026年版】Agent Skills 入門 」を参照してください。 併せて読みたい: Copilot チャット履歴から copilot-instructions.md と SKILL.md を育てる方法 Copilot Chat、使い捨てにしてませんか? ども!龍ちゃんです。 GitHub Copilot のチャット履歴って、UIがあるおかげで会話の復帰はしやすいですよね。でも、「あのとき何をどう指示したっけ?」みたいな目的で履歴を見返すことって、あんまりないんじゃないでしょうか。 実は!Skillsやpromptみたいなファイルを作るにあたっては、履歴は宝庫なんです。 VS Code には「 Chat: Export Chat… 」というエクスポート機能があります。ただ、出力されるのは JSON。実際にエクスポートしてみると、6ターンの会話が 20,347行 の JSON になります。こういうやつです: { "responderUsername": "GitHub Copilot", "requests": [ { "message": { "text": "読み込まれているSkill一覧を取得することは可能ですか?" }, "response": [ { "kind": "thinking", "value": "ユーザーは「読み込まれているSkill一覧を取得することは可能ですか?」と聞いています...", "generatedTitle": "Reviewed skill information and considered directory structure" }, { "kind": "toolInvocationSerialized", "toolSpecificData": { "kind": "terminal", "commandLine": { "original": "ls .claude/skills/" }, "terminalCommandState": { "exitCode": 0, "duration": 50 } }, "toolId": "list_dir" }, { "value": "はい、可能です。現在読み込まれているSkill一覧を表示します..." } ], "modelId": "copilot/claude-sonnet-4.5", "result": { "timings": { "totalElapsed": 34900 } } } ] } テキスト応答、思考過程、ツール呼び出しが response[] に混在している。 20,000行の JSON から必要な情報を拾うのは現実的ではない。(というか人間が読むのは無理ですわ) 本記事では、この JSON を CLI 1行で Markdown に変換する方法を紹介します。先ほどの 20,347行の JSON は 720行の Markdown に変換されます。情報量はほぼそのまま、行数は 96.5%削減 です。 Before After 形式 JSON(20,347行) Markdown(720行) 可読性 エディタでも厳しい そのまま読める ツール呼び出し toolInvocationSerialized の入れ子 ファイル名 + diff 表示 思考過程 kind: "thinking" を JSON の中から探す <details> 折りたたみ コンテキスト参照 variableData の巨大なオブジェクト Referenced Context リスト VS Code の Export Chat 機能 手順はシンプルなんですが、一応確認しておきますね。 VS Code で Copilot Chat パネルを開く コマンドパレット( Ctrl+Shift+P / Cmd+Shift+P )を開く 「 Chat: Export Chat… 」を選択 保存先を指定 → JSON ファイルが出力される 操作はこれだけです。問題はこの JSON の中身で、次で何が入っているかを整理していきますね。 Export JSON に何が入っているか:情報の棚卸し 「何を保存できるか」が分からないと、ツールを使う気にもならないですよね。まず export JSON に何が入っているかを整理していきます。 含まれる情報の一覧 カテゴリ 含まれる情報 実際の例(今回のサンプル) 会話テキスト ユーザーの入力、Copilot の応答 「読み込まれているSkill一覧を取得することは可能ですか?」→ スキル13個のリストを返答 モデル情報 使用モデル ID(ターンごと) copilot/claude-sonnet-4.5 応答時間 各ターンの応答ミリ秒 totalElapsed: 34900 (34.9秒) 思考過程 Thinking ブロック(モデルを問わず記録) 思考テキスト + 生成タイトル。内容の詳細度はモデルにより異なる ツール呼び出し ファイル読み書き、ターミナル実行の記録 list_dir , read_file , grep_search , run_in_terminal コンテキスト参照 チャットに渡したファイル、ワークスペース @workspace , prompt:copilot-instructions.md , prompt:CLAUDE.md スキル一覧 Copilot に読み込まれたスキル定義 <skills> タグ内に13個のスキル名とファイルパス エラー情報 レート制限、キャンセル等 Canceled (Turn 6) エージェント情報 Agent Mode の種別 agent , modes: ["agent"] 今回のサンプル(6ターンのスキル発火テスト)だけでも、これだけの情報が JSON に詰まっています。 なぜ読みにくいか:情報の分散とノイズ JSON が読みにくい理由は、サイズだけではありません。構造上の問題が3つあります。 まあそもそも生の JSON は人間が読む想定で設計されてないんですけどねw 1. ツール呼び出しの情報が2箇所に分散している response[] 内の toolInvocationSerialized → ターミナル出力、exit code、所要時間 result.metadata.toolCallRounds → ツール名、引数(ファイルパス、検索クエリ等) 同じツール呼び出しの情報を2箇所から突き合わせないと全体像が見えません。 2. テキスト応答とツール呼び出しが混在している response[] 配列の中に、テキスト応答( kind なし)、思考過程( kind: "thinking" )、ツール呼び出し( kind: "toolInvocationSerialized" )、インライン参照( kind: "inlineReference" )がフラットに並んでいます。 3. VS Code 内部用のノイズが混ざっている mcpServersStarting 、 progressTaskSerialized 、 prepareToolInvocation など、ユーザーに関係ないイベントが response[] に含まれます。今回のサンプルでも Turn 1 の先頭に mcpServersStarting が入っていました。 さらに、コンテキスト参照の variableData には devcontainer のフルパスが Base64 エンコードで入っており、1つの変数定義だけで数十行を消費します。20,347行のうち相当部分がこのノイズです。 変換ツールで保存される情報 / されない情報 変換ツール copilot-chat-converter が何を残して何を落とすか、整理しておきますね。ここは個人・チームの判断で何を残すかという判断が必要かなって思います。 情報 保存 変換後の表示 会話テキスト(全文) ○ User / Copilot セクション モデル ID ○ ターンごと + ヘッダーに最頻モデル 応答時間 ○ 人間可読に変換( 34.9s 、 500ms 、 1m 30.0s ) Thinking(思考過程) ○ <details> 折りたたみ(タイトル付き) ツール呼び出し(名前・引数) ○ Tool Calls セクション(ファイルパス抽出) ファイル書き込み ○ diff ブロック( <details> 折りたたみ) ターミナル実行 ○ コマンド + exit code + 出力 コンテキスト参照 ○ Referenced Context リスト エラー情報 ○ blockquote で表示( > **Error**: Canceled ) ツール出力(長文) △ 500文字超は末尾を切り詰め VS Code 内部イベント × ノイズとしてスキップ 空の Thinking ブロック × vscodeReasoningDone マーカーをスキップ ANSI エスケープコード × 除去(ターミナル出力のカラーコード等) devcontainer パス情報 × ワークスペースプレフィックスを除去 まとめると : 会話の記録として必要な情報はほぼ全部入ってます。落ちるのは VS Code 内部のノイズと、長すぎるツール出力の末尾だけですね。 copilot-chat-converter:JSON → Markdown 変換 というわけで、エクスポートした JSON を Markdown に変換してくれる CLI ツールを作りました。Python + Click で実装し、実行には uv を使っています。SKILL.md もセットで用意しているので、後で出てくるワンコマンド化の話まで使えます。 自分の環境で再現したい方へ 既存の変換ツールとしては copilot-chat-to-markdown (Python スクリプト、スター106)があります。基本的なチャットとツール呼び出しの変換に対応しているので、まずはこちらを試してみるのがお手軽です。ただし Thinking ブロックや Agent Mode のファイル操作 diff には未対応なので、今回はそこまで含めて変換したくて自作しました。 本記事のツールは pip パッケージとしては公開していませんが、設計書(plan.md)と実装手順(action-plan.md)を Gist で公開しています。お手元の AI コーディングエージェントに読み込ませれば同等のツールを実装できるので、既存ツールと見比べながら自分の用途に合ったものを作ってみてください。 基本的な使い方 # 標準出力に変換結果を表示(内容確認用) uv run copilot-chat-converter chat.json # ファイルに出力 uv run copilot-chat-converter chat.json -o chat.md ファイル出力時のターミナル表示: 🚀 copilot-chat-converter 起動 📝 処理対象: 1 ファイル 📄 処理中: chat.json ✅ 保存完了: chat.md -o を省略すると標準出力に Markdown がそのまま出ます。パイプで他のコマンドに渡したいときに便利です。 複数ファイルの一括変換 定期的にエクスポートしてると JSON がどんどん溜まっていくんですよね。ディレクトリを指定すれば一括変換できます: uv run copilot-chat-converter exports/*.json -o docs/chats/ 出力ディレクトリが存在しない場合は自動作成されます。各 .json が同名の .md に変換されます( chat-2026-03-04.json → docs/chats/chat-2026-03-04.md )。1件でエラーが出ても他のファイルの処理は継続します。 オプション一覧 オプション 短縮形 説明 デフォルト --output -o 出力先(ファイルまたはディレクトリ) 標準出力 --quiet -q 進捗メッセージを非表示 表示する 条件 動作 -o なし、1ファイル 標準出力(進捗メッセージは自動で抑制) -o なし、複数ファイル エラー(出力先を明示してください) -o ファイル名 指定ファイルに出力 -o ディレクトリ/ 各 JSON を同名の .md として出力 変換結果の読み方:何がどう変わるか さっき整理した JSON の中身が、実際にどう変換されるかを見ていきますね。以下はぜんぶ、先ほどの6ターンのチャットを変換した実際の出力です。 基本構造 ヘッダーにセッション全体のメタデータが入っていて、その下にターンごとの User / Copilot のやりとりが並ぶ構造です。 # Copilot Chat Session ## Metadata - **Exported**: 2026-03-04 05:30:28 UTC - **Model**: copilot/claude-sonnet-4.5 - **Total Turns**: 6 - **Requester**: (unknown) - **Responder**: GitHub Copilot --- ## Turn 1 ### User 読み込まれているSkill一覧を取得することは可能ですか? ### GitHub Copilot **Model**: copilot/claude-sonnet-4.5 | **Response Time**: 34.9s はい、可能です。現在読み込まれているSkill一覧を表示します。 Model : 全ターンで最も多く使われたモデルをヘッダーに表示、ターンごとの使用モデルも記載 Response Time : ミリ秒を人間可読に変換( 34900ms → 34.9s ) 思考過程(Thinking)→ 折りたたみ Agent Mode では、Copilot の思考過程が kind: "thinking" として記録されるんですね。Claude Sonnet 4.5、GPT-5.3-Codex、GPT-5 mini いずれのモデルでも確認できました。思考の詳細度はモデルによって違いますが、変換ツールはどのモデルでも同じ形式で表示してくれます。変換後は <details> で折りたたまれます: <details><summary>Thinking: Reviewed skill information and considered directory structure</summary> ユーザーは「読み込まれているSkill一覧を取得することは可能ですか?」と聞いています。 現在のコンテキストを見ると、システムプロンプトに以下のセクションがあります: ... このSkill一覧をユーザーに提供できます。また、`.claude/skills/` ディレクトリの 内容をリストすることもできます。 </details> 折りたたみなので普段は邪魔にならず、「なぜ Copilot がその行動を取ったか」を確認したいときだけ展開できます。 今回のサンプルでは6ターン中に 10個の Thinking ブロック が含まれていました。JSON ではこれらが response[] 配列の中に埋もれていますが、Markdown では見出し付きの折りたたみとして整理されます。なお、VS Code が挿入する空の Thinking ブロック( vscodeReasoningDone: true のマーカー)は自動的にスキップされます。 モデルによる Thinking の違い : Claude モデルでは思考過程の全文がプレーンテキストで記録されます。GPT-5.3-Codex ではタイトル的な短文が記録され、詳細は暗号化されています。GPT-5 mini でも Thinking ブロックは記録されますが、内容は空のケースがあります。変換ツールはいずれも <details> 折りたたみとして表示し、利用可能な情報をすべて出力します。 ツール呼び出し → Tool Calls + File Operations Agent Mode のツール呼び出しは2つのセクションに整理されます。 Tool Calls : 個々のツール呼び出しの詳細 #### Tool Calls **1.1. list_dir** `.claude/skills` File Operations : ファイル操作のサマリー #### File Operations **Read** (2 files): - `docs/features/copilot-agent-skills-gh-actions/action-plan.md` - `docs/features/copilot-agent-skills-gh-actions/verification-log.md` 書き込み系のツール呼び出し( create_file , replace_string_in_file 等)がある場合は diff 形式で表示されます: **3.2. replace_string_in_file** `src/utils.py` <details><summary>Diff (4 lines)</summary> ​```diff - def old_function(): - pass + def new_function(): + return True ​``` </details> 読み取り系は File Operations にまとめて、書き込み系は diff で詳細を見せる形です。この分離のおかげで、ログが長くなっても見通しが保たれるんですよね。 ターミナル実行 → コマンド + exit code + 出力 run_in_terminal によるターミナル実行は、コマンド・exit code・出力をセットで表示します: **5.1. run_in_terminal** - Explanation: `最新10件のワークフロー実行状態を取得` - Command: `gh run list --limit 10 --json databaseId,name,status,conclusion,createdAt,headBranch ...` ツール出力が500文字を超えると末尾が切り詰められます。ターミナルの ANSI カラーコードは自動で除去してくれるので、コピペしても化けないのは地味に助かりますね。 ツール分類の一覧 変換ツールは内部でツールを3種類に分類し、それぞれ適切な表示形式を適用します。 分類 ツール例 表示形式 読み取り read_file , list_dir , grep_search , file_search File Operations にまとめ表示 書き込み create_file , replace_string_in_file , insert_text_in_file diff ブロック( <details> 折りたたみ) ターミナル run_in_terminal コマンド + exit code + 出力(500文字で切り詰め) ワークスペースのパスプレフィックス( /workspaces/repo-name/ )も自動で除去してくれます。ファイルパスが長くて読みにくいのはストレスなので、ここは地味に重要ですね。 Agent Skills でワンコマンド化:「チャット変換して」 なぜ SKILL 化するか CLI コマンドは覚えれば使えますが、毎回 uv run copilot-chat-converter ... と打つのは正直だるいんですよね。SKILL.md に組み込めば: 「チャット変換して exports/chat.json」の1行で完了 ファイルパスの指定もチャットの中でできる CLI のオプションを覚えなくていい CLI ツールを Agent Skills でラップするのは結構汎用的なパターンで、覚えておくと便利です。 「 gh CLI × Claude Code でブラウザなし GitHub 操作を実現する 」で gh CLI を Skills でラップする設計思想を解説しており、その実践編として、以下の記事があります。。既存の CLI があるなら、SKILL.md を書くだけで「チャットから呼べるツール」になります。 「 gh CLI × Claude Code で Gist 操作をチャット1行で完結させる 」で Gist 操作 「 GitHub Actions 失敗ログ、まだ手動で読む?Copilot Agent Skills で CI デバッグを自動化する実装ガイド 」で CI デバッグ GitHub Copilot 版 SKILL.md GitHub Copilot の SKILL.md では description: | (YAML の複数行記法)を使うと発火しない問題があります。「 Claude CodeからGitHub Copilotへ移植したらAgent Skillsが動かない?原因と解決策 」で検証結果と回避策を詳しく解説しているので、Copilot 向けにスキルを書く場合は必ず確認してください。 .github/skills/copilot-chat-converter/ ├── SKILL.md ← ~500トークンに収める └── references/ └── output-format.md ← 詳細仕様(必要時に Copilot が読み込む) .github/skills/copilot-chat-converter/SKILL.md に配置します。抜粋: --- name: copilot-chat-converter description: "CRITICAL: Copilot Chat JSONをMarkdownに変換。MANDATORY: チャット変換, チャット履歴, JSONをMarkdownに, チャットエクスポート。VS CodeのExport ChatのJSONを読みやすいMarkdownに変換。" --- # Copilot Chat Converter ## When to Use - ユーザーが「チャット変換して」「chat.json を変換」と言ったとき - `.json` ファイルのパスと変換の指示が与えられたとき ## Quick Start ​```bash uv run copilot-chat-converter chat.json -o output.md ​``` ポイント : description に CRITICAL / MANDATORY を入れて発火を安定させる description: | (パイプ)は使わない 。複数行記法だと各行が別の属性として誤解釈され、description が空になって発火しません。必ず description: "..." の単一行で書きます。実際にこのスキルの初版でもこの問題を踏みました SKILL.md 本体は Copilot の ~500 トークン制限に収める。詳細なフォーマット仕様は references/output-format.md に分離 Claude Code 版 SKILL.md .claude/skills/copilot-chat-converter/SKILL.md に配置します。抜粋: --- name: copilot-chat-converter description: | GitHub Copilot ChatのエクスポートJSON形式をMarkdownに変換するスキル。 "チャット変換して", "chat.jsonを変換", "Copilot Chatをマークダウンに", "JSONをMarkdownに", "/copilot-chat-converter" の場合に使用。 allowed-tools: Read, Bash --- Claude Code 版はトークン制限が緩いため、出力の解釈方法やエラーハンドリングまで SKILL.md 本体に書けます。 allowed-tools でスキルが使えるツールを明示的に制限するのも Claude Code 固有の機能です。 SKILL.md の全文、設計書(plan.md)、実装手順(action-plan.md)、出力形式リファレンスは Gist にまとめて公開しています。自分で同様のツールを作りたい方は参考にしてください。 Copilot 版 / Claude Code 版の違い 同じ CLI ツールをラップしていますが、スキル定義は各ツールの特性に合わせて変える必要があります。 GitHub Copilot 版 Claude Code 版 配置場所 .github/skills/ .claude/skills/ description CRITICAL / MANDATORY キーワード必須 自然言語で記述 トークン制限 ~500トークン( references/ で分離) 緩い(全文書ける) allowed-tools 記載なし(Copilot が自動判断) 明示的に指定 発火条件 description のキーワードマッチ description + 自然言語理解 詳細仕様 references/ に分離して Progressive Loading 本体に含めてOK 両方に配置しておけば、Claude Code と VS Code Copilot のどちらからでも「チャット変換して」で同じ操作ができます。設定ファイルの共存パターンについては「 Claude Code→GitHub Copilot移行で使える設定ファイル6つの対応表 」を参照してください。 ちなみに GitHub Copilot は .claude/skills/ も参照できるので、Claude Code 版だけ作っておけばスラッシュコマンド( /copilot-chat-converter )で Copilot からも呼べます。両方書くのが面倒な人はまず Claude Code 版だけでOKです。 まとめ Copilot Chat の会話を保存する流れをおさらいしておきますね。 エクスポート : VS Code の「Chat: Export Chat…」で JSON を取得 情報の確認 : export には会話テキスト・思考過程・ツール呼び出し・コンテキスト参照まで含まれている 変換 : uv run copilot-chat-converter chat.json -o chat.md で Markdown に変換(20,000行 → 720行) ワンコマンド化 : SKILL.md を配置して「チャット変換して」で完了 変換ツールは Agent Mode のツール呼び出し(diff 表示付き)、思考過程(Thinking)の折りたたみ表示、ファイル操作サマリーまで対応してます。VS Code 内部のノイズは自動で除去されて、必要な情報だけが残る感じです。 まずは1回エクスポートして変換してみてください。「こんなに情報が入っていたのか」と驚くはずです。(同時に履歴が長くてびっくりすると思います) 保存した会話履歴を copilot-instructions.md や SKILL.md の改善にフィードバックする方法は、次の記事で解説しますね。 ほなまた〜 実装リファレンス : 設計書・実装手順・SKILL.md 全文・出力形式リファレンスは Gist にまとめて公開しています。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot Chat の会話履歴を Markdown で変換・保存:20,000行→720行 first appeared on SIOS Tech Lab .
アバター
GitHub Actions の失敗ログ、まだ手動で読んでいませんか? ども!龍ちゃんです。 CI が落ちたときの対応、毎回こうなっていませんか? GitHub Actions の UI を開く 失敗した Run を探す 失敗したジョブを開く ログをスクロールしてエラー行を目視で探す エラーメッセージをコピーしてググる 地味に面倒なんですよね、これ。特にジョブが複数あるワークフローだと、どのジョブのどのステップで落ちたのかを探すだけで時間を食う。 これ、Agent Skills に任せたら1行で終わります。 Before After 操作 GitHub UI → ログ目視 → 手動分析 「CI失敗してるから原因を調べて」 所要時間 5〜10分 30秒 得られるもの エラー行のコピペ 原因分析 + 修正方針のレポート 本記事では、gh CLI を使った CI ログ自動分析スキルを GitHub Copilot の Agent Skills として実装する手順を解説します。Claude Code で同じスキルを作った「 gh CLI × Claude Code で GitHub Actions 失敗ログをチャットで即解決 」の Copilot 移植版です。 190行あった Claude Code 版の SKILL.md を、Copilot の 500 トークン制限に合わせて 58 行に圧縮する設計がメインの話題になります。 対象読者 Agent Skills で実用的なスキルを作りたい人 Claude Code のスキルを Copilot に移植したい人 CI/CD のデバッグを自動化したい人 Agent Skills の基本(ディレクトリ構造、SKILL.md の書き方)は「 【2026年版】Agent Skills 入門 」を参照してください。 GitHub Actions の失敗を Agent Skills で自動分析する 作り方の前に、まず完成形を見てください。こういうやりとりになります。 龍ちゃん CI失敗してるから原因を調べて GitHub Copilot 失敗した Run を特定します。 → gh run list –status failure –limit 5 → gh run view 22573804432 –json jobs → gh run view 22573804432 –log –job 65388009712 ## Failure Analysis Root Cause: integration test が HTTP 503 で失敗 Suggested Fix: サービスのヘルスチェックをテスト前に実行 1行のプロンプトで、失敗 Run の特定からログ取得、原因分析まで全自動。この動きを実現するスキルの作り方を、このあと順を追って解説します。 前提条件 VS Code + GitHub Copilot(Agent Mode が使える状態) gh CLI がインストール・認証済み( gh auth status で確認) .github/skills/gh-workflow/ にスキルが配置済み(本記事で作ります) テスト用の失敗ワークフロー(demo-failure.yml) 検証用に、意図的に失敗するワークフローを用意しました。 name: Demo - Failure Workflow on: workflow_dispatch: jobs: setup: name: Setup runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Verify environment run: | echo "Environment check..." echo "OS: $(uname -s)" echo "Setup completed." test: name: Test runs-on: ubuntu-latest needs: setup steps: - name: Checkout repository uses: actions/checkout@v4 - name: Run unit tests run: | echo "Running unit tests..." echo "test_fetch_article ... PASSED" echo "test_parse_html ... PASSED" echo "Unit tests passed." - name: Run integration tests run: | echo "Running integration tests..." echo "" echo "FAILED: test_pipeline.py::test_full_pipeline" echo " AssertionError: Expected status code 200, got 503" echo " File: tests/test_pipeline.py, line 42" echo " assert response.status_code == 200" echo "" echo "1 passed, 1 failed in 3.42s" exit 1 deploy: name: Deploy runs-on: ubuntu-latest needs: test steps: - name: Deploy application run: echo "Deploying..." Setup → Test → Deploy の3ジョブ構成で、Test ジョブの integration tests が 503 エラーで exit 1 します。CI/CD でよくあるパターンを再現しています。 手動実行して失敗させておきます。 gh workflow run "Demo - Failure Workflow" # 30秒ほど待ってから確認 gh run list --workflow "demo-failure.yml" --limit 1 デモ 1:gh run list でクイックステータス確認 VS Code の Agent Mode で「CI確認して」と入力します。 スキルが発火すると、Copilot は SKILL.md の Mode 1(クイックステータス)に従い、 gh run list で最新の実行状況を一覧取得します。 実行されたコマンド: git branch --show-current gh run list --branch main --limit 10 --json databaseId,status,conclusion,name,headBranch,event,createdAt,displayTitle 出力(Copilot のレポート): 日時 ワークフロー名 ステータス Run ID 3/2 11:23 Demo – Failure Workflow failure 22573804432 2/26 06:20 Demo – Failure Workflow failure 22430458939 2/26 06:20 Demo – CI Pipeline success 22430440934 2/26 06:19 Demo – CI Pipeline success 22430416062 「最新の CI が失敗しています」と報告したうえで、「失敗ログを分析しますか?」と次のアクションを提案してくれます。ブランチ取得から一覧表示まで自動で、自分が打つのはプロンプト1行だけです。 デモ 2:gh run view –log で失敗ログを自動分析 ここがメインのデモです。「3/2に失敗したものを取得してほしい」と入力すると、スキルの Mode 4(ログ分析)が起動します。 Copilot は SKILL.md に書かれた Progressive Filtering (段階的絞り込み)の手順に従って、4ステップで自動分析します。 Step 1: 失敗 Run の特定 gh run view 22573804432 --json jobs,status,conclusion,createdAt,updatedAt,headBranch,event,workflowName Run 全体の情報を取得。3ジョブの中から Test ジョブが failure であることを特定。 Step 2: 失敗ジョブの絞り込み JSON レスポンスから失敗ジョブの ID(65388009712)を抽出。Step 4 の「Run integration tests」が failure であることを確認。 Step 3: ログ取得 gh run view 22573804432 --log --job 65388009712 --log は必ず --job と併用します。これは SKILL.md の Token Efficiency Rules で定義したルールです。 --job なしだと全ジョブのログが出力されてトークンを大量消費するので、失敗ジョブに絞って取得します。 Step 4: エラー分析とレポート出力 ## Failure Analysis **Run:** #22573804432 - Demo - Failure Workflow **Branch:** main | Job | Error Summary | |-----|--------------| | Test | AssertionError: Expected status code 200, got 503 | **Root Cause:** Integration test `test_pipeline.py::test_full_pipeline` が失敗。 外部サービスへの接続で HTTP 503 (Service Unavailable) を受信。 **Suggested Fix:** 1. サービスのヘルスチェックをテスト前に実行 2. テストエンドポイントへのネットワーク接続を確認 3. テストデータ・環境設定を見直し Run 全体 → 失敗ジョブ → 失敗ステップ → エラー行と、段階的に絞り込んで 503 エラーを特定し、原因と修正方針をレポートにまとめてくれました。これが Progressive Filtering の実際の動きです。 デモ 3:英語プロンプトでの発火確認 description に英語キーワードも埋め込んであるので、英語でも発火します。 「Analyze the GitHub Actions error logs」と入力すると、同じく Progressive Filtering が走り、英語で Failure Analysis Report が出力されました。日本語チームと英語チームが混在する環境でも使えます。 Copilot 用 SKILL.md の作り方:190行→58行に圧縮する設計 やっと本題です。ここが一番悩んだところで、Claude Code 版の SKILL.md(190行)を Copilot の制約に合わせて58行に圧縮する設計を解説します。 ディレクトリ構成と配置ルール .github/ ├── copilot-instructions.md ← ルーティング追記 ├── skills/ │ └── gh-workflow/ │ ├── SKILL.md ← 本体(58行・219語) │ └── references/ │ └── commands.md ← 詳細情報(79行・266語) └── workflows/ ├── demo-failure.yml ← テスト用(意図的に失敗) └── demo-success.yml ポイント: ファイル名は SKILL.md (大文字必須) ディレクトリ名は kebab-case で name フィールドと一致させる Claude Code 版( .claude/skills/gh-workflow/ )とは別ディレクトリなので、両方共存できる SKILL.md と references/ の分離基準 GitHub Copilot には SKILL.md に対する 500 トークン制限 (約60行・300語以内)があります。Claude Code にはこの制限がないので、190行の SKILL.md がそのまま動いていましたが、Copilot ではそうはいきません。 圧縮の方針は「SKILL.md はルーター、詳細は references/ に分離」です。 分類 SKILL.md に残す references/commands.md に分離 モード判定 4モードのテーブル(4行) – Token Efficiency Rules 必須ルール4項目 – 各モードの手順 概要(主なコマンド1行ずつ) JSON 出力パターン全文 レポートテンプレート – ステータス一覧 + 失敗分析テンプレート Status/Conclusion 一覧 – 12行の値テーブル Important Notes – 4項目の注意事項 残す基準 : Copilot がスキルを実行するときに「最初に読む必要がある情報」だけ残す。モード判定(何をすべきか)とトークン効率ルール(やってはいけないこと)。この2つがないと、Copilot は最初の一歩を踏み出せません。 分離する基準 : 必要になったタイミングで参照すればいい情報。JSON の出力パターンやレポートテンプレートは、実行が進んだ段階で references/ から読み込めば十分です。「あとで見ればいいもの」は全部 references/ に押し出す。割り切りが大事でした。 Copilot の Progressive Disclosure(関連度に基づいて必要なファイルだけ読み込む仕組み)がこの分離設計と噛み合います。SKILL.md を読んでモードを判定し、実行に必要な詳細情報を references/ から追加で読む、という流れになるわけです。 圧縮結果: Claude Code 版 Copilot 版 SKILL.md 190行 58行(219語) 参照ファイル なし(全部入り) references/commands.md(79行・266語) 合計 190行 137行(2ファイル) 行数の合計はあまり変わりませんが、SKILL.md 単体が 500 トークン予算の 73% に収まっているのがポイントです。 description の書き方:単一行で書く鉄則 description は Copilot がスキルを選ぶかどうかを判定する 最重要フィールド です。ここの設計が発火率を左右します。 鉄則:description は単一行で書く。 description: | の複数行記法は使わない。 # NG: 複数行記法 → Copilot が "|" だけ読み込んでしまう description: | GitHub Actions の Workflow 状態確認とログ分析を行うスキル。 Use when: CIが失敗した原因を調べたい。 # OK: 単一行 → Copilot が全文を正しく読み込む description: GitHub Actionsのワークフロー状態確認と失敗ログ分析を行うスキル。gh CLIでCI/CDのログ取得から原因分析まで実行する。Use when: CIが失敗した原因を調べたい、ワークフローの状態を確認したい、PRのチェック結果を見たい。Triggers on: CI確認, Workflow確認, CI失敗, ログ分析, GitHub Actions debug,CI status check, workflow failure, PRチェック, エラーログ, pipeline debug. GitHub Copilot のスキルローダーは YAML の複数行記法( description: | )を正しくパースできません。 | という文字列だけが description として読み込まれ、発火判定に使われる情報がほぼゼロになります。Claude Code ではこの問題は起きないので、移植時に見落としがちな落とし穴です。 description に含める3要素: 機能説明 — 何をするスキルか Use when — どういう場面で使うか(ユーザーの意図) Triggers on — 発火キーワード(日本語 + 英語) Copilot は description の文字列とユーザーの発話の 類似度 でスキルを選ぶので、「ユーザーが実際に言いそうな言葉」を入れておくのがコツです。 description の設計思想や、なぜ発火しないのかの原因分析は「 Claude CodeからGitHub Copilotへ移植したらAgent Skillsが動かない?原因と解決策 」で詳しく解説しています。本記事では実装だけ押さえて先に進みます。 copilot-instructions.md でスキル発火を安定させる description だけでは発火が不安定な場合の補強策として、 copilot-instructions.md にルーティングテーブルを追加します。copilot-instructions.md は Agent Mode で常に読み込まれるファイルなので、ここにスキルへの道しるべを書いておくと発火が安定します。 ## Agent Skills Routing | キーワード | スキル | パス | |-----------|--------|------| | CI確認, Workflow, GitHub Actions, ログ分析, CI失敗, pipeline debug | gh-workflow | `.github/skills/gh-workflow/SKILL.md` | 既存の copilot-instructions.md の末尾に追記するだけです。中身は触らない。 注意点として、ルーティングに登録するスキルは 重要なもの3〜5個に絞る べきです。copilot-instructions.md に書いた内容は毎回のコンテキストに含まれるので、スキル全部を登録するとトークンを無駄遣いします。 この手法の効果と限界は「 Agent Skillsが動かない? 」の「解決策③:Instructions ルーティング」セクションで検証データ付きで解説しています。 allowed-tools の検証:agentskills.io 仕様と実装のギャップ SKILL.md のフロントマターには allowed-tools というフィールドがあります。スキルが使ってよいツールを制限する仕組みです。今回のスキルでは以下のように書きました。 allowed-tools: Bash(gh:*) Bash(git:*) Bash(jq:*) Read 「gh CLI と git と jq のコマンド実行、それとファイル読み取りだけ許可する」という意味です。 agentskills.io Specification での定義 agentskills.io の仕様 では、 allowed-tools は正式なオプションフィールドとして定義されています。ただし Experimental(実験的) ステータスで、”Support for this field may vary between agent implementations” と注記されています。 記法は2パターンあります: # パターン記法(コマンドプレフィックス指定) allowed-tools: Bash(gh:*) Bash(git:*) Read # ツール名列挙(MCP ツール等) allowed-tools: list_workflow_runs summarize_job_log_failures get_job_logs GitHub Copilot での動作検証結果 結論から言うと、 現時点では allowed-tools の enforcement(強制)は実装されていません 。 今回の検証では、 allowed-tools を書いた状態でスキルは正常に発火し、gh CLI のコマンドも問題なく実行されました。一方で、 allowed-tools に列挙していないツールの使用を制限する動作は確認できませんでした。 確認項目 結果 スキルの発火 問題なし(allowed-tools が発火を妨げない) allowed-tools 内のツール使用 問題なし allowed-tools 外のツール制限 未確認(制限が効いている証拠なし) VS Code バリデータの警告 Issue #14131 として報告済み(Open、対応予定なし) allowed-tools はスキル選択時には参照されず、スキル実行時に SKILL.md の本文を読んだ段階で「ガイドライン」として機能している可能性があります。強制力のあるサンドボックスではなく、エージェントへの「お願い」に近い位置づけです。 コミュニティの allowed-tools 実装例 参考までに、 heilcheng/awesome-agent-skills の github-actions-failure-debugging スキルを見ると、 allowed-tools はフロントマターに書かず、本文テキスト内で手順として使用ツールを記述しています。 To debug failing GitHub Actions workflows: 1. Use `list_workflow_runs` to look up recent workflow runs 2. Use `summarize_job_log_failures` to get an AI summary of failed jobs 3. Use `get_job_logs` for full detailed failure logs if needed フロントマターの allowed-tools で制限するか、本文で手順として指示するか。enforcement が実装されていない現時点では、本文に書くほうが確実にエージェントに伝わります。 正直どちらにするか迷ったんですが、自分のスキルでは「両方書いておく」方針にしました。フロントマターは将来の enforcement が来たときの保険、本文は今のエージェントへの確実な指示。二重管理にはなりますが、壊れるリスクよりマシだと判断しています。 Claude Code 版 SKILL.md との違い(比較表) 同じ gh-workflow スキルの Claude Code 版と Copilot 版を比較します。 変わる点:description・トークン予算・ファイル構成 比較項目 Claude Code 版 Copilot 版 ファイルパス .claude/skills/gh-workflow/ .github/skills/gh-workflow/ SKILL.md 行数 190行(1ファイル完結) 58行 + references/commands.md description トリガー文字列列挙 単一行で Use when + Triggers on allowed-tools Bash, Read Bash(gh:*) Bash(git:*) Bash(jq:*) Read 参照ファイル分離 不要(全文コンテキストに読み込み) 必須(500トークン制限) copilot-instructions.md 不要 ルーティング追記推奨 一番大きいのは「トークン予算」です。Claude Code は SKILL.md を丸ごと読み込めるので圧縮の必要がなく、190行でも問題ありません。Copilot は 500 トークン制限があるので、references/ への分離が必須になります。 変わらない点:gh CLI コマンド・Progressive Filtering 比較項目 Claude Code / Copilot 共通 gh CLI コマンド 同一( gh run list , gh run view --log --job 等) Progressive Filtering 同一(一覧→ジョブ特定→ログ取得→分析の4ステップ) Token Efficiency Rules 同一( --log は --job と併用、 watch 禁止等) Report Template 同一(references/commands.md に移植済み) gh CLI のノウハウはそのまま使えます。変えるのはスキルの外側(配置・description・トークン予算)だけで、中身は同じものが動きます。 リポジトリ/ ├── .claude/skills/gh-workflow/SKILL.md ← Claude Code版(190行・1ファイル完結) ├── .github/skills/gh-workflow/ ← Copilot版(58行 + references/) │ ├── SKILL.md │ └── references/commands.md └── (両方共存可能・互いに干渉しない) ちなみに GitHub Copilot は .claude/skills/ 配下のファイルも読み込みます。つまり Claude Code 用に作ったスキルが Copilot 側でも認識される。ただし 190 行の SKILL.md だとトークン予算を超えるし、 description: | の複数行記法は Copilot で壊れるので、そのまま期待どおりに動くかは別問題です。Claude Code ユーザーが Copilot でも使いたいなら、本記事のように .github/skills/ に圧縮版を別途置くのが確実です。 まとめ CI が落ちたら「CI失敗してるから原因を調べて」。それだけで原因分析から修正方針までレポートが出てきます。 やることは .github/skills/gh-workflow/ に SKILL.md と references/commands.md を置くだけ。あとは copilot-instructions.md にルーティングを1行追記すれば完了です。 本記事のポイント: SKILL.md は 500 トークン以内に圧縮 — 190行→58行。モード判定とトークン効率ルールだけ残し、詳細は references/ に分離 description は単一行で書く — description: | の複数行記法は Copilot で読み込まれない。詳細は「 Agent Skillsが動かない? 」 allowed-tools は Experimental — 仕様にはあるが enforcement 未実装。書いておいて損はないが、過信しない gh CLI のノウハウは共通 — Claude Code と Copilot で変えるのは外側だけ ではまた! Claude Code で同じことをやるなら「 gh CLI × Claude Code で GitHub Actions 失敗ログをチャットで即解決 」を参照してください。 検証環境 検証日: 2026年3月2〜3日 環境: VS Code + GitHub Copilot(Agent Mode) モデル: claude-sonnet-4.5(Copilot 経由) 参考: VS Code Docs – Agent Skills , agentskills.io Specification 付録:SKILL.md / references/commands.md 全文コピー用 自分のリポジトリに導入するときは、以下のディレクトリ構成でファイルを配置してください。 .github/ ├── copilot-instructions.md ← 末尾にルーティングを追記 ├── skills/ │ └── gh-workflow/ │ ├── SKILL.md ← 下記「SKILL.md 全文」をコピー │ └── references/ │ └── commands.md ← 下記「commands.md 全文」をコピー └── workflows/ └── demo-failure.yml ← テスト用(本記事「テスト用の失敗ワークフロー」参照) .github/skills/gh-workflow/SKILL.md(58行・219語) --- name: gh-workflow description: GitHub Actionsのワークフロー状態確認と失敗ログ分析を行うスキル。gh CLIでCI/CDのログ取得から原因分析まで実行する。Use when: CIが失敗した原因を調べたい、ワークフローの状態を確認したい、PRのチェック結果を見たい。Triggers on: CI確認, Workflow確認, CI失敗, ログ分析, GitHub Actions debug,CI status check, workflow failure, PRチェック, エラーログ, pipeline debug. allowed-tools: Bash(gh:*) Bash(git:*) Bash(jq:*) Read --- # GitHub Actions Workflow Status & Log Analysis gh CLI を使って GitHub Actions の Workflow ステータス確認・ログ分析を行います。 ## Mode Selection ユーザーの指示から以下の4モードを自動判定して実行する。 | モード | トリガー例 | 主なコマンド | |--------|-----------|-------------| | クイックステータス | 「CI確認して」 | `gh run list` | | Run 詳細 | 「Run #123 の詳細」 | `gh run view <run-id>` | | PR チェック | 「PR #42 のチェック」 | `gh pr checks <pr-number>` | | ログ分析 | 「CI失敗してるから見て」 | `gh run view --log --job` | ## Token Efficiency Rules(必須) - `--log` は必ず `--job <job-id>` と併用(全ジョブログは膨大) - `gh run watch` / `gh pr checks --watch` は使用禁止(トークン大量消費) - JSON 出力 + jq で必要なフィールドのみ取得する - Progressive Filtering: 一覧→絞り込み→詳細の順に段階的に情報取得 ## 各モードの実行概要 ### Mode 1: クイックステータス `gh run list --branch $(git branch --show-current) --limit 10` で一覧取得→テンプレート報告。 ### Mode 2: Run 詳細 `gh run view <run-id> --json status,conclusion,jobs,createdAt,updatedAt,headBranch,event` で詳細取得。 ### Mode 3: PR チェック `gh pr checks <pr-number>` でチェック状況取得。PR番号不明時はブランチから自動検出。 ### Mode 4: ログ分析(メイン) 1. `gh run list --status failure` で失敗 Run 特定 2. `gh run view <run-id> --json jobs` で失敗ジョブ特定 3. `gh run view <run-id> --log --job <job-id>` でログ取得(必ず --job 指定) 4. エラー箇所を分析し原因と修正方針を報告 ## 詳細リファレンス JSON 出力パターン、レポートテンプレート、ステータス一覧は `references/commands.md` を参照。 .github/skills/gh-workflow/references/commands.md(79行・266語) # gh-workflow コマンドリファレンス > SKILL.md から参照される詳細情報。Copilot が必要に応じて読み込む。 ## JSON Output Patterns よく使う構造化データ取得パターン: ```bash # Run 一覧を JSON で取得 gh run list --limit 10 --json databaseId,displayTitle,status,conclusion,headBranch,createdAt,event # Run 内のジョブ詳細 gh run view <run-id> --json jobs --jq '.jobs[] | {id: .databaseId, name, status, conclusion, startedAt, completedAt}' # 失敗ジョブのみ抽出 gh run view <run-id> --json jobs --jq '.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name}' # PR チェックの詳細(JSON) gh pr checks <pr-number> --json name,state,description,detailsUrl ``` ## Report Template 以下のテンプレートを使って結果を報告する。 ### ステータス一覧 ``` ## Workflow Status | # | Workflow | Branch | Status | Conclusion | Triggered | |---|---------|--------|--------|------------|-----------| | 1 | <name> | <branch> | <status> | <conclusion> | <event> | ``` ### 失敗分析 ``` ## Failure Analysis **Run:** #<run-id> - <workflow-name> **Branch:** <branch> **Failed Jobs:** | Job | Error Summary | |-----|--------------| | <job-name> | <error-summary> | **Root Cause:** <分析結果> **Suggested Fix:** <修正方針> ``` ## Status / Conclusion Reference | 値 | 種類 | 意味 | |----|------|------| | `queued` | status | キュー待ち | | `in_progress` | status | 実行中 | | `completed` | status | 完了 | | `waiting` | status | 承認待ち | | `success` | conclusion | 成功 | | `failure` | conclusion | 失敗 | | `cancelled` | conclusion | キャンセル済み | | `skipped` | conclusion | スキップ | | `timed_out` | conclusion | タイムアウト | | `action_required` | conclusion | アクション要求 | | `neutral` | conclusion | 中立(情報のみ) | | `stale` | conclusion | 古い結果 | ## Important Notes - **gh CLI 未認証の場合**: `gh auth status` で認証状態を確認し、未認証ならユーザーに通知 - **リポジトリ外で実行した場合**: エラーをユーザーに通知 - **大量のログ出力**: 必ず `--job` で絞り込み、それでも長い場合は主要なエラー行のみ抽出 - **進行中の Run**: ステータスが `in_progress` の場合、完了を待たずに現在の状態を報告(`watch` は使わない) copilot-instructions.md に追記するルーティング ## Agent Skills Routing | キーワード | スキル | パス | |-----------|--------|------| | CI確認, Workflow, GitHub Actions, ログ分析, CI失敗, pipeline debug | gh-workflow | `.github/skills/gh-workflow/SKILL.md` | 参考リンク 公式ドキュメント VS Code Docs – Agent Skills agentskills.io Specification VS Code Docs – Copilot Customization 関連 Issue microsoft/vscode-copilot-release#14131 — VS Code バリデータが allowed-tools を未認識 関連記事 【2026年版】Agent Skills 入門:SKILL.md の基本から実践まで Claude CodeからGitHub Copilotへ移植したらAgent Skillsが動かない?原因と解決策 gh CLI × Claude Code で GitHub Actions 失敗ログをチャットで即解決 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Actions失敗ログ、まだ手動で読む?Copilot Agent Skills で CI デバッグを自動化する実装ガイド first appeared on SIOS Tech Lab .
アバター
前回の記事ではSCANOSS GitHub Actionsを使った基本的なコードスキャン自動化の手順 を紹介しました。基本スキャンやポリシーチェック、サブディレクトリスキャンまで確認できたところで、次に気になるのはモノレポ構成への対応です。 scanPath パラメータ は単一の相対パスしか受け付けないため、複数のサブディレクトリを個別にスキャンするにはワークフロー側で工夫が必要です。本記事では、SCANOSS公式の MONOREPO_SETUP.md で推奨されている reusable workflow( workflow_call )パターン を使い、PRで変更されたコンポーネントだけを自動スキャンする構成を実際に構築・検証します。 この記事でわかること : モノレポで scanPath が抱える制約と解決策 reusable workflow( workflow_call )による共通スキャンワークフローの作り方 パスフィルターとの連携で、変更コンポーネントだけスキャンする構成 構築時のハマりポイント( permissions の配置)と解決策 前提条件 前回の記事 の内容を理解していること(基本スキャン、 scanPath の動作) GitHubリポジトリにActions実行権限があること リポジトリのSettings > Secretsに SCANOSS_KEY を登録済みであること scanoss/gha-code-scan@v1.5.0 を使用 検証に使用したリポジトリ構成 本記事の検証には、 application/tools/src/ 配下に複数のPythonパッケージを持つモノレポ構成のリポジトリを使用しました。 application/tools/src/ ├── blog_scraper/ # ブログスクレイピングツール(Pythonファイル9個) ├── svg_to_png/ # SVG→PNG変換ツール(Pythonファイル6個) ├── html_to_png/ # HTMLスクリーンショットツール ├── cleanup_articles/ # 記事クリーンアップツール ├── thumbnail_generator/ # サムネイル生成ツール ├── pv_analyzer/ # PV分析ツール └── notion_sync/ # Notion同期ツール このうち blog_scraper と svg_to_png の2ディレクトリをスキャン対象として検証します。 モノレポでのスキャン課題 前回の記事で紹介した scanPath パラメータ を使えば、スキャン対象をサブディレクトリに限定できます。しかし、 scanPath は 単一の相対パスしか受け付けません 。複数パスのカンマ区切りやワイルドカードには対応していません。 # これはできる scanPath: application/tools/src/blog_scraper # これはできない scanPath: application/tools/src/blog_scraper, application/tools/src/svg_to_png モノレポで複数パッケージを個別にスキャンするには、ワークフロー側で対応が必要です。SCANOSS公式の MONOREPO_SETUP.md では、GitHub Actionsの reusable workflow ( workflow_call )を使ったパターンが推奨されています。 ワークフローの全体構成 作成するファイルは以下の3つです。スキャン対象のコンポーネントが増えるたびにトリガーワークフローを1つ追加します。 .github/workflows/ ├── scanoss-monorepo-base.yml # ベースワークフロー(共通、1つだけ) ├── scanoss-monorepo-blog-scraper.yml # blog_scraper 用トリガー └── scanoss-monorepo-svg-to-png.yml # svg_to_png 用トリガー ファイル 役割 scanoss-monorepo-base.yml workflow_call で呼び出される共通スキャン処理。 scan_path を入力として受け取る scanoss-monorepo-blog-scraper.yml blog_scraper ディレクトリの変更時にベースワークフローを呼び出す scanoss-monorepo-svg-to-png.yml svg_to_png ディレクトリの変更時にベースワークフローを呼び出す ベースワークフローの作成 workflow_call で呼び出される共通のスキャンワークフローです。スキャン対象のパスを inputs.scan_path として受け取ります。 # .github/workflows/scanoss-monorepo-base.yml name: SCANOSS Monorepo Base Scan on: workflow_call: inputs: scan_path: description: 'Directory to scan' required: true type: string jobs: scanoss-scan: name: SCANOSS Scan (${{ inputs.scan_path }}) runs-on: ubuntu-latest permissions: contents: write pull-requests: write checks: write actions: read steps: - name: Checkout code uses: actions/checkout@v4 - name: Run SCANOSS Code Scan id: scanoss-scan uses: scanoss/gha-code-scan@v1.5.0 with: scanPath: ${{ inputs.scan_path }} api.key: ${{ secrets.SCANOSS_KEY }} permissions を ジョブレベル に配置している点が重要です。トップレベルに置くと startup_failure で失敗します。詳細は後述の「ハマりポイント」で説明します。 コンポーネント別トリガーワークフロー 各コンポーネントにつき1つのワークフローファイルを作成し、ベースワークフローを呼び出します。 # .github/workflows/scanoss-monorepo-blog-scraper.yml name: SCANOSS Scan - blog_scraper on: workflow_dispatch: permissions: contents: write pull-requests: write checks: write actions: read jobs: scan: uses: ./.github/workflows/scanoss-monorepo-base.yml with: scan_path: application/tools/src/blog_scraper secrets: inherit # .github/workflows/scanoss-monorepo-svg-to-png.yml name: SCANOSS Scan - svg_to_png on: workflow_dispatch: permissions: contents: write pull-requests: write checks: write actions: read jobs: scan: uses: ./.github/workflows/scanoss-monorepo-base.yml with: scan_path: application/tools/src/svg_to_png secrets: inherit secrets: inherit でリポジトリのSecretsを呼び出し先に渡します。これにより、ベースワークフロー側で ${{ secrets.SCANOSS_KEY }} が使えます。 まずはこの workflow_dispatch (手動実行)の構成で動作確認した後、次のセクションでパスフィルターを追加します。 パスフィルターとの連携 この構成の最大の利点は、 パスフィルター ( on.pull_request.paths )との組み合わせです。トリガーワークフローに paths を追加するだけで、 PRで変更されたコンポーネントだけスキャンを発火 させることができます。 # .github/workflows/scanoss-monorepo-blog-scraper.yml name: SCANOSS Scan - blog_scraper on: pull_request: paths: - 'application/tools/src/blog_scraper/**' workflow_dispatch: permissions: contents: write pull-requests: write checks: write actions: read jobs: scan: uses: ./.github/workflows/scanoss-monorepo-base.yml with: scan_path: application/tools/src/blog_scraper secrets: inherit 変更点は on: セクションに pull_request.paths を追加しただけです。 workflow_dispatch も残しておけば、手動実行も引き続き可能です。 この構成により、以下の動作になります。 PRの変更内容 blog_scraper スキャン svg_to_png スキャン blog_scraper/main.py を修正 実行される 実行されない svg_to_png/converter.py を修正 実行されない 実行される 両方を修正 実行される 実行される docs/ のみ修正 実行されない 実行されない モノレポで7つのパッケージがある場合、全パッケージをフルスキャンすると不要なAPI呼び出しと待ち時間が発生します。パスフィルターを使えば、PRで触ったコンポーネントだけをスキャンできるため、 レビューのフィードバックが速くなり、API利用量も抑えられます 。 ハマりポイント:permissionsの配置 構築時に遭遇したハマりポイントを紹介します。 ベースワークフローのpermissionsをトップレベルに置くとstartup_failure 最初にベースワークフローの permissions を ワークフローのトップレベル に配置したところ、呼び出し元のワークフローが startup_failure で即座に失敗しました。 # NG: トップレベルに配置すると startup_failure になる permissions: contents: write pull-requests: write checks: write actions: read jobs: scanoss-scan: ... startup_failure はログが一切生成されないため、原因の特定に手間取ります。 呼び出し元にpermissionsを書かないとstartup_failure ベースワークフローの permissions をジョブレベルに移動しても、呼び出し元ワークフローに permissions がないと同じく startup_failure で失敗します。 reusable workflow では、 呼び出し元のpermissionsが上限 となります。呼び出し元に permissions を書かない場合はリポジトリのデフォルト権限が適用されますが、SCANOSS Code Scan Actionが必要とする権限( contents: write 、 checks: write 等)が不足する場合に startup_failure が発生します。 解決策 2つの対応を同時に行います。 ベースワークフローの permissions を ジョブレベル に配置する 呼び出し元ワークフローにも permissions を 明示的に記述する 本記事のワークフロー例はすべてこの対応済みの構成です。 実行結果 workflow_dispatch で手動実行した結果です。 ワークフロー ステータス 実行時間 SCANOSS Scan – blog_scraper success 40秒 SCANOSS Scan – svg_to_png success 47秒 各コンポーネントが 独立したワークフローラン として実行されます。GitHub Actions UIでは別々のワークフローとして表示されるため、どのコンポーネントのスキャンが失敗したかが一目でわかります。 コンポーネントの追加方法 新しいパッケージ(例: html_to_png )をスキャン対象に追加する場合、トリガーワークフローを1つ作成するだけです。ベースワークフローの変更は不要です。 # .github/workflows/scanoss-monorepo-html-to-png.yml name: SCANOSS Scan - html_to_png on: pull_request: paths: - 'application/tools/src/html_to_png/**' workflow_dispatch: permissions: contents: write pull-requests: write checks: write actions: read jobs: scan: uses: ./.github/workflows/scanoss-monorepo-base.yml with: scan_path: application/tools/src/html_to_png secrets: inherit コピーして scan_path と paths を書き換えるだけなので、追加コストは低いです。 まとめ SCANOSS GitHub Actionsのreusable workflowパターンを使い、モノレポでコンポーネント別にスキャンを実行する構成を紹介しました。 項目 ポイント scanPathの制約 単一の相対パスのみ。複数パスやワイルドカードは非対応 公式推奨パターン reusable workflow( workflow_call )でベースワークフローを共通化 パスフィルター連携 on.pull_request.paths で変更コンポーネントだけスキャンを発火 permissions ベースワークフローはジョブレベル、呼び出し元にも明示的に記述 コンポーネント追加 トリガーワークフローを1ファイル追加するだけ 参考資料 関連リンク SCANOSS Code Scan Action(GitHub リポジトリ) SCANOSS MONOREPO_SETUP.md(公式モノレポガイド) SCANOSS Code Scan Action(GitHub Marketplace) GitHub Actions: Reusing workflows GitHub Actions: paths フィルター シリーズ記事 SCANOSS CLIでローカルスキャン:インストールからSBOM生成まで SCANOSS GitHub Actionsでコードスキャンを自動化 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SCANOSS GitHub Actions モノレポ対応:変更コンポーネントだけスキャンする構成 first appeared on SIOS Tech Lab .
アバター
ども!最近 GitHub Actions の Workflow を新規で書くことが増えている龍ちゃんです。Bicep のデプロイパイプラインとか、ブログのビルドチェックとか、地味に CI 周りをいじる機会が多いんですよね。 GitHub Actions のログ確認で作業が途切れる問題 CI をいじってると避けられないのが「CI が落ちたときの確認作業」。 VS Code の GitHub Actions 拡張機能 を入れてれば、どのステップが落ちたかはサイドバーのツリーで確認できます。ただ、拡張機能が教えてくれるのは 「どこが落ちたか」 まで。 「なぜ落ちたのか」をテキストレベルで確認する手段 が足りないんですよね。エラーログの中身を確認しようとすると、ブラウザで GitHub Actions の Run ページを開いて、ジョブを展開して、ログをコピーして…というエディタとブラウザの反復横跳びが発生します。コードの修正を Claude Code と進めてる最中だったら、作業の流れもそこで切れてしまう。 僕がほしかったのは、 「なぜ落ちたか」のログを VS Code 内でテキストとして取得できる仕組み でした。テキストさえ手に入れば、「どう直すか」は Claude Code が分析してくれます(人間もやるよ…)。そこで、 Claude Code の Skills (SKILL.md)と GitHub CLI( gh コマンド) を組み合わせて、CI のログ取得から分析までを VS Code 内で完結させるスキルを作りました。この記事ではスキルの使い方と作り方を解説します。SKILL.md やデモ用 Workflow の全文は付録に載せているので、すぐ試したい人はそちらもどうぞ。 gh-workflow スキルで何が変わるか /gh-workflow スキルを入れると、Claude Code のチャットで「CI 失敗してるから見て」と打つだけで、失敗した Run の特定 → ログの取得 → 原因分析 → 修正方針の提示まで一気に進みます。SKILL.md に gh CLI の手順を書いておくだけで、Claude がログ取得から分析まで全部やってくれる仕組みです。 なお、 GitHub Actions 拡張機能 は「どこが落ちたか」のツリー確認や YAML 編集支援が得意で、このスキルは「なぜ落ちたか」のログ取得と分析が得意です。置き換えではなく補完関係なので、両方入れておくのがおすすめです。 なぜ gh CLI + スキルを選んだか CI のログを VS Code 内で取得する方法は他にもあります。 GitHub MCP Server の Actions toolset を使えば、 list_workflow_runs や get_job_logs といったツールで同等のことが実現できます。機能的には十分ですが、MCP はツールの戻り値が大きくなりがちで、トークン消費が膨らみやすいというのはわりとあるあるな話です。また、Claude Code の組み込みツール(WebSearch / WebFetch)で GitHub の情報を取りにいくという手もなくはありません。 その上で gh CLI + SKILL.md を選んだのは、僕の環境に合っていたからです。 CLI があるなら CLI で叩きたい。 これは完全に好みの話ですが、サービスが公式 CLI を出しているなら、僕は CLI を選びがちです。MCP だとツールの戻り値をスキル側でコントロールしにくいけど、CLI なら --json や --jq で出力形式を自在に絞れる。SKILL.md に書く手順も Bash コマンドそのままなので、トークン効率のルールをスキル側で設計しやすいのが大きいです プロジェクトで利用実績があった。 Bicep との統合でシークレット登録に gh CLI を使っていたので、認証周りは設定済みだった 認証が gh auth で済む。 devcontainer の postCreateCommand に入れておけば、環境構築時に自動で認証できる SKILL.md 一枚で完結する。 Markdown ファイルを .claude/skills/gh-workflow/SKILL.md に置くだけで動く。Skills は description だけが常時コンテキストに入り、本文は呼び出し時のみロードされるので、普段の会話を圧迫しない 選択肢が複数ある中で gh CLI を選んだのは優劣の話ではなく、自分の環境と好みに合っていたからです。 gh CLI × Claude Code で CI ログを確認する(デモ) ここからは実際のデモです。前提として、 gh CLI がインストール済みで認証が完了している必要があります。 認証方法について : とりあえず試すなら gh auth login でブラウザ認証するのが一番手軽です。ただし OAuth はスコープが広くなりがちなので、運用するなら PAT(Personal Access Token)で最小限のスコープに絞るのがおすすめです。Fine-grained PAT なら対象リポジトリと Actions の読み取り権限だけに制限できます。 # PAT を使った認証 echo "<your-pat>" | gh auth login --with-token このリポジトリにデモ用の Workflow を2つ用意しました。 demo-success.yml : ファイル構造のチェックと YAML バリデーションを行う2ジョブ構成の CI パイプライン demo-failure.yml : 3ジョブ構成で、Test ジョブが意図的に失敗する Workflow(テスト失敗のログを再現) SKILL.md は4モード対応(クイックステータス / Run 詳細 / PR チェック / ログ分析)ですが、今回はデモ用 PR がないので3モードで見ていきます。 クイックステータス — 「CI 確認して」 一番シンプルなモードです。「CI 確認して」と打つと、スキルが gh run list を実行して直近の Workflow 実行状況を取得します。 completed failure Demo - Failure Workflow main workflow_dispatch 22430458939 22s 2026-02-26T06:20:53Z completed success Demo - CI Pipeline main workflow_dispatch 22430440934 21s 2026-02-26T06:20:13Z completed success Demo - CI Pipeline main workflow_dispatch 22430416062 46s 2026-02-26T06:19:12Z completed failure Demo - CI Pipeline main workflow_dispatch 22430368912 48s 2026-02-26T06:17:20Z この生データを、スキルのレポートテンプレートに従って整形します。 ## Workflow Status | # | Workflow | Branch | Status | Conclusion | Triggered | |---|---------|--------|--------|------------|-----------| | 1 | Demo - Failure Workflow | main | completed | failure | workflow_dispatch | | 2 | Demo - CI Pipeline | main | completed | success | workflow_dispatch | | 3 | Demo - CI Pipeline | main | completed | success | workflow_dispatch | | 4 | Demo - CI Pipeline | main | completed | failure | workflow_dispatch | ブラウザを開かなくても、今の CI の状況がチャット上で確認できます。 Run 詳細 — 「この Run の詳細を見て」 成功した Run の中身を確認したいこともあります。「この Run、ジョブの実行時間どのくらいだった?」みたいなとき、Run ID を指定すると gh run view <run-id> で詳細を取得します。 ✓ main Demo - CI Pipeline · 22430440934 Triggered via workflow_dispatch about 4 minutes ago JOBS ✓ Code Check in 6s (ID 64947983939) ✓ Config Validate in 7s (ID 64947996283) ジョブごとの実行時間とステータスが見えるので、Workflow の構成を変えた後に「ちゃんと速くなったか」を確認するのに使ったりします。 ログ分析 — 「CI 失敗してるから見て」 ここがメインの見せ場です。「CI 失敗してるから見て」と打つと、スキルが以下のステップを自動で実行します。 Step 1: 失敗した Run を特定する gh run list --branch main --status failure --limit 5 completed failure Demo - Failure Workflow main workflow_dispatch 22430458939 Step 2: 失敗したジョブを特定する gh run view 22430458939 --json jobs --jq '.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name}' {"id": 64948053027, "name": "Test"} --json と jq を使って、失敗したジョブだけを抽出しています。全ジョブの情報を取得する必要はなく、失敗したものだけに絞ることでトークンを節約しています。このあたりの工夫は SKILL.md の解説セクションで詳しく説明します。 Step 3: 失敗したジョブのログを取得する gh run view 22430458939 --log --job 64948053027 Test Run integration tests Running integration tests... Test Run integration tests Test Run integration tests FAILED: test_pipeline.py::test_full_pipeline Test Run integration tests AssertionError: Expected status code 200, got 503 Test Run integration tests File: tests/test_pipeline.py, line 42 Test Run integration tests assert response.status_code == 200 Test Run integration tests Test Run integration tests 1 passed, 1 failed in 3.42s Test Run integration tests ##[error]Process completed with exit code 1. --log には必ず --job を付けています。これがないと全ジョブのログが流れてきてトークンを大量に消費するので、失敗したジョブだけに絞るのが鉄則です。 Step 4: Claude が分析してレポートを出す ログを取得したら、Claude がエラー内容を読み取って原因分析と修正方針をまとめます。 ## Failure Analysis **Run:** #22430458939 - Demo - Failure Workflow **Branch:** main **Failed Jobs:** | Job | Error Summary | |-----|--------------| | Test | integration test で AssertionError(期待 200、実際 503) | **Root Cause:** test_pipeline.py の test_full_pipeline テストで、API レスポンスのステータスコードが 期待値 200 に対して 503 (Service Unavailable) を返している。 **Suggested Fix:** - テスト対象の API サーバーが起動しているか確認 - urllib3 のバージョン互換性を確認(requests 2.31.0 は urllib3<3 を要求) 「CI 失敗してるから見て」の一言で、ここまで出てきます。手作業だと Run を探して、ジョブを開いて、ログを読んで…とやっていた調査が全部自動で走り、Claude がそのまま分析に入ります。コードの修正をしていた会話の流れのまま「じゃあこう直しましょう」と続けられるのがポイントです。 このデモではテスト失敗を再現するために作った Workflow を使っています。 demo-failure.yml は echo で pytest 風のエラー出力を再現して exit 1 する構成で、実際のプロジェクトではここに本物のテスト結果が出ます。 SKILL.md の設計 — トークン効率とログ分析の工夫 ここからは「どうやって作ったか」の話です。スキルの実体は .claude/skills/gh-workflow/SKILL.md という Markdown ファイル1枚(全文は付録参照)。自分のプロジェクトでも同じような仕組みを作りたい人向けに、中身をパートごとに見ていきます。 メタデータ( --- で囲まれたヘッダー部分) --- name: gh-workflow description: | This skill should be used when the user asks to "CI確認して", "Workflow確認", "CIの状態を見て", "ワークフローのログを見て", "CI失敗してるから直して", "PRのチェック状況は", "/gh-workflow", or needs to check GitHub Actions workflow status or analyze CI logs. allowed-tools: Bash, Read --- description にスキルの起動条件を書いておくと、Claude がユーザーの発言を見て自動的にこのスキルを呼び出します。「CI確認して」のような日本語のトリガーも列挙できます。 allowed-tools: Bash, Read で、このスキルが使えるツールを制限しています。 Skills は description だけが常時コンテキストに入っていて、本文はスキルが呼び出されたときにだけロードされるので、普段の会話には影響しません。 モード判定 | モード | トリガー例 | 主なコマンド | |--------|-----------|-------------| | クイックステータス | 「CI確認して」 | `gh run list` | | Run 詳細 | 「Run #123 を見て」 | `gh run view <run-id>` | | PR チェック | 「PR #42 のチェック状況」 | `gh pr checks <pr-number>` | | ログ分析 | 「CI失敗してるから見て」 | `gh run view --log --job` | ユーザーの一言からどのモードを実行するかを、このテーブルで Claude に判断させています。 トークン効率のルール ここが SKILL.md で一番重要なパートです。ただ手順を書くだけなら誰でもできますが、 トークンを無駄遣いしないルール を入れることでスキルの実用性が大きく変わります。 **必ず守ること:** - `--log` は必ず `--job <job-id>` と併用する(全ジョブログは膨大でトークンを大量消費する) - `gh run watch` は使用禁止(リアルタイム出力でトークンを大量消費する) - `gh pr checks --watch` は使用禁止(同上) - JSON 出力 + jq で必要なフィールドのみ取得する --log を --job なしで実行すると、全ジョブのログが一気に流れてきます。CI のログは長くなりがちなので、これをそのままコンテキストに入れるとトークンがあっという間になくなります。 --job で失敗したジョブだけに絞るのが鉄則です。 gh run watch を禁止しているのも同じ理由です。リアルタイム出力がずっと流れ続けるとトークンを食い尽くします。進行中の Run は「今の状態を確認する」だけにして、完了を待ちません。 JSON + jq のルールは、 gh run view --json jobs --jq '.jobs[] | select(.conclusion == "failure")' のように必要なフィールドだけ抽出するためのものです。構造化データで取れるものは構造化データで取ります。 ログ分析モードの設計意図 デモセクションで「何が起きるか」を見ましたが、ここでは「なぜこの設計にしたか」を補足します。SKILL.md に以下の手順を書いておくと、Claude はこの順番で gh CLI を実行します。 ### Step 1: 失敗した Run の特定 gh run list --branch $(git branch --show-current) --status failure --limit 5 ### Step 2: 失敗したジョブの特定 gh run view <run-id> --json jobs --jq '.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name}' ### Step 3: 失敗ジョブのログ取得 gh run view <run-id> --log --job <job-id> ### Step 4: エラー分析と報告 Run 全体 → 失敗ジョブ → 失敗ジョブのログ、と段階的に絞り込んでいく設計です。最初から全部のログを取るのではなく、必要なものだけに辿り着くようにステップを分けています。この段階的な絞り込みがトークン効率のルールと連動していて、各ステップで情報量を削ることで Claude に渡すデータを最小限にしています。 レポートテンプレート ## Failure Analysis **Run:** #<run-id> - <workflow-name> **Branch:** <branch> **Failed Jobs:** | Job | Error Summary | |-----|--------------| | <job-name> | <error-summary> | **Root Cause:** <分析結果> **Suggested Fix:** <修正方針> 出力フォーマットを統一しておくと、毎回同じ形式で結果が出るので読み方が一定になります。「Root Cause を見て、Suggested Fix を見る」というパターンに慣れれば、確認作業がさらに速くなります。 改善の余地 gh CLI には --log-failed という失敗ステップのみ取得するオプションもあります。SKILL.md にはまだ組み込んでいませんが、 --log --job と組み合わせればさらにログを絞り込めるので、今後の改善候補です。 もう一つ気になっているのが、ログが大量になるケースです。 --job で絞っても、ジョブ自体のログが長いとトークンを圧迫する可能性があります。たとえばログを取得してファイルに保存だけしておくとか、失敗したログをテキストファイルとして書き出す CLI ツールを作るとか、いろいろ模索できそうだなと思っています。まだやってないですけどね。 まとめ ざっくり振り返ると、Before / After はこうなりました。 Before : CI が落ちたら、ブラウザで Run を探して、ジョブを開いて、ログをコピーして、Claude に貼って、文脈を説明する。コードの修正中だったら作業の流れが切れる。 After : 「CI 失敗してるから見て」と打つ。スキルが gh CLI でログを集めて、Claude が分析して修正方針を出す。コードの修正中でも会話の流れはそのまま。 これを実現しているのは、 .claude/skills/gh-workflow/SKILL.md という Markdown ファイル1枚です。特別なインフラもサーバーも不要で、プロジェクトにファイルを1つ追加するだけで動きます。 GitHub Actions 拡張機能 とは補完関係で使うのがおすすめです。YAML の編集支援や「どこが落ちたか」の確認は拡張機能が得意で、「なぜ落ちたか」のログ取得と分析はスキル + Claude が得意です。 SKILL.md は他のプロジェクトでもそのまま使えます。 gh CLI のコマンドを自分のプロジェクトに合わせて書き換えるだけで、同じ仕組みが動きます。トークン効率のルール( --log には --job 必須、 watch 禁止、JSON + jq で絞り込み)はどのプロジェクトでも共通なので、テンプレートとして参考にしてもらえると思います。 ほなまた〜 シリーズ記事 記事 内容 概要編 — ブラウザなし GitHub 操作を実現する 問題提起と解決策の全体像 この記事(CI 編) 失敗ログの取得から原因分析まで Gist 編 — Gist 操作をチャット1行で完結させる Gist の検索・保存・更新 付録 SKILL.md 全文 .claude/skills/gh-workflow/SKILL.md の内容です。 --- name: gh-workflow description: | This skill should be used when the user asks to "CI確認して", "Workflow確認", "CIの状態を見て", "ワークフローのログを見て", "CI失敗してるから直して", "PRのチェック状況は", "/gh-workflow", or needs to check GitHub Actions workflow status or analyze CI logs. allowed-tools: Bash, Read --- # GitHub Actions Workflow Status & Log Analysis gh CLI を使って GitHub Actions の Workflow ステータス確認・ログ分析を行います。 ## Mode Selection ユーザーの指示から以下の4モードを自動判定して実行する。 | モード | トリガー例 | 主なコマンド | |--------|-----------|-------------| | クイックステータス | 「CI確認して」「Workflowの状態を見て」 | `gh run list` | | Run 詳細 | 「Run #123 を見て」「この Run の詳細」 | `gh run view <run-id>` | | PR チェック | 「PR #42 のチェック状況」「PRのCI」 | `gh pr checks <pr-number>` | | ログ分析 | 「CI失敗してるから見て」「エラーログ確認」 | `gh run view --log --job` | ## Token Efficiency Rules **必ず守ること:** - `--log` は必ず `--job <job-id>` と併用する(全ジョブログは膨大でトークンを大量消費する) - `gh run watch` は使用禁止(リアルタイム出力でトークンを大量消費する) - `gh pr checks --watch` は使用禁止(同上) - JSON 出力 + jq で必要なフィールドのみ取得する --- ## Mode 1: クイックステータス 現在のブランチまたはリポジトリの Workflow 実行状況を一覧表示する。 ### Step 1: Run 一覧の取得 ```bash # 現在のブランチの最新10件 gh run list --branch $(git branch --show-current) --limit 10 # 全ブランチの最新10件(ブランチ指定なし) gh run list --limit 10 ``` ### Step 2: 結果を報告テンプレートで表示 ## Mode 2: Run 詳細 特定の Run ID の詳細情報を取得する。 ### Step 1: Run の詳細取得 ```bash # Run の詳細表示 gh run view <run-id> # JSON で構造化データを取得 gh run view <run-id> --json status,conclusion,jobs,createdAt,updatedAt,headBranch,event ``` ### Step 2: ジョブごとのステータスを整理して報告 ## Mode 3: PR チェック PR に紐づくチェック状況を確認する。 ### Step 1: PR チェックの取得 ```bash # PR のチェック状況 gh pr checks <pr-number> # 現在のブランチの PR チェック(PR 番号不明時) gh pr checks ``` ### Step 2: チェック結果を報告テンプレートで表示 ## Mode 4: ログ分析 失敗した Workflow のログを取得・分析する。 ### Step 1: 失敗した Run の特定 ```bash # 現在のブランチで失敗した Run を検索 gh run list --branch $(git branch --show-current) --status failure --limit 5 ``` ### Step 2: 失敗したジョブの特定 ```bash # Run 内のジョブ一覧を JSON で取得 gh run view <run-id> --json jobs --jq '.jobs[] | {name, status, conclusion}' ``` ### Step 3: 失敗ジョブのログ取得 ```bash # 特定ジョブのログのみ取得(必ず --job を指定) gh run view <run-id> --log --job <job-id> ``` **重要:** `--log` は必ず `--job <job-id>` と併用する。`--job` なしだと全ジョブのログが出力されトークンを大量消費する。 ### Step 4: エラー分析と報告 ログからエラー箇所を特定し、原因と修正方針を報告する。 ## Status / Conclusion Reference | 値 | 種類 | 意味 | |----|------|------| | `queued` | status | キュー待ち | | `in_progress` | status | 実行中 | | `completed` | status | 完了 | | `waiting` | status | 承認待ち | | `success` | conclusion | 成功 | | `failure` | conclusion | 失敗 | | `cancelled` | conclusion | キャンセル済み | | `skipped` | conclusion | スキップ | | `timed_out` | conclusion | タイムアウト | | `action_required` | conclusion | アクション要求 | | `neutral` | conclusion | 中立(情報のみ) | | `stale` | conclusion | 古い結果 | ## JSON Output Patterns よく使う構造化データ取得パターン: ```bash # Run 一覧を JSON で取得 gh run list --limit 10 --json databaseId,displayTitle,status,conclusion,headBranch,createdAt,event # Run 内のジョブ詳細 gh run view <run-id> --json jobs --jq '.jobs[] | {id: .databaseId, name, status, conclusion, startedAt, completedAt}' # 失敗ジョブのみ抽出 gh run view <run-id> --json jobs --jq '.jobs[] | select(.conclusion == "failure") | {id: .databaseId, name}' # PR チェックの詳細(JSON) gh pr checks <pr-number> --json name,state,description,detailsUrl ``` ## Report Template 以下のテンプレートを使って結果を報告する。 ### ステータス一覧 ``` ## Workflow Status | # | Workflow | Branch | Status | Conclusion | Triggered | |---|---------|--------|--------|------------|-----------| | 1 | <name> | <branch> | <status> | <conclusion> | <event> | ``` ### 失敗分析 ``` ## Failure Analysis **Run:** #<run-id> - <workflow-name> **Branch:** <branch> **Failed Jobs:** | Job | Error Summary | |-----|--------------| | <job-name> | <error-summary> | **Root Cause:** <分析結果> **Suggested Fix:** <修正方針> ``` ## Important Notes - **gh CLI 未認証の場合**: `gh auth status` で認証状態を確認し、未認証ならユーザーに通知 - **リポジトリ外で実行した場合**: エラーをユーザーに通知 - **大量のログ出力**: 必ず `--job` で絞り込み、それでも長い場合は主要なエラー行のみ抽出 - **進行中の Run**: ステータスが `in_progress` の場合、完了を待たずに現在の状態を報告(`watch` は使わない) デモ用 Workflow demo-success.yml 2ジョブ構成の CI パイプライン。ファイル構造のチェックと YAML バリデーションを行います。 name: Demo - CI Pipeline on: workflow_dispatch: jobs: check: name: Code Check runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Check file structure run: | echo "Checking project files..." test -f pyproject.toml && echo "OK: pyproject.toml" test -f CLAUDE.md && echo "OK: CLAUDE.md" echo "All checks passed." validate: name: Config Validate runs-on: ubuntu-latest needs: check steps: - name: Checkout repository uses: actions/checkout@v4 - name: Validate YAML configs run: | echo "Validating workflow files..." ls .github/workflows/*.yml | wc -l | xargs -I{} echo "Found {} workflow files" echo "Validation complete." demo-failure.yml 3ジョブ構成で、Test ジョブが意図的に失敗する Workflow。echo で pytest 風のエラー出力を再現しています。 name: Demo - Failure Workflow on: workflow_dispatch: jobs: setup: name: Setup runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Verify environment run: | echo "Environment check..." echo "OS: $(uname -s)" echo "Setup completed." test: name: Test runs-on: ubuntu-latest needs: setup steps: - name: Checkout repository uses: actions/checkout@v4 - name: Run unit tests run: | echo "Running unit tests..." echo "test_fetch_article ... PASSED" echo "test_parse_html ... PASSED" echo "Unit tests passed." - name: Run integration tests run: | echo "Running integration tests..." echo "" echo "FAILED: test_pipeline.py::test_full_pipeline" echo " AssertionError: Expected status code 200, got 503" echo " File: tests/test_pipeline.py, line 42" echo " assert response.status_code == 200" echo "" echo "1 passed, 1 failed in 3.42s" exit 1 deploy: name: Deploy runs-on: ubuntu-latest needs: test steps: - name: Deploy application run: echo "Deploying..." ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post gh CLI × Claude Code で GitHub Actions 失敗ログをチャットで即解決 first appeared on SIOS Tech Lab .
アバター
ども!最近 Gist の出番が増えた龍ちゃんです。スニペットの保存はもちろん、プロジェクト横断で使い回すプロンプトテンプレートとか、ちょっとした設定ファイルとか。操作が面倒で使ってなかったんですけど、チャットで完結するようにしたら話が変わりました。 .claude/skills/gh-gist/SKILL.md という Markdown ファイル1枚で、Gist の検索・取得・保存・更新がチャットの一言で動きます。この記事ではスキルの使い方と、CRUD をスキル化するときのトークン効率・安全設計の考え方を解説します。 GitHub Gist の課題 — 検索性と保存コスト Gist 自体は優秀な仕組みなんですよね。URL 一つでコードを共有できて、バージョン管理もついてくる。複数ファイルをまとめられるし、secret / public の切り替えもできます。「ちょっとしたコードの保存と共有」にちょうどいい粒度で設計されています。 でも実態、使いこなせてなくないですか? 僕の場合がまさにそうでした。問題は2つあります。 検索性が弱い。 Gist が増えてくると、Web UI では目視スクロールで探すことになります。「あのスニペットどこだっけ」が頻発して、見つからないとそのまま諦めてしまう。 保存のハードルが高い。 ブラウザで gist.github.com を開いて、ファイル名を入力して、コードを貼り付けて、description を書いて、public か secret を選んで、Create ボタンを押す。コードを書いてる最中にこの手順をやる気にはなれないんですよね。 結果どうなるかというと、「保存するほどでもないか」→ 使わなくなる → 存在を忘れる。もったいない。 操作コストが下がれば話は変わる 「Gist に保存して」「Python のスニペット取ってきて」で済むなら話は別です。VS Code のチャットで完結するなら、コーディングの流れも切れません。 スニペットの保存・取得だけじゃなくて、操作コストが下がると 使い方の発想自体が変わる んですよね。たとえばプロジェクト横断で使い回すファイル(人格設定、プロンプトテンプレートなど)を Gist で一元管理して、必要なプロジェクトにスキルで引っ張ってくるとか。1ファイル完結 × 複数プロジェクトで共有 × バージョン管理つき、という Gist の粒度にぴったりハマります。「Gist = ちょっとしたコード置き場」から「プロジェクト横断のファイル管理」へ。 これを Claude Code の Skills (SKILL.md)と GitHub CLI( gh コマンド) で作りました。この記事ではスキルの使い方と作り方を解説します。SKILL.md の全文は付録に載せているので、すぐ試したい人はそちらもどうぞ。 なぜ gh CLI + スキルを選んだか Gist をエディタ内で操作する方法は他にもあります。 GitHub の Web UI は Gist の作成・閲覧・編集が一通りできますが、前述のとおり検索性が弱い。何よりコードを書いてる最中にブラウザに切り替えると作業の流れが切れます。 GitHub MCP Server には Gist 関連のツールがありません(2026年2月時点)。仮に対応していたとしても、MCP はツールの戻り値が大きくなりがちでトークン消費が膨らみやすいのはわりとあるあるな話です。 その上で gh CLI + SKILL.md を選んだのは、Gist の操作に合っていたからです。 gh CLI が Gist のサブコマンドを持っている。 gh gist list / view / create / edit / delete で CRUD 全部できます。 --filter や --raw --filename で出力を絞れるので、トークン効率のルールを設計しやすい 認証が gh auth で済む。 devcontainer の postCreateCommand に入れておけば環境構築時に自動で認証できる SKILL.md 一枚で完結する。 .claude/skills/gh-gist/SKILL.md に Markdown で手順を書くだけで動きます。 Skills は description だけが常時コンテキストに入り、本文は呼び出し時のみロードされるので、普段の会話を圧迫しません 選択肢が複数ある中で gh CLI を選んだのは優劣の話ではなく、自分の環境と用途に合っていたからです。 gh CLI × Claude Code で Gist を操作する(デモ) ここからは実際のデモです。検証用に Gist を事前に作成して、Claude Code のチャットで操作しました。前提として、 gh CLI がインストール済みで認証が完了している必要があります。 認証方法について : とりあえず試すなら gh auth login でブラウザ認証するのが一番手軽です。ただし OAuth はスコープが広くなりがちなので、運用するなら PAT(Personal Access Token)で最小限のスコープに絞るのがおすすめです。Classic PAT なら gist スコープだけで Gist の全操作が動きます。 # PAT を使った認証 echo "<your-pat>" | gh auth login --with-token SKILL.md は5モード対応(一覧 / 取得 / 保存 / 更新 / データ管理)ですが、よく使う3モードをデモで見ていきます。 取得 —「Python のスニペット取ってきて」 「Gist から Python のスニペット取ってきて」と打つと、スキルが以下のステップを自動で実行します。 Step 1: キーワードで Gist を検索 gh gist list --filter "Python" e7236579... Python useful snippets (updated) 2 files secret 2026-02-26T14:20:47Z Step 2: ファイル一覧を確認 gh gist view e72365796014c25d70629f6d8cfe01bc --files snippets.py todo.md Step 3: 目的のファイルを取得 gh gist view e72365796014c25d70629f6d8cfe01bc --raw --filename snippets.py """Python useful snippets - よく使うコードスニペット集""" # --- flatten nested list --- def flatten(nested: list) -> list: ... 「Python のスニペット」という曖昧な指示から、3ステップで目的のファイルに辿り着いています。 --filter で絞り → --files でファイル一覧 → --filename で中身取得。この 段階的な絞り込み がトークン効率のルールと連動していて、各ステップで情報量を削っています。このあたりの工夫は SKILL.md の解説セクションで詳しく説明します。 保存 —「このコードを Gist に保存して」 コードを渡して「Gist に保存して、description は Verification test で」と打ちます。 Step 1: 一時ファイルに書き出し ユーザーが指定したコードを application/tools/tmp/hello.py に書き出します。 gh gist create はファイルパスを引数に取るので、一時ファイル経由で作成する形です。 Step 2: Gist を作成 gh gist create application/tools/tmp/hello.py -d "Verification test" https://gist.github.com/user/xxxx デフォルトは secret で作成されます。public にしたい場合は「public で」と明示する必要があり、SKILL.md にそのルールを書いてあります。書き込み操作をスキル化するときに入れておきたい安全策の一つです。 更新 —「さっきの Gist にファイル追加して」 「さっき保存した Gist に README.md を追加して」と打つと、スキルが直前の会話のコンテキストから Gist ID を拾い、 gh gist edit でファイルを追加します。 ファイル追加 gh gist edit <gist-id> --add application/tools/tmp/README.md description 変更 gh gist edit <gist-id> --desc "Verification test (updated)" gh gist edit はデフォルトだとエディタが開いて対話的な編集になります。Claude Code から使うにはオプション引数で非対話的に更新する必要があって、このあたりも SKILL.md に手順を書いてあります。 SKILL.md の設計 — トークン効率と安全策 ここからは「どうやって作ったか」の話です。スキルの実体は .claude/skills/gh-gist/SKILL.md という Markdown ファイル1枚(全文は付録参照)。中身をパートごとに見ていきます。 メタデータ( --- で囲まれたヘッダー部分) --- name: gh-gist description: | This skill should be used when the user asks to "Gist一覧", "Gistに保存して", "Gistから取ってきて", "Gistを更新して", "コードをGistに", "Gist確認して", "/gh-gist", or needs to manage GitHub Gists via gh CLI. allowed-tools: Bash, Read --- description にスキルの起動条件を書いておくと、Claude がユーザーの発言を見て自動的にスキルを呼び出します。「Gist に保存して」のような日本語のトリガーも列挙できます。 allowed-tools: Bash, Read で、このスキルが使えるツールを制限しています。 Skills は description だけが常時コンテキストに入っていて、本文はスキルが呼び出されたときにだけロードされるので、普段の会話には影響しません。 モード判定 | モード | トリガー例 | 主なコマンド | |--------|-----------|-------------| | 一覧 | 「Gist一覧」 | `gh gist list` | | 取得 | 「Gistから取ってきて」 | `gh gist view --raw --filename` | | 保存 | 「Gistに保存して」 | `gh gist create` | | 更新 | 「Gistを更新して」 | `gh gist edit` | | データ管理 | 「Gistのデータ更新して」 | `gh gist view` + jq + `gh gist edit` | ユーザーの一言からどのモードを実行するかを、このテーブルで Claude に判断させています。 CI 編 が読み取り中心の4モードだったのに対して、Gist 編は CRUD 操作を含む5モード構成です。 トークン効率のルール ここが SKILL.md で一番重要なパートです。 CI 編 では --log に --job 必須・ watch 禁止というルールでしたが、Gist 編ではサブコマンドが違う分、ルールの中身も変わります。ただし 「必要な情報だけに絞り込む」 という設計思想は共通です。 **必ず守ること:** - `gh gist list --include-content` は使用禁止(全 Gist の中身を取得してトークンを大量消費する) - `gh gist view` で複数ファイル Gist を扱う場合は必ず `--filename` で絞る - `gh gist view --files` でファイル一覧を先に確認してから `--raw --filename` で中身取得 --include-content を禁止しているのは、 gh gist list に付けると全 Gist の中身まで取得してしまうからです。 --filter だけで description とファイル名は検索できるので、中身を全部取る必要はありません。 --filename を必須にしているのは、複数ファイルの Gist で --filename なしだと全ファイルが連結出力されるからです。検証では2ファイルの Gist で --filename ありだと6行、なしだと37行の出力差がありました。ファイル数が多い Gist だとこの差はさらに開きます。 デモで見た「 --filter → --files → --raw --filename 」という段階的な絞り込みも、このルールに基づいています。最初から全部取るのではなく、ステップを分けて必要な情報だけに辿り着く設計です。 --raw の落とし穴 検証で見つけた話なんですが、 gh gist view <id> --raw を --filename なしで実行すると、description が出力の先頭行に入ります。 $ gh gist view e72365796014c25d70629f6d8cfe01bc --raw Python useful snippets ← description が1行目に出る """Python useful snippets - よく使うコードスニペット集""" ... ターミナルで見る分には気にならないんですけど、 > file.json でリダイレクトすると JSON の先頭に description が混入してパースが壊れます。後述するデータ管理モードで JSON を取得→更新するフローがあるので、SKILL.md に 「 --raw は必ず --filename と併用する」 というルールを明記して、Claude が --filename なしで実行するのを防いでいます。 書き込み操作の安全策 CI 編 のスキルは読み取り専用でしたが、Gist 編は CRUD 全操作を含むので、安全策の設計が必要になります。 secret デフォルト : --public はユーザーが「public で」と明示した場合のみ使う。意図せず公開されるのを防ぐ 削除前確認 : gh gist delete はユーザーに確認を取ってから実行する --yes フラグ : 非インタラクティブ環境(Claude Code)では削除時に --yes が必須。検証中に --yes なしで実行してエラーになって気づいた 一時ファイル経由 : 保存・更新は application/tools/tmp/ に一時ファイルを書き出してからコマンドに渡す 読み取り専用のスキルにはない設計ですが、CRUD をスキル化するなら、こういう安全策のセットはテンプレートとして使い回せると思います。 データ管理モード SKILL.md には5つ目のモードとして「データ管理」を入れています。description に [data] プレフィックスを付けてデータ用 Gist を識別する規約で、JSON の取得 → jq で変換 → Gist に反映、というフローを定義しています。Gist を簡易 DB 的に使う発想ですね。実用レベルかはまだ試行中ですが、仕組みとしては動きます。興味がある人は付録の SKILL.md 全文を参照してください。 改善の余地 gh CLI には gh gist clone でローカルに落として編集→ push するフローや、 gh gist rename でのファイル名変更もあります。SKILL.md にはまだ組み込んでいませんが、使いながら必要になったら足していく予定です。 まとめ ざっくり振り返ると、Before / After はこうなりました。 Before : Gist は便利な仕組みだけど、操作コストが高くて使いこなせていなかった。保存するにもブラウザ、探すにもブラウザ。結局「保存するほどでもないか」で使わなくなる。 After : 「Gist に保存して」「スニペット取ってきて」とチャットで打つだけ。VS Code から離れない。操作コストが下がると、Gist を「使おう」と思えるようになる。 これを実現しているのは .claude/skills/gh-gist/SKILL.md という Markdown ファイル1枚です。特別なインフラもサーバーも不要で、プロジェクトにファイルを1つ追加するだけで動きます。 トークン効率のルール( --include-content 禁止、 --filename 必須、段階的絞り込み)は gh CLI スキル共通のパターンとして使えます。書き込み操作の安全策(secret デフォルト、削除前確認、 --yes )は CRUD をスキル化するときの設計テンプレートになると思います。 ほなまた〜 シリーズ記事 記事 内容 概要編 — ブラウザなし GitHub 操作を実現する 問題提起と解決策の全体像 CI 編 — GitHub Actions 失敗ログをチャットで即解決 失敗ログの取得から原因分析まで この記事(Gist 編) Gist の検索・保存・更新 付録 SKILL.md 全文 .claude/skills/gh-gist/SKILL.md の内容です。 --- name: gh-gist description: | This skill should be used when the user asks to "Gist一覧", "Gistに保存して", "Gistから取ってきて", "Gistを更新して", "コードをGistに", "Gist確認して", "/gh-gist", or needs to manage GitHub Gists via gh CLI. allowed-tools: Bash, Read --- # GitHub Gist Management gh CLI を使って GitHub Gist の管理(一覧・取得・保存・更新・データ管理)を行います。 ## Mode Selection ユーザーの指示から以下の5モードを自動判定して実行する。 | モード | トリガー例 | 主なコマンド | |--------|-----------|-------------| | 一覧 | 「Gist一覧」「Gist確認して」 | `gh gist list` | | 取得 | 「Gistから取ってきて」「Gistの中身見せて」 | `gh gist view --raw --filename` | | 保存 | 「Gistに保存して」「コードをGistに」 | `gh gist create` | | 更新 | 「Gistを更新して」「Gistに追加して」 | `gh gist edit` | | データ管理 | 「Gistのデータ更新して」「Gistの設定を変更」 | `gh gist view --raw` + jq + `gh gist edit` | ## Token Efficiency Rules **必ず守ること:** - `gh gist list --include-content` は使用禁止(全 Gist の中身を取得してトークンを大量消費する) - `gh gist view --raw` を `--filename` なしで使うと description が先頭行に混入する。特にリダイレクトで JSON/YAML を取得する場合は **必ず `--filename` を指定する**(パースが壊れる) - `gh gist view` で複数ファイル Gist を扱う場合は必ず `--filename` で絞る(全ファイル連結出力になる) - `gh gist view --files` でファイル一覧を先に確認してから `--raw --filename` で中身を取得する - 新規作成時はデフォルト secret(`--public` はユーザーの明示的指示がある場合のみ) - データ管理モードでは `application/tools/tmp/` に一時ファイルを書き出して `gh gist edit` で反映 --- ## Mode 1: 一覧 自分の Gist 一覧を表示する。 ### Step 1: Gist 一覧の取得 ```bash # 最新10件 gh gist list --limit 10 # フィルタ付き(description / ファイル名で検索) gh gist list --filter "keyword" # public のみ / secret のみ gh gist list --public gh gist list --secret ``` ### Step 2: 結果を報告テンプレートで表示 ## Mode 2: 取得 特定の Gist からファイル内容を取得する。 ### Step 1: 対象 Gist の特定 ユーザーが Gist ID / URL を指定していない場合は一覧から検索する。 ```bash # キーワードで検索 gh gist list --filter "keyword" ``` ### Step 2: ファイル一覧の確認 ```bash # Gist 内のファイル名を確認 gh gist view <gist-id> --files ``` ### Step 3: ファイル内容の取得 ```bash # 特定ファイルの内容を raw で取得 gh gist view <gist-id> --raw --filename <filename> ``` **重要:** 複数ファイル Gist では必ず `--filename` を指定する。省略すると全ファイルが出力されトークンを大量消費する。 ## Mode 3: 保存 新しい Gist を作成する。 ### Step 1: ファイルの準備 ユーザーが指定したコードやテキストを `application/tools/tmp/` に一時ファイルとして書き出す。 ### Step 2: Gist の作成 ```bash # 単一ファイル(secret、デフォルト) gh gist create application/tools/tmp/<filename> -d "description" # 複数ファイル gh gist create application/tools/tmp/file1 application/tools/tmp/file2 -d "description" # public(ユーザーが明示的に指示した場合のみ) gh gist create application/tools/tmp/<filename> -d "description" --public # stdin から作成 echo "content" | gh gist create -f <filename> -d "description" ``` **重要:** デフォルトは secret。`--public` はユーザーが「public で」「公開で」と明示した場合のみ使用する。 ### Step 3: 結果を報告テンプレートで表示 ## Mode 4: 更新 既存の Gist を更新する。 ### Step 1: 対象 Gist の特定 ユーザーが Gist ID / URL を指定していない場合は一覧から検索する。 ```bash gh gist list --filter "keyword" ``` ### Step 2: 現在の内容を確認 ```bash gh gist view <gist-id> --files gh gist view <gist-id> --raw --filename <filename> ``` ### Step 3: 更新の実行 ```bash # ファイル内容の上書き(一時ファイル経由) gh gist edit <gist-id> --filename <target> application/tools/tmp/<local-file> # ファイルの追加 gh gist edit <gist-id> --add application/tools/tmp/<new-file> # description の変更 gh gist edit <gist-id> --desc "new description" # ファイルの削除(ユーザーの明示的指示がある場合のみ) gh gist edit <gist-id> --remove <filename> ``` ### Step 4: 結果を報告テンプレートで表示 ## Mode 5: データ管理 JSON / YAML などの構造化データを Gist に保存し、DB 的に管理する。 ### データ用 Gist の識別 description に `[data]` プレフィックスを付けてデータ用 Gist を識別する。 ```bash # データ用 Gist の検索 gh gist list --filter "\\[data\\]" ``` ### データの取得と更新 ```bash # 1. 現在のデータを取得 gh gist view <gist-id> --raw --filename <filename> > application/tools/tmp/<filename> # 2. jq で変換(JSON の場合) jq '.key = "new_value"' application/tools/tmp/<filename> > application/tools/tmp/<filename>.updated # 3. Gist に反映 gh gist edit <gist-id> --filename <filename> application/tools/tmp/<filename>.updated ``` ### データ用 Gist の新規作成 ```bash # JSON データ gh gist create application/tools/tmp/data.json -d "[data] project-config" # YAML データ gh gist create application/tools/tmp/data.yaml -d "[data] settings" ``` **削除操作:** ユーザーの明示的指示がある場合のみ実行。必ず確認を取る。 ```bash # 削除(非インタラクティブ環境では --yes が必須) gh gist delete <gist-id> --yes ``` ## Report Templates 以下のテンプレートを使って結果を報告する。 ### 一覧 ``` ## Gist 一覧 | # | Description | Files | Visibility | Updated | |---|-------------|-------|------------|---------| | 1 | <description> | <file-count> | <public/secret> | <date> | ``` ### 取得結果 ``` ## Gist 内容 **Gist:** <description> **ID:** <gist-id> **File:** <filename> <ファイル内容をコードブロックで表示> ``` ### 保存完了 ``` ## Gist 作成完了 **URL:** <gist-url> **Description:** <description> **Visibility:** <public/secret> **Files:** <file-list> ``` ### データ管理 ``` ## データ更新完了 **Gist:** <description> **File:** <filename> **変更内容:** <変更のサマリ> ``` ## Important Notes - **gh CLI 未認証の場合**: `gh auth status` で認証状態を確認し、未認証ならユーザーに通知 - **`--include-content` は絶対に使わない**: トークン効率のため。`--filter` だけで description とファイル名は検索可能 - **secret がデフォルト**: ユーザーが明示的に public を指定しない限り secret で作成する - **削除操作は慎重に**: `gh gist delete` はユーザーの明示的な指示 + 確認の上でのみ実行。非インタラクティブ環境では `--yes` フラグが必須 - **一時ファイルの場所**: 必ず `application/tools/tmp/` を使用する(`/tmp/` は使わない) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post gh CLI × Claude Code で Gist 操作をチャット1行で完結させる first appeared on SIOS Tech Lab .
アバター
ども!最近は VS Code のターミナルとチャット欄だけで一日が終わる龍ちゃんです。 生成 AI を使った開発、爆速ですよね。コード書いて、テストして、コミットして。エディタの中で AI と会話しながら全部完結します。この体験を知ってしまうと、もう戻れないんですよ。 爆速になりつつ品質をいかに担保するのか?というのが直近の大きな課題です。 作業が途切れる瞬間がある ただ、途切れる瞬間があるんですよね。CI が落ちたらログを確認しないといけないし、コードを Gist に保存したくなることもある。GitHub でやりたいことが出てくるたびに、ブラウザを開いてしまうんです。その瞬間にエディタから手が離れてしまいます。 たかが数分のことなんですけど、集中してたのに一回途切れるあの「うっ」って感じ、ずっと気になってました。 よく考えると、ブラウザを開いてまでやってることって CI のログを読むとか、Gist にコードを貼るとか、そういうことなんですよね。中身はぜんぶテキストです。テキストとして手に入ればそれでいいのに、わざわざブラウザの UI をポチポチ… この記事は、VS Code でコード書いてて、GitHub のためにブラウザに飛ぶたびに「うっ」ってなる人に向けて書いています。 「ちょっとブラウザ開くだけ」がどれだけ重いか書き出してみて、それを解決する方法として gh CLI(GitHub CLI) や MCP にどんな選択肢があるか整理して、実際にどう変わるのかを具体例で見ていきます。 ブラウザを開く「ちょっとだけ」が重い理由 「ちょっとブラウザ開くだけ」。実際にちょっと開くだけです。でも、実際にやってることを書き出してみると、ちょっとじゃないんですよね。 CI が落ちたときの「ちょっとだけ」 GitHub Actions の CI が落ちたとき、ログを確認するまでの手順を書き出してみます。 ブラウザで GitHub Actions の Run ページを開く 失敗したジョブを展開する エラーログの該当箇所を探す ログをコピーする VS Code に戻る コピーしたログを読んで原因を調べる 6ステップもあります。「ちょっとだけ」とは一体何だったのか。 Gist に保存したいときの「ちょっとだけ」 Gist も似たようなもんです。 gist.github.com をブラウザで開く ファイル名を入力する コードを貼り付ける description を書く public / secret を選択する 「Create gist」ボタンを押す こちらも6ステップです。逆方向、つまり Gist からコードを取ってくるときも同じぐらいかかります。 ステップ数が問題じゃない ただ、正直に言うとステップ数自体はそこまで大した話じゃないんですよね。慣れてれば数分で終わります。 問題はそこじゃなくて、 作業の流れが切れる ことなんです。 コード書いてる最中にブラウザに飛んで、用が済んでエディタに戻ってくる。たかが数分です。でも戻ったときに「何してたっけ」「どこまでやったっけ」ってなりません? エディタの画面は変わってないのに、頭の中の作業文脈がリセットされてるんですよね。さっきまで集中してた「あの感じ」を取り戻すのに、また時間がかかります。 これだけでもダルいんですけど、生成 AI と会話しながら作業してるときはもっとキツくなります。コードの修正を進めてる最中にブラウザに飛ぶと、戻ってきてからさっきの続きを取り戻すのに時間がかかるんですよね。頭の中を巻き戻すだけじゃなくて、ブラウザで確認してきたログの内容をいちいち説明し直さないといけない。あの手間がほんとにダルいんですよ。 結局、ブラウザの GUI で得られる情報って、テキストで十分なものばかりなんです。CI のログも Gist のコードも、テキストとして手に入ればいい。それならエディタの中で完結させたいんですよね。 gh CLI・MCP・Claude Code Skills — エディタから GitHub を操作する方法 「エディタの中で GitHub の操作を呼び出したい」——この望みを叶えてくれる方法は、実はいくつかあります。 GitHub CLI(gh コマンド) GitHub CLI は、GitHub が公式に提供しているコマンドラインツールです。 gh run list で CI の実行一覧を取得したり、 gh gist create で Gist を作成したり、ブラウザでやっていた操作がターミナルのコマンドで一通りできるようになります。 VS Code のターミナルから実行すれば、ブラウザを開く必要がなくなります。情報はテキストで返ってくるので、そのまま読めるし、AI に渡すこともできます。 --json や --jq で出力形式を絞れるので、必要な情報だけを取り出しやすいのも強みです。 認証の補足 : とりあえず試すなら gh auth login のブラウザ認証が手軽です。運用するなら PAT(Personal Access Token)で必要なスコープだけに絞るのがおすすめです。認証方法の詳細は各編で触れます。 MCP(Model Context Protocol) MCP は、AI ツールが外部の API を呼び出すための仕組みです。 GitHub MCP Server を設定すると、AI が直接 GitHub の API を叩いてくれます。Issue の操作やリポジトリの情報取得など、カバー範囲は広いです。 ただし、ツールの戻り値が大きくなりがちで、トークン消費が膨らみやすいという特性があります。また、Gist 関連のツールは 2026年2月時点では提供されていません。 Claude Code Skills Claude Code の Skills を使うと、コマンドの手順を Markdown ファイルに書いておくだけで、チャットから呼び出せる仕組みを作れます。gh CLI の手順を書いておけば、「CI 失敗してるから見て」と打つだけでスキルが gh CLI を実行してログを取得し、Claude が分析する——という流れをチャットの中で完結させられます。 このシリーズのアプローチ どの方法を使っても「エディタから離れない」という目的は達成できます。 このシリーズでは gh CLI + Claude Code Skills の組み合わせを選びました。CLI なら出力形式を自在に絞れるのでトークンを無駄遣いしにくいし、SKILL.md に書く手順も Bash コマンドそのままなので設計しやすかったんですよね。選んだ理由の詳しい話は各編で触れますが、大事なのは方法論よりも「エディタから GitHub の操作を呼び出せるようになる」という体験のほうです。 シリーズ構成 記事 内容 学べること この記事(概要編) 問題提起と解決策の全体像 なぜエディタから離れたくないのか、どんな選択肢があるか CI 編 失敗ログの取得から原因分析まで SKILL.md の設計パターン、トークン効率のルール Gist 編 Gist の検索・保存・更新 CRUD をスキル化するときの安全策、段階的絞り込み GitHub Actions をよく使うなら CI 編から、スニペット管理や Gist 活用が気になるなら Gist 編から読むのがおすすめです。 gh CLI × Claude Code Skills で何が変わるか じゃあ、実際にどう変わるのか。各編の内容から1つずつ味見してもらいます。 CI の場合 エディタのチャット欄で「CI 失敗してるから見て」と打ちます。 スキルが失敗した Run を探して、ログを取得して、Claude が分析結果を返してくれます。 ## Failure Analysis **Run:** #22430458939 - Demo - Failure Workflow **Failed Jobs:** | Job | Error Summary | |-----|--------------| | Test | integration test で AssertionError(期待 200、実際 503) | **Root Cause:** test_pipeline.py の test_full_pipeline テストで、 API レスポンスのステータスコードが期待値 200 に対して 503 を返している **Suggested Fix:** - テスト対象の API サーバーが起動しているか確認 ブラウザは一度も開いていません。Run を探す → ジョブを展開する → ログをコピーする → 原因を調べる、という6ステップが「CI 失敗してるから見て」の一言に置き換わっています。そのまま「じゃあ直して」と続ければ、修正まで会話の流れの中で進められます。 CI 編 : 失敗ログの取得から修正まで VS Code で完結させる仕組みと作り方。SKILL.md の設計やトークン効率のルールも紹介します。 Gist の場合 「Python のスニペット取ってきて」と打ちます。 スキルが Gist を検索して、ファイルを特定して、中身を取得してくれます。 """Python useful snippets - よく使うコードスニペット集""" # --- flatten nested list --- def flatten(nested: list) -> list: ... gist.github.com は開いていません。検索してコードをコピーしてエディタに貼り付けて……という手順がまるごとなくなっています。 保存も同じです。コードを書いてて「これ Gist に保存しといて」と打てば、ファイル名も description もいい感じに付けて保存してくれます。URL が返ってくるので、共有したければそのまま貼るだけです。 Gist 編 : 「保存して」「取ってきて」で Gist 操作が完結する仕組みと作り方。CRUD をスキル化するときの安全策(secret デフォルト、削除前確認)も紹介します。 まとめ ブラウザに飛ぶたびに作業の流れが切れる。ステップ数じゃなくて、頭の中の文脈がリセットされるのがキツいんですよね。CI のログも Gist のコードも、テキストとして手に入ればそれでいいんです。 gh CLI や MCP を使えば、エディタの中から GitHub の操作を呼び出せます。Claude Code Skills と組み合わせれば、チャットの一言で全部完結します。仕組み自体は .claude/skills/ に Markdown ファイルを1つ置くだけなので、自分のプロジェクトに合わせてカスタマイズしやすいのもポイントです。 このシリーズでは gh CLI + Skills の組み合わせで2つのスキルを作りました。各編では SKILL.md の全文と設計の考え方を解説しているので、そのまま使うこともできるし、自分のプロジェクトに合わせてカスタマイズすることもできます。CI と Gist に限らず、gh CLI でできる操作なら同じパターンで横展開できるので、「自分のプロジェクトだとこの操作が面倒」というものがあれば、各編のやり方を参考にスキルを作ってみてください。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post gh CLI × Claude Code でブラウザなし GitHub 操作を実現する first appeared on SIOS Tech Lab .
アバター
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 「理屈はいいから、まずは実際にオブザーバビリティー(可観測性)というものを動かして体験してみたい」。 本記事は、そんな方々に向けて、システム運用に不可欠なオブザーバビリティーの基礎を手を動かしながら学習できる、実践的な入門ガイドとして用意しました。 Grafana OSS LGTM スタックを使って、メトリック・ログ・トレース・プロファイルを包括的に可視化する環境を無料で体験できるハンズオンを提供します。 本ハンズオンでは、以下のオープンソース・プロダクトのみで構成された環境でコンテナを使って一括で立ち上げ、Web アプリケーションから実際にデータを収集・可視化する流れを体験していただきます。 Grafana: 可視化ダッシュボード Mimir / Loki / Tempo: メトリクス、ログ、トレースのバックエンド Pyroscope / Faro / Alloy: プロファイリングやフロントエンド監視、データ収集 OpenTelemetry Collector:テレメトリーデータの中継・処理パイプライン なお、本記事は「どのような観測ができるのか」を体験いただくことを主目的としています。そのため、Grafana OSS LGTM Stack の環境構築手順そのものの詳細解説は割愛しており、「まずは動かしてみたい」という方に最適です。もし環境構築の裏側に興味を持っていただいた場合は、ぜひ GitHub リポジトリの設定ファイルを解析してみてください。 構成概要 本ハンズオンの動作検証を行った環境(バージョン)は以下の通りです。 記事では Windows 11 上の WSL 2 (Ubuntu) で起動する流れで記載していますが、Docker が動作する環境であれば他の OS でも実施可能です。 Windows 11 Professional WSL 2.5.9.0 Ubuntu 24.04.3 LTS Docker Engine 28.5.1 Grafana 12.3.2 Windows (WSL) 以外のOSをご利用の方も、条件が満たしていれば以下の手順からハンズオンを進められます。 Ubuntu (Linux) 環境の方: WSL の構築は不要ですので、「 Docker Engine 環境の構築 」 章から開始してください。 macOS 環境の方: Docker Desktop for Mac などでコンテナ実行環境が準備済みであれば、「 GitHub からハンズオン用のリポジトリ取得 」 章から開始してください。 ハンズオンを構成する環境は以下の通りです。 オブザーバビリティーを構成する Grafana LGTM Stack と OpenTelemetry Collector、および観測対象の Web アプリケーションはそれぞれコンテナで構築します。 Web アプリケーションをユーザーの立場として操作し、Grafana を運用担当の立場として操作してロールプレイングしながら進めます。 Web アプリケーションと Grafana LGTM Stack 間で、メトリクス、ログ、およびトレースは OpenTelemetry Collector を介し、プロファイルと Real-time User Monitoring (RUM) は Alloy を介して連携します。 Web アプリケーションから OpenTelemetry Collector を介して連携するデータと送出元は以下の通りです。 メトリクス: Micrometer (Prometheus 形式) メトリクス:Micrometer (OpenTelemetry 形式) メトリクス: Node Exporter ログ: OpenTelemetry トレース:OpenTelemetry Web アプリケーションから Alloy を介して連携するデータと送出元は以下の通りです。 プロファイル: Pyroscope Java Agent RUM: Faro Web SDK from CDN 汎用的な OpenTelemetry Collector でも Grafana LGTM Stack に連携できることを表したいので、できる限り OpenTelemetry Collector を介した連携で構成しています。 オブザーバビリティーの Grafana LGTM Stack は、Grafana、Mimir、Loki、Tempo、Pyroscope、Faro、および Alloy で構成します。 ユーザーインターフェースは Grafana が担います。 Mimir、Loki、Tempo、および Pyroscope は、それぞれの前段に Nginx で構築するロードバランサーを配置します。 Mimir、Loki、および Tempo は、それぞれの後段に MinIO で構築する永続ストレージを構成します。 Tempo の構成における主な考慮事項は以下の通りです。 OpenTelemetry Collector から送信されてくるトレースを受け取るポート番号(4318)と、Grafana で表示のために取得するポート番号(80)が異なるため、それぞれのロードバランサーを構成します。 Grafana でサービスグラフを表示するために、Tempo のメトリクスジェネレーターでトレースからメトリクスを生成し Mimir へ送信します。 メトリクスジェネレーターを始めとする Tempo の内部構造のアーキテクチャーについては「 Tempo architecture | Grafana Tempo documentation 」を参照してください。 Alloy はプロファイルと RUM の中継のみを担っています。本ハンズオンでは、汎用的な OpenTelemetry Collector でも Grafana LGTM Stack に連携できることを表したいので、あえてプロファイルと RUM のみの扱いに限定しています。 観測対象の Web アプリケーションは、WebUI、WebAPI、および MySQL で構成します。 WebUI と WebAPI の構成における主な考慮事項は以下の通りです。 Spring Boot で実装したアプリケーションで構成します。 メトリクス、ログ、トレース、およびプロファイルは、自動計装で収集しているので、ビジネスロジックに手動計装のロジックは一切実装していません。 異常検知のアラートは、アラートメールの通知先もハンズオン環境内で完結するように、SMTP サーバーと受信したメールが閲覧できる Web メールを搭載した MailDev コンテナを利用します。 環境構築や各種設定に使用するそれぞれのファイルは、以下の GitHub リポジトリで公開しています。 Toshiharu-Konuma-sti/hands-on-grafana :本ハンズオンで使うメインのリポジトリです。 $ tree ~/handson/hands-on-grafana/ hands-on-grafana/ |-- container/ …… 「環境構築」章でコンテナの作成で使う素材 | |-- docker-compose.yml | : | `-- webapp/ …… 観測対象となる Web アプリケーションを構成するリポジトリを参照するサブモジュール Toshiharu-Konuma-sti/hands-on-rollingdice-webapp :観測対象となる Web アプリケーションのリポジトリで、ハンズオンのリポジトリからサブモジュールで参照されているので、意図的に Clone する必要はありません。 $ tree ~/handson/hands-on-webapp-rolling-dice/ hands-on-webapp-rolling-dice/ : |-- mysql …… Web 三層アーキテクチャのデータ層に該当するデータベースを構成する素材 | |-- config | | `-- my.cnf | `-- init | `-- init.sql | |-- webapi …… Web 三層アーキテクチャのプレゼンテーション層に該当するフロントエンドサーバーを構成する素材 | |-- build.gradle | : | `-- webui …… Web 三層アーキテクチャのアプリケーション層に該当する API サーバーを構成する素材 |-- build.gradle : 環境構築 WSL 環境の構築 Windows PC の場合には、 以下手順を参考に WSL と Linux ディストリビューション(Ubuntu)環境を用意します。 初期環境構築: WSL 環境 on Windows Docker Engine 環境の構築 コンテナ環境を使うため、以下手順を参考に Ubuntu へ Docker Engine 環境を用意します。 初期環境構築: Docker Engine on Ubuntu GitHub からハンズオン用のリポジトリ取得 ハンズオンを進めるための環境構築用の設定ファイルやスクリプトを含んだリポジトリを GitHub からダウンロードして取得します。 本章ではターミナルを用いてハンズオンのフォルダ領域を作成して作業を実施します。 $ mkdir -p ~/handson/ $ cd ~/handson/ 「 $ git clone 」コマンドで本ハンズオン用のリポジトリを取得します。 $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-grafana.git $ cd hands-on-grafana/ コンテナ構築スクリプトの実行 本章ではターミナルを用いて以下のディレクトリで作業を実施します。 $ cd ~/handson/hands-on-grafana/container/ コンテナ構築用に用意してあるスクリプトを実行して、オブザーバビリティー環境の各種コンテナを構築します。初回はイメージのダウンロードと作成に時間がかかりますので、コンソールの表示を見守りつつ、処理が完了するまでしばらくお待ちください。 $ ./CREATE_CONTAINER.sh コンテナが構築されてから info オプションを付けてスクリプトを実行すると、ハンズオンに必要なアプリケーションの URL などを表示することができます。 $ ./CREATE_CONTAINER.sh info /************************************************************ * Information: * - Access to Grafana Web ui tools with the URL below. * - Grafana: http://localhost:3000 * - dashboard(Prometheus): https://grafana.com/grafana/dashboards/4701-jvm-micrometer/ * - dashboard(OpenTelemetry): https://grafana.com/grafana/dashboards/20352-opentelemetry-jvm-micrometer/ * - dashboard(Node Exporter): https://grafana.com/grafana/dashboards/1860-node-exporter-full/ : Grafana コンテナが稼働するのでブラウザでアクセスします。 http://localhost:3000 Web アプリケーションのコンテナが稼働するのでブラウザでアクセスします。 http://localhost:8081/ Web アプリケーションが起動したかどうかは、以下のコマンドでアプリケーションログを確認し、「Spring」のロゴが出力されていたら Web アプリケーションが利用できる状態の目安になります。「 docker logs 」に続くコマンド末尾は「webapp-webui」「webapp-webapi」のそれぞれのコンテナを指定して確認します。 $ docker logs webapp-webui > Task :bootRun . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.4.3) 2025-04-17T14:36:32.114Z INFO 412 --- [webui-app-yml] [ restartedMain] ... 2025-04-17T14:36:34.926Z INFO 412 --- [webui-app-yml] [ restartedMain] ,,, 2025-04-17T14:36:35.048Z INFO 412 --- [webui-app-yml] [ restartedMain] ... 2025-04-17T14:36:35.895Z INFO 412 --- [webui-app-yml] [ restartedMain] ... 2025-04-17T14:36:37.925Z INFO 412 --- [webui-app-yml] [ restartedMain] ... 2025-04-17T14:36:41.572Z INFO 412 --- [webui-app-yml] [nio-8081-exec-1] ... なお、コンテナ構築スクリプトで実行する内容は以下を参照してください。 コンテナ構築スクリプトの解説 Web アプリケーションの説明 オブザーバビリティーの実演に移る前に、観測対象となる Web アプリケーションの画面構成と機能を説明します。 Web ページからのリクエストごとにサイコロを振り、結果と履歴を表示します。 「Roll a dice」メニューには、単純にサイコロを振る以外にも、トラブルシューティングの練習用に特殊な挙動をするリンクも用意しています。 Roll normally: 特別な処理を行わず、通常通りサイコロを振ります。 Sleep n sec at API: サイコロを振る前に、処理時間を意図的に延ばすために指定秒間スリープします。 Loop n sec at API: サイコロを振る前に、システムへ負荷を掛けるために指定秒間ループします。 Trigger 5xx error at API: サーバー側で意図的に例外を発生させ、HTTP ステータス 500 エラーを起こし、サイコロを振るのを中断します。 Force specific value: サイコロの出目を、指定した値で強制的に固定します。 RUM Simulation at Browser: RUM 関連の動作確認が行えます。 Trigger JS error at Browser: サーバーへのリクエストは行わず、ブラウザ(フロントエンド)側で JavaScript の例外を意図的に発生させます。 Fetch API: Normal: 画面遷移を行わず、JavaScript (Fetch API) から非同期通信で サイコロを振る WebAPI を実行します。 Fetch API: Sleep 2 sec: 2秒間のスリープも指示した非同期通信を実行します。 観測対象となる Web アプリケーションのシーケンスとコンポーネント仕様を説明します。 Web アプリケーションは、WebUI、WebAPI、および Database の3つのコンポーネントで構成します。WebUI と WebAPI は Spring Boot のフレームワークで実装し、Database は MySQL を利用します。 各コンポーネントの仕様: WebUI:WebUI から WebAPI へ以下2つの API をコールして画面表示を行います。 WebAPI の「/api/v1/dices」を POST でコールしてサイコロを振ります。 WebAPI の「/api/v1/dices」を GET でコールしてサイコロ出目の一覧を取得します。 WebAPI:以下2つの API を持ちます。 POST: /api/v1/dices サイコロを振り、出目を Database に保存します。WebAPI を呼び出す際に、パラメータを付与することで観測向けの機能も実行します。 sleep パラメータを付与すると、指定秒間スリープを行います。 loop パラメータを付与すると、指定秒間ループし、ループ毎にファイルを開いて1行読むディスクアクセスの処理を行います。 error パラメータに true を付与すると、サイコロを振らずに処理を中断し、HTTP ステータスコード 500 エラーで API をレスポンスします。 GET: /api/v1/dices Database からサイコロ出目の全履歴を、振った日時の降順で取得します。 Database サイコロの出目、日時、およびシーケンス番号のフィールドで構成するテーブルを保持します。 オブザーバビリティーの実演 メトリクス閲覧 (Mimir) 観測対象の Web アプリケーション(Micrometer 実装)、および Node Exporter から収集されたメトリクスが、Mimir へと送信されています。まずは Grafana のダッシュボードを使って、これらのメトリクスを可視化してみましょう。 Grafana 公式サイトでは、データソースや用途に合わせた数多くのダッシュボードテンプレートが公開されています。今回はその中から、Micrometer (Prometheus 形式) のメトリクスを表示するのに適した「 JVM (Micrometer) | Grafana Labs 」を利用して、メトリクスを眺められるようにしてみます。 左ペインのメニューから「Dashboard」を選択して Dashboards 画面を表示し、画面右側の「New」ボタンをクリックすると表示されるメニューから「Import」を選びます。 画面中央のテキストボックスにインポートする「JVM (Micrometer)」ダッシュボードテンプレートの URL を入力し「Load」ボタンをクリックします。 ダッシュボードテンプレートの URL: https://grafana.com/grafana/dashboards/4701-jvm-micrometer/ インポートするダッシュボードの設定画面が表示(ダッシュボードにより設定内容は異なる)されるので、本テンプレートではデータソースとなる Prometheus を選択して「Import」ボタンをクリックします。 Prometheus 項目:「Mimir for Metrics」 ダッシュボードが表示されました。これで Web アプリケーションから送信されたメトリクス情報を確認できます。 なお、Grafana や Web アプリケーションを起動した直後の場合、デフォルトの表示期間(Last 24 hours)ではデータ幅が小さすぎてグラフが表示されないことがあります。その場合は、画面右上の時間設定を「Last 15 minutes」などに変更し、表示期間を狭めてみてください。 本ハンズオンの Grafana でメトリクスを表示できるダッシュボードテンプレートと、設定画面で設定する値を紹介します。なお、公式サイトには紹介するテンプレート以外にも様々なテンプレートが紹介されているので、検索してご自分に合ったテンプレートを探してみてください。 JVM (Micrometer) | Grafana Labs Micrometer (Prometheus 形式) のメトリクスを表示します。 Prometheus 項目:「Mimir for Metrics」 OpenTelemetry JVM Micrometer | Grafana Labs Micrometer (OpenTelemetry 形式) のメトリクスを表示します。 Mimir 項目:「Mimir for Metrics」 Loki 項目:「Loki」 Node Exporter Full | Grafana Labs Node Exporter のメトリクスを表示します。 Mimir 項目:「Mimir for Metrics」 ログ閲覧 (Loki) Web アプリケーションが出力するログを Loki で収集・集約しています。これにより、各サーバーやコンテナへ個別にログインすることなく、Grafana の画面上だけでログの検索・閲覧が完結します。 それでは、実際に Web アプリケーションを操作し、その挙動に合わせてリアルタイムにログが出力される様子を確認してみましょう。 左ペインのメニュー「Explore」を選択して Explore 画面を表示し、データソースのドロップダウンから「Loki」を選び「Label browser」ボタンをクリックします。 Label browser 画面にて、検索対象のラベルとして「service_name」、値として「webapp-webui」の順番で選択してから「Show logs」ボタンをクリックすることで、webapp-webui コンテナのアプリケーションログを表示できます。 値として「webapp-webapi」を選択すれば、webapp-webapi コンテナのアプリケーションログ表示を指定できます。 青色「Run query」ボタン右端の下矢印をクリックすると表示するリロード間隔一覧から「5s」を選択することで、ログ表示が5秒間隔で自動的にリロードされるため、リアルタイムでログを確認することができます。 ログがリアルタイムに表示することを確認したいので、ユーザーの立場で Web アプリケーションをアクティブにしてサイコロを振ります。 http://localhost:8081/ 再び運用担当の立場で Grafana に戻るとサイコロを振ったログが表示されていて、ログを眺めてみると先ほど Web アプリケーションで表示されていたサイコロの出目と同じ値を示すメッセージが存在することも確認できるはずです。 ログから一つメッセージを選んでクリックすると展開表示され詳細情報が確認できます。 「+ Operations」を使うことでログの表示を絞り込むこともできます。 よく使う検索指定を例示します。 「+ Operations > Label filters > Label filter expression」:ラベル、演算子、値の組み合わせで検索します。(例:「detected_level = error」は、エラーレベルで絞り込むことを意味します) 「+ Operations > Line filters > Line contains」:大文字と小文字を区別した完全一致で文字列検索します。 「+ Operations > Line filters > Line contains case insensitive」:大文字と小文字を区別せず文字列検索します。 ログからトレース閲覧 (Loki to Tempo) アプリケーションログの確認ができたら、次はログとトレースの連携機能を体験してみましょう。 ログ一覧から任意の一行を選択し、そこから紐付いているトレース情報へジャンプします。 トレースを閲覧することで、ユーザーのリクエストからレスポンスに至るまでの一連の処理フローや、各処理ごとの所要時間をウォーターフォール形式で視覚的に把握できます。「それぞれの処理で、どのくらい時間がかかっているか」が一目瞭然になる様子を確認してください。 ログ表示の中から気になるログを選びます。 選んだログをクリックして詳細情報を表示します。 詳細情報を下にスクロールすると「Links」のセクションに(閉じている場合はクリックして展開すると)「Tempo」への連携ボタンがあるので、クリックすることで該当ログのトレースが画面をスプリットして右半分に表示されます。 元から表示されている画面左半分のログ表示側で、右上部にある三点リーダーをクリックすると表示する「Close」メニューをクリックするとトレースが全面表示になります。 Web アプリケーションの1リクエストで、WebUI から WebAPI、そして WebAPI からデータベースへ連携して実行される一連の処理フローと、各フローで掛かった処理時間を確認することができます。 表示したトレースとアプリケーション仕様で説明したシーケンス図を比較すると、実際に実行された処理フローがシーケンス仕様と一致していることが確認できます。 別のリクエスト・レスポンス事例として、Web アプリケーション初回起動時の1回目にサイコロを振った際のトレースと、2回目以降のトレースを比べてみます。プログラム初回起動時には、オブジェクトの生成や外部リソースの接続準備などで処理のオーバーヘッドが高く処理時間が掛かると一般的に言われていますが、実際にトレースを比べてみるとその事実が確認することができます。 再度トレース機能の説明に戻りますが、 いずれかのトレースをクリックして詳細情報を展開すると「Log for this span」ボタンが表示され、クリックするとトレースからログへの表示も可能です。 また、画面上部にある「Service Graph」タブをクリックすることで「Node Graph」が表示され、各コンポーネント間で生じる通信の関係性が確認できます。 本ハンズオンでは user が webapp-webui だけではなく、webapp-webui と webapp-webapi の両方にもアクセスが生じているのは、OpenTelemetry Collector が Prometheusプロトコルで両方のコンポーネントへ定期的にメトリクスを取得するアクセスが発生しており、これらが user からのアクセスとして認識されているためです。 メトリクスからトレース閲覧 (Mimir to Tempo) 続いて、メトリクス(数値の変動)からトレース(詳細な処理フロー)への連携を確認します。 Web アプリケーションから送信される Micrometer(Prometheus 形式)のメトリクス、特に HTTP リクエストのパフォーマンスを示す http_server_requests_seconds_bucket などには、Exemplar と呼ばれる機能によって Trace ID が紐付けられています。 Grafana のグラフパネル上に表示される Exemplar(点やひし形のマーク)をクリックすることで、その時点の具体的なリクエストのトレースへ直接ジャンプすることができます。グラフで「遅い」と感じた箇所の裏側を即座に特定できる便利さを体験してみましょう。 Explore でデータソースに「Mimir for Metrics」を選択し、「Code」入力モードに切り替えてから下に掲載する Prom QL クエリーを入力します。クエリーを入力したら、「Exemplar」スイッチをオンにして「Run Query」ボタンをクリックすると、Exemplar が有効となったメトリクスのグラフが表示されます。 入力する Prom QL クエリーは以下の通りです。 histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[$__rate_interval])) by (le)) 今度はユーザーの立場で Web アプリケーションをアクティブにします。Grafana でメトリクスのグラフ変化が分かりやすいようにするため、Web アプリケーションの「Roll a dice」メニューで、「2. Sleep 05 sec at API」から「4. Sleep 30 sec at API」までを上から順番に、一つクリックしたら少し間隔を置いてから次をクリックしてを繰り返してサイコロを振ってみます。 続いて運用担当の立場で Grafana に戻ります。メトリクスのグラフに小中大の3つの山が現れています。Exemplar を有効にしているため、それぞれの山頂の左横には HTTP リクエストを示す点が存在しています。 気になる HTTP リクエストに対してマウスオーバーすると詳細情報が掲載されたウィンドウが現れ、Tempo へ連携できる「Query with Tempo」ボタンも表示されています。 ウィンドウ内の「Query with Tempo」ボタンをクリックすると、詳細表示していたリクエストのトレースを画面右半分に表示することができ、「 ログからトレース閲覧 (Loki to Tempo) 」章で説明したようにトレースで分析を行うことも可能です。 プロファイルで解析 (Pyroscope) メトリクスやトレースで「遅い処理」を見つけたとしても、具体的なコードのどこに原因があるかまでは分からないことがあります。そこで Pyroscope を用いたプロファイリングを行います。 ここでは、CPU 使用率やメモリ割り当て状況を可視化する「フレームグラフ (Flame Graph)」を利用して、アプリケーションの内部状態を解析します。リソースを過剰に消費している関数やメソッドをピンポイントで特定する手順を体験してみましょう。 Explore にてデータソースに「Pyroscope」を選択し、直下のドロップダウンからリソースとして「process_cpu > cpu」の順番で CPU の利用時間を選択します。ドロップダウン横のテキストボックスに「 {service_name="webapp-webapi"} 」の条件式を入力して「Run Query」ボタンをクリックすると WebAPI を対象としたプロファイルが表示されます。 入力する条件式は以下の通りです。 WebUI を対象とする場合には「 {service_name="webapp-webui"} 」を入力します。 WebAPI を対象とする場合には「 {service_name="webapp-webapi"} 」を入力します。 「Run Query」ボタン右わきをクリックし一覧から「5s」を選択すると、5秒間隔でリロードされるのでリアルタイムでプロファイルを確認できます。 プロファイル表示の左上にあるテキストボックスで絞り込みを行うことができます。試しに「java」で入力してみると、Java 言語のクラスやメソッドがリソースを利用した値が表示されます。お馴染みな Java 標準クラス名やメソッド名があれば、それらがリソースを費やしていると言うことを表します。 今度は「jp/sios」で絞り込んでみると、ハンズオン用に実装した Web アプリケーションのビジネスロジックがリソースに費やした値を表示することができます。値を確認して他の値より抜きんでて大きいシンボルがある場合には、ハードウェアリソースを占有して利用している可能性があるため、ビジネスロジックの実装を見直して頂くことも検討します。キャプチャーした実績では大きな値では無いので、問題ないと言ってよいでしょう。 プロファイルの基礎的な表示方法が分かりましたので、プロファイルの活用事例を試してみます。ユーザーの立場で Web アプリケーションをアクティブにして、メニュー「2. Sleep 5 sec at API」から「4. Sleep 30 sec at API」を上から順にクリックして、WebAPI で時間が掛かる処理を実行してみます。 運用担当の立場で Grafana に戻り、プロファイルを確認します。リアルタイム更新されるさまを暫く眺めますが、Sleep 処理ではそれほど CPU を使っていないため、大きく値が変わっていないことが確認できるはずです。 再度ユーザーの立場で Web アプリケーションをアクティブにして、今度はメニュー「5. Loop 5 sec at API」から「7. Loop 30 sec at API」を上から順にクリックして、WebAPI で時間が掛かる処理を実行してみます。 運用担当の立場で Grafana に戻り、プロファイルを確認します。引き続き、リアルタイム更新されるさまを暫く眺めると、今度は「ms(ミリ秒)」から「s(秒)」へ単位が変わり、値が大きく増えたプロセスが存在することが確認できるはずです。もう少し詳細に迫ってみましょう。 大きく値が増えたプロセスを抽出します。 jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.readFile:47.5 s jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.lambda$loop$1:47.9 s jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl$$Lambda_.accept:47.9 s jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.loop:47.9 s jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.rollDice:48.0 s jp/sios/apisl/handson/rollingdice/webapp/webapi/controller/WebApiController.rollDice:48.0 s WebAPI のシーケンス図に大きく値が増えたプロセスをマッピングしてみると、一番深い階層でコールされる「WebApiServiceImpl.readFile」が要因であることが想像できます。 プロファイル画面で示すと赤枠の一行に絞り込めることになります。 実際にソースコードを見てみると、ループ処理の中で、都度ファイルを開いて読み込む readFile メソッドが呼ばれていることが分かります。これにより、ループ回数分のファイルアクセス(=ディスクアクセス)が発生し、それに伴って CPU リソースも消費されていたことがコードレベルでも裏付けられます。 webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java : while (System.currentTimeMillis() < endTime) { line = this.readFile(FILE_PATH_IN_LOOP); : } : webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java private String readFile(final String filePath) { String line = null; try (InputStream inputStream = new ClassPathResource(filePath).getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { LOGGER.debug("Successfully loaded a file."); line = reader.readLine(); : 改めてシーケンス図で解析結果を整理すると、指定時間内にディスクアクセス処理がループで連続実行されたため、CPU 利用時間が肥大化していたと言えます。 今回はプロファイルを用いてボトルネックを特定する手順を学ぶ「意図的な実装」のため修正は行いませんが、実際の運用コードであれば、ファイル読み込みをループの外に出す、あるいはキャッシュを利用してディスクアクセスを減らすといったパフォーマンスのチューニングを行うと良いでしょう。 オリジナルのダッシュボード作成 ここまでの手順では、Explore 画面を使用して、その都度データソースやクエリを指定する「アドホックな分析」を行ってきました。 しかし実際の運用では、重要な指標や頻繁に確認するログを常に表示させ、定点観測することが求められます。そこで、よく見る検索条件やグラフをパネルとして保存し、いつでも即座にシステムの状態を把握できるオリジナルのダッシュボードを作成してみましょう。 Explore で表示しているメトリクスをダッシュボード化したいとします。メトリクスを表示している状態で、画面上部にある「Add」ボタンをクリックすると表示するメニューから「Add to dashboard」をクリックします。 表示された「Add panel to dashboard」ダイアログで「Open dashboard」ボタンをクリックします。 先ほどまで Explore で表示ていたメトリクスがパネル化して載った新たなダッシュボードが生成されました。 ブラウザで新たなタブを立ち上げて、次にダッシュボードに載せたいログを Explore で表示させ、メトリクス同様に「Add > Add to dashboard」を選択してダッシュボード化します。 メトリクス同様に、Explore で表示ていたログがパネル化して載った新たなダッシュボードが生成されましたが、ログのパネルをメトリクスのダッシュボードにコピー&ペーストするため、パネル右上角の三点リーダーをクリックしすると表示するメニューから「More… > Copy」をクリックして、ログのパネルをクリップボードにコピーします。 最初に作ったメトリクスのダッシュボードに戻り、「Add」ボタンクリックで表示するメニューから「Paste panel」をクリックします。 メトリクスとログのパネルが載ったダッシュボードが出来上がりました。このような流れで、頻繁に確認したい項目をまとめた簡易的なオリジナルダッシュボードを作成することもできます。 ここまでの手順と Variables を使ってサンプルとして作ったダッシュボードを用意してありますので、Dashboards 画面から確認してみてください。 メトリクス、ログ、ノードグラフのパネルが載ったダッシュボードで、Server Address フィールドで「webapp-webui」「webapp-webapi」を選択することで表示を切り替えることができます。「Edit」ボタンを使って構成や仕組みを確認してみてください。 フロントエンドのユーザー体験観測 (Faro) これまでの手順ではサーバーサイドの内部状態を観測してきましたが、ユーザーが実際に触れるブラウザ上(フロントエンド)での体験も同様に重要です。 本環境には Grafana Faro Web SDK が組み込まれており、実際のユーザー体験(Real User Monitoring: RUM)を可視化できます。ページの読み込み速度などのパフォーマンス指標(Web Vitals)や、ブラウザで発生した JavaScript エラー、そしてユーザーが利用しているブラウザの種類や OS といった環境ごとのアクセス傾向を確認してみましょう。 RUM の観測用にダッシュボードを用意してあります。ダッシュボード一覧から「My Real-time User Monitoring」を選択します。 ダッシュボードに遷移すると、まず始めに「Performance」のパネルでパフォーマンスに関する各種値が確認できます。 確認できる代表的な値は以下を確認ください。 TTFB (Time to First Byte):サーバーの応答待ち時間です。ブラウザがリクエストを送ってから、最初のデータが返ってくるまでの時間を指します。(参照先: Time to First Byte (TTFB)  |  Articles  |  web.dev ) FCP (First Contentful Paint):「何かが表示されるまでの時間」です。テキストや画像など、ページの一部が初めて描画された時点を指し、この値には TTFB の時間も含みます。(参照先: First Contentful Paint (FCP)  |  Articles  |  web.dev ) LCP (Largest Contentful Paint):「メインコンテンツが表示されるまでの時間」です。そのページで最も大きな画像やテキストブロックが表示された時点を指し、この値には TTFB や FCP の時間もすべて含んだトータルタイムです。( Largest Contentful Paint (LCP)  |  Articles  |  web.dev ) CLS (Cumulative Layout Shift):「視覚的なズレの量」です。画像の遅延読み込みなどでレイアウトがガクッとズレる度合いを示します。数値が低いほど、画面が安定しています。(参照先: Cumulative Layout Shift (CLS)  |  Articles  |  web.dev ) FID (First Input Delay):「操作可能になるまでの遅延」です。ユーザーがボタンをクリックしてから、ブラウザが実際にその処理を開始できるまでの待機時間を指します。(参照先: First Input Delay (FID)  |  Articles  |  web.dev ) 次に「Meta」のパネルでクライアントのOSやブラウザなどのメタ情報が確認できます。 次に「Exceptions」のパネルでクライアントサイドで発生した例外(エラー)に関する各種値を参照するために、Web アプリケーションで例外を発生させます。ブラウザの検証ウィンドウ(デベロッパーツール)を表示している状態で、Web アプリケーションの Roll a dice メニューから「10. RUM Simulation at Browser > Trigger JS error」のリンクをクリックすると、画面中央にエラー発生を知らせるメッセージ(トースト通知)が一瞬表示され、裏側では例外が発生したことが検証ウィンドウでも確認できるはずです。 ダッシュボードに戻り「Exceptions」のパネルで例外の発生件数や例外時に出力されたメッセージなどが確認できます。 次に「Events」のパネルでユーザー操作やライフサイクルの変化が発生した回数が参照できます。 次に「Faro log」のパネルでここまでの集計の元となっている実際のログが確認できます。 アラートからエラー原因の探索 (Alerting) 実際の運用現場において、24時間365日ずっとダッシュボードを監視し続けることは現実的ではありません。システム内で異常が発生した際には、自動的に通知を受け取る仕組みが必要です。 アラートを設定することで、監視の負荷を下げつつ、トラブル発生時の初動対応をいかに迅速化できるかを確認してみましょう。 本セクションでは、Alerting を利用した以下のインシデント対応フローを体験します。 Web アプリケーションのログ (Loki) を監視し、「ERROR」レベルのログ出力を検知する。 運用担当者へ即座にアラートメールを送信する。 メールのリンクから Grafana へ遷移し、前述のログやトレース機能を活用して原因を特定する。 アラーティングを機能させるには、アラートの通知先を定義する Contact points と、監視条件を定義する Alert rules の設定が必要となりますが、まず始めに Contact points の定義から説明します。 左ペインのメニューから「Contact points」を選択し、表示された Contact points 画面には2つの通知先が登録されています。1つ目の「grafana-default-email」は Grafana がデフォルトで登録している通知先で、2つ目の「Hands-on Receive E-mail」がハンズオン用に用意した通知先です。 「Hands-on Receive E-mail」の設定内容は以下の通りです。 Name: 通知先定義の名称として「Hands-on Receive E-mail」を入力しています。 Integration: ハンズオンではメールで通知するため「Email」を選択しています。 Address: 通知先のメールアドレスに MailDev コンテナで受信できる「target-address@test.com」を入力しています。 Test: Test ボタンをクリックすることで「Address」の宛先へテストメールを送信します。 続いて Alert rules で定義している監視条件を説明します。 左ペインのメニューから「Alert rules」を選択し、表示された Alert rules 画面で「Hands-on Training > Eval Interval – 10s」フォルダ内に登録されている「Error level – WebUI log」が該当します。 「Error level – WebUI log」の設定内容は以下の通りです。 1. Enter alert rule name: Name: 監視条件の名称として「Error level – WebUI log」を入力しています。 2. Define query and alert condition: アラートを発動する監視条件を定義します。 条件A: データソース: 「Loki」を選択しています。 Options: 第1条件: 「webapp-webui」から送出されたログを監視対象にします。 第2条件: エラーレベルのログを意図する、「detected_level」ラベルに大文字小文字問わず「error」の値を持ったログを抽出します。 第3条件: 過去1分間のログを監視対象とします。 第4条件: 第1~3条件に該当するログを対象に( 「message」ラベルの)件数をカウントします。 条件C: 条件Aが0件を超過(=1件以上存在)した場合にアラート判定とします。 3. Add folder and labels: Folder: 「Hands-on Training」フォルダを選択しています。 4. Set evaluation behavior: Evaluation group and interval: 監視条件の評価間隔を「Eval Interval – 10s」名称で10秒間隔で評価します。 Pending period: 監視条件が1件でも一致したら即アラートにしたいので「None」を設定しています。 5. Configure notifications: Contact point: アラートの送信先に「Hands-on Receive E-mail」を設定しています。 実演に入る前に、Contact Points で送信するメールを受信する MailDev をブラウザでアクセスします。既に「[FIRING:1] DatasourceNoData ~」などの件名のアラートメールが数通届いています。これはコンテナ起動時の Web アプリケーションがまだ起動していない最中の監視が拾った結果なので、コンテナを構築した時間帯付近のアラートメールは無視してください。 http://localhost:1080 ここからが本格的なアラートの実演で、ユーザーの立場として Web アプリケーションにアクセスし、「Roll normally」をクリックしてサイコロを振ります。 その状況で運用担当として MailDev にアクセスし、暫く経過してもアラートメールが届かないことを確認します。 再びユーザーの立場として Web アプリケーションにアクセスし、「Trigger 5xx error at API」リンクをクリックして Web アプリケーションでエラーが発生した状況にします。 運用担当として MailDev を確認すると、Web アプリケーションでエラー発生から約1分以内に件名が「FIRING」と書かれたアラートメールが届きます。これが障害発生の通知です。メール本文内の「View alert」ボタンをクリックし、Grafana のアラート詳細画面に遷移します。 エラー発生から1分以内に Grafana へ遷移できると、画面下部に「Firing」(発報中)の状態を示すラベルが表示されています。(遷移に1分を超えてラベル表示が解除されていたとしても履歴は残っているので、そのまま進めます。)画面上の「View in Explore」をクリックして Explore に遷移しエラーの原因調査を開始します。 画面下部の「Logs sample」領域で監視に引っ掛かったエラーログを確認しますが、エラー発生から1分を超えてエラーログが表示されていない場合は、画面中央の「Count over time」を「Rang=1m」から「Rang=5m」などにレンジを拡大してエラーログを表示させます。ログが確認できたら、ログ表示の右上にある「Open logs in split view」ボタンをクリックすると画面をスプリットして右半分にログを表示させます。 左半分のログ表示はアラーティングから遷移直後の Explore で機能が制限されているため、左半分のログを閉じ「Tempo」への連携ボタンが表示される右半分のログ表示を全画面表示にします。 ログをクリックして画面右側に詳細情報を展開し、下側へスクロールすると「Tempo」ボタンが現れます。これをクリックしてトレースに遷移します。 トレースを表示させると1つ目の WebUI から WebAPI への呼び出しにおいて、WebAPI 側でエラーが発生していることが一目で分かります。 トレースの中からエラーの発生源と思われる WebAPI のスパンをクリックし、出現したパネル内の「Logs for this span」をクリックします。 画面右半分がトレース表示から、「Logs for this span」をクリックしたスパンに関連するログだけにフィルタリングされた WebAPI のログに切り替わります。 ログを注視していると手掛かりになりそうなメッセージが見つかります。 ログに記録されているメッセージとソースコードを対比すると、例外が発生している箇所が見つかり、ここがアラートの原因であることが把握できます。 webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java private void error(final Optional<Boolean> optError) { UtilEnvInfo.logStartClassMethod(); if (optError.isPresent() && optError.get()) { LOGGER.error( "!!! Intentional exception triggered: '{}' !!!", "HandsOnException"); throw new HandsOnException("Intentional error triggered by request parameter."); } } 実際の運用では、把握した原因に対して改修などで応急処置や恒久対応を行うことで事故完了となりますが、今回はデモとして意図的にエラーを発生させているので、該当箇所は修正せずに、今後は同じ操作をしないと言う誓いで事故完了の扱いとします。 エラー発生から5分を経過すると、MailDev にエラー発生が解消されたことを示す「RESOLVED」メールが届いていることが確認できるので、これでエラー探索の一連の流れが終了となります。 以上で、オブザーバビリティーの実演はすべて終了です。お疲れ様でした! メトリクスの可視化からアラートを起点としたトラブルシューティングまでを体験することで、オブザーバビリティーが実際の運用でどう役立つのか、その「手触り」を掴んでいただけたなら幸いです。 環境のクリーンアップ オブザーバビリティーの実現が一通り終わったら、これまでに利用した環境をクリーンアップしてハンズオンを終了します。 コンテナ削除スクリプトの実行 ターミナルを用いて、以下のディレクトリへ移動します。 $ cd ~/handson/hands-on-grafana/container/ 環境構築時にも使用したスクリプトに down オプションを指定して実行して、オブザーバビリティー環境の各種コンテナを停止・削除します。 $ ./CREATE_CONTAINER.sh down スクリプトの実行が終わったら、 list オプションを付けてスクリプトを実行し、起動しているコンテナが存在しない(一覧に表示されない)ことを確認します。 $ ./CREATE_CONTAINER.sh list ### START: Show a list of container ########## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 以上で環境のクリーンアップは完了です。お疲れ様でした。 続く Appendix では、このハンズオン環境を裏で支えている設定ファイルやスクリプトについて解説します。「どうやってこの環境を作ったのか詳しく知りたい!」という方は、ぜひこのままご覧ください。 Appendix ハンズオン環境の構築や設定手順で利用している各種スクリプトや設定ファイルの実装内容について解説します。 コンテナ構築スクリプトの解説 「 コンテナ構築スクリプトの実行 」章で使用する「 CREATE_CONTAINER.sh 」スクリプトの処理内容について解説します。 コンテナの構築 本ハンズオン用のリポジトリは、別リポジトリ「 Toshiharu-Konuma-sti/hands-on-rollingdice-webapp 」で管理されている Web アプリケーションを参照するサブモジュールを含んでいるので、サブモジュール配下のソースコードを最新版で取得します。 $ git submodule update --init $ git submodule update --remote webapp 用意した「docker-compose.yml」と「docker-compose-webapp.~.yml」のコンテナ定義ファイルで一気にコンテナを構築します。 $ docker-compose \ -f docker-compose.yml \ -f docker-compose-webapp.base.yml \ -f docker-compose-webapp.mode.grafana.yml \ up -d -V --remove-orphans Creating network "intra-net" with driver "bridge" Creating network "hands-net" with driver "bridge" Creating grafana ... done : Creating webapi ... done コンテナ構築に利用しているコンテナ定義ファイルの内容は以下を参照してください。 オブザーバビリティー環境の構成ファイル解説 docker-compose.yml プロダクション環境の構成ファイル解説 docker-compose-webapp.base.yml docker-compose-webapp.mode.grafana.yml オブザーバビリティー環境の構成ファイル解説 オブザーバビリティー環境のコンテナを構築する各種ファイルについて説明します。 コンテナ定義(docker-compose) オブザーバビリティー環境のコンテナを構築する「docker-compose.yml」と環境変数ファイルです。 docker-compose.yml 「x-healthcheck-minio」ノードでは、MinIO に対して実施するヘルスチェック処理として、9000番ポートが TCP 接続できるかどうかの確認を実装しています。 「grafana」コンテナの特記事項は以下の通りです。 「environment」ノードの「GF_SMTP_HOST」にて、アラート通知でメール送信する際の SMTP サーバーを指定します。 .env 「MINIO_ROOT_PASSWORD」にて、MinIO にアクセスするコンテナが接続時に必要となるパスワードを指定します。 Grafana 設定ファイル データソースの定義ファイルです。 datasources.yaml メトリクス表示用に「Mimir for Metrics」データソース、ログ表示用に「Loki」データソース、トレース表示用に「Tempo」と「Mimir for Trace」のデータソースを用意しています。 ダッシュボードの定義ファイルです。 dashboards.yaml 「My Explore Collection」「My Real-time User Monitoring」の2つのダッシュボードが認識されるように定義しています。 my-explore-collection.json 「My Explore Collection」ダッシュボードの定義ファイルで、Exemplar を有効にしたメトリクス、ログ、ノードグラフ、および CPU 利用時間とメモリ割当て量のプロファイルを確認することができます。 配置位置を示す「gridPos」ノードの考え方は以下の通りです。 「x」は横幅を表し、画面横幅全体を仮想的な24ブロックで考えます。 「y」と「h」の関係は、縦にA→B→Cとパーツが並んでいた場合、最初のパーツは「0」から始まり「y+h」が次に続くパーツの「y」になるため、 Aが「A:y=0, h=2」の場合、次に続くBは「B:y=2, h=3」、その次に続くCは「C:y=5, h=4」となります。 my-real-time-user-monitoring.json 「My Real-time User Monitoring」ダッシュボードの定義ファイルで、Faro から送信されてきた RUM を確認することができます。 アラートの定義ファイルです。 contact-points.yaml 「 アラートからエラー原因の探索 (Alerting) 」章で説明している Contact points を定義しているファイルです。 alert-rules.yaml 「 アラートからエラー原因の探索 (Alerting) 」章で説明している Alert rules を定義しているファイルです。 Mimir 設定ファイル Mimir に適用する設定ファイルです。 mimir.yaml 「common.storage.backend」と「common.storage.s3」ノードで、ストレージに S3(実際には互換ストレージの MinIO)を指定します。 Loki 設定ファイル Loki に適用する設定ファイルです。 loki.yaml 「common.storage.s3」ノードで、ストレージに MinIO を指定します。 Tempo 設定ファイル Tempo に適用する設定ファイルです。 tempo.yaml 「metrics_generator」ノードで、サービスグラフの描画用にトレースからメトリクスの生成と、生成されたメトリクスの送信先を指定します。 「storage.trace.backend」と「storage.trace.s3」ノードで、ストレージに MinIO を指定します。 Alloy 設定ファイル Alloy に適用する設定ファイルです。 config.alloy 「pyroscope.receive_http」ノードで Pyroscope から送信されてくるデータの受信ポートと、「pyroscope.write」ノードで送信先サーバーを指定します。 「faro.receiver」ノードで Faro から送信されてくるデータの受信ポートと、「faro.receiver.output」ノードで送信先を定義するノードを指定します。送信先として指定された「loki.writ」と「otelcol.exporter.otlp」ノードで送信先のサーバーアドレスを指定します。 ロードバランサー設定ファイル Mimir、Loki、Pyroscope の前段に配置するロードバランサーの Nginx に適用する設定ファイルです。 nginx-mimir.conf nginx-loki.conf nginx-pyroscope.conf Tempo の前段に配置するロードバランサーの Nginx に適用する設定ファイルです。 nginx-tempo-otlp.conf nginx-tempo-view.conf Tempo では、OpneTelemetry Collector からトレースが送られてくるポート番号(4318)と、Grafana から表示用にアクセスしてくるポート番号(80)が異なっているため、それぞれのロードバランサーを建てています。 OpenTelemetry Collector 設定ファイル OpenTelemetry Collector コンテナに適用する設定ファイルです。 otel-collector-config.yaml 「receivers.otlp.protocols.http」ノードで、Web アプリケーションから OpenTelemetry 形式のメトリクスを HTTP で受信できるように指定します。 「receivers.prometheus.config.scrape_configs」ノードの「job_name: “webapp”」設定で、OpenTelemetry Collector コンテナから Web アプリケーションへ Prometheus 形式でメトリクスを取得するように指定します。 「receivers.mysql」ノードで、MySQL サーバーからメトリクスを取得するように指定します。 プロダクション環境の構成ファイル解説 プロダクション環境のコンテナを構築する各種ファイルについて説明します。 コンテナ定義(docker-compose) アプリケーションを構成する各コンテナを構築する「docker-compose-webapp.mode.grafana.yml」ファイルです。 docker-compose-webapp.mode.grafana.yml 「webapp-webui」と「webapp-webapi」コンテナが起動する際に実行するコマンドを指定する「command」ノードでは、Web アプリケーションを Pyroscope の自動計装でプロファイリングさせたいので、Web アプリケーションの起動に「 ./gradlew bootRun 」は使わずに「 java -javaagent:pyroscope.jar 」コマンドを使い該当のライブラリをロードしながら起動します。 まとめ 実際に手を動かしてログやメトリクスを追うことで、「オブザーバビリティー(可観測性)」という言葉の解像度がぐっと上がったのではないでしょうか? 複雑な理論を学ぶことも大切ですが、こうして実際の画面でシステムの挙動を見る体験こそが、DevOps 実践への第一歩だと考えており、今回の体験を通して、「意外と自分でも構築できそうだ」「この機能は今の現場でも使えそうだ」と、少しでも身近に感じていただけたなら本望です。 なお、本記事で紹介した OSS 版の活用はもちろんのこと、運用負荷を軽減したい場合には Grafana Cloud を選択肢に入れることも可能です。「まずは OSS で小さく始めて、規模が大きくなったら Cloud へ」といった柔軟な使い分けも検討いただけます。 このデモ環境での体験を切っ掛けに、ぜひ次は皆様自身のアプリケーションやインフラ環境でも、この「見える化」に挑戦するモチベーションに繋がれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Grafana OSS LGTM スタックで体験する『オブザーバビリティー入門』 first appeared on SIOS Tech Lab .
アバター
こんにちは。サイオステクノロジーのひろです。 先日OSC福岡、OSC広島でセミナー登壇してきました。 今回は登壇内容についてまとめていきたいと思います。 生成AIの概要を理解できる 生成AI×ツールで新たな価値を生み出せることを理解できる この2点を目的として以下のことについてお話してきました。 生成AIにできることできないこと 生成AIにツールを使わせる技術について 生成AIにロボットを操作させるデモ セミナーに使用した資料を交えつつセミナー内容について解説していきます。 生成AIってなに? 生成AIとはコンテンツを新たに生み出してくれるAIのことを指します。 ユーザがプロンプトを入力することで生成AIは文章や画像や動画、音楽、音声等を出力してくれます。 例としては、テキスト生成であれば Gemini 3.0 GPT-5 Claude Sonnet 4.5 等がありますね。 まず、テキスト生成に焦点をあてて、LLMが文章を生成する過程について説明しました。 テキスト生成を行うLLM(大規模言語モデル)は膨大なテキストデータから言語のパターンを学習したもので、文脈から次に来る確率が高い言葉を予測し、リストアップされた言葉の中からランダムに単語を選択することを繰り返して文章を生成します。 例えば「今日はいい日だ。」という文章を生成する過程を考えてみます。 以下の図のように、まず、「今日は」の次に来る可能性が高い言葉として、「いい」、「天気」、「寒い」がリストアップされ、その中から確率で選択されます。今回は「いい」が選択されていますね。この操作を繰り返します。 ここで一点重要なのは生成AIは最も確率が高い言葉を選択するわけではないという点です。 試しに同じプロンプトを何度か生成AIに投げていただけると異なるレスポンスが来ると思いますので、この仕組みを実感できると思います。 LLMは言葉を理解しているのではなく、パターンを知っており、そのパターンに倣って言葉を連ねているということになります。こちらについては、永田さんの記事「 AIは「1+1って、2になること多いなあ」と思っている!? 」でLLM内部で起こっていることについて解説されてるので気になった方は是非ご覧ください。 次に、生成AIモデルでできることできないことを切り分けるために、まずLLMモデルと生成AIサービスの違いの説明を行いました。 LLMと生成AIサービス。一体どう違うのかというと、機能が異なります。 LLM 生成AIサービス 機能 文章の生成 LLMの機能+履歴保持、web検索、ファイルアップロード,etc 例 GPT-5等 ChatGPT,Gemini等 生成AIサービスにはLLMの機能に加えて様々な機能が追加されています。 例えば表にあるように履歴保持機能。これがあるとこれまでの会話の文脈を生成AIが理解してくれるのでしっかり会話が成り立ちますね。 web検索機能。最近の生成AIサービスは検索機能を持っており、生成AIにリアルタイムの出来事を回答させることもできます。例えば今日の天気等をGeminiに聞いてみると検索結果から回答してくれます。 ChatGPTやGemini等の生成AIサービスは純粋なLLMとは異なる機能を持っているということです。 そして、生成AIモデル単体でできることとできないことを挙げると以下のようになります。 LLM単体でできないことについて解説していきます。 最新の情報に関する回答 LLMモデルはリアルタイムで学習し知識を更新する仕組みはなく、知識のカットオフ日が存在します。 カットオフ日とは、LLMが学習したデータがいつまでのデータかを指し、LLMはその日までのデータしか知り得ません。 そのため、例えば今日の天気を聞いても学習していないので回答することができません。 試しにGPT-4o-miniに対し、カットオフ日について尋ねてみました。 私の知識のカットオフ日は2023年10月です。それ以降の情報については持っていませんので、ご了承ください。何か質問があればお答えします! このLLMは2023年10月までのことしか知らないというわけですね。 試しに「2025年は何が流行しそうか考えてみてください」と尋ねてみました。 2025年に流行しそうなトレンドについて、現在の技術や社会の動向を考慮した上でいくつかの予測を挙げてみます。 持続可能なライフスタイル: 環境意識の高まりに伴い、エコフレンドリーな製品やサービス、サステナブルなファッションが流行する可能性があります。プラスチックの削減やリサイクル製品の人気が高まるでしょう。 ウェアラブルテクノロジー: 健康管理やフィットネスのためのウェアラブルデバイスが進化し、より多機能で使いやすくなることで、一般的なアイテムとして普及するかもしれません。 メタバースとバーチャル体験: メタバース関連のプラットフォームが進化し、仮想空間でのソーシャルインタラクションやエンターテインメントが一般的になる可能性があります。特に、ゲームや仮想イベントが人気を集めるでしょう。 デジタルアートとNFT: デジタルアートやNFT(非代替性トークン)が引き続き注目され、アート市場やエンターテインメント業界で新しい形のクリエイションが流行するかもしれません。 パーソナライズされた医療と健康: 個別化医療や遺伝子ベースの健康管理が進展し、自分に最適な健康法や食事が提案されることで、健康志向のライフスタイルが流行する可能性があります。 リモートワークとフレキシブルな働き方: リモートワークやハイブリッドな働き方が一般化し、職場環境や働き方に関する新しいトレンドが形成されるでしょう。これにより、ワークライフバランスを重視する文化が広がる可能性があります。 エンターテインメントの新形態: ストリーミングサービスやポッドキャスト、バーチャルコンサートなど、デジタルコンテンツの消費スタイルが進化し、新しいエンターテインメントの形が流行するかもしれません。 これらはあくまで予測であり、実際に何が流行するかは様々な要因によって変わるでしょう。未来のトレンドは常に変化し続けるため、柔軟な視点で見ることが重要です。 もう2026年ですが2025年の流行を予想してもらいました。 あたっているかはさておき、このようにカットオフ日以降のことは知らないんですよね。 これから時間が経って、年齢を重ねたころにこのモデルと会話できたら懐かしい気分になれるかもしれません。 独自の情報に関する回答 LLMはもちろん学習したことしか知りません。 そのため、例えば会社の社内規約等のクローズドな情報について聞いても回答することができません。 情報の正確性の保証 LLMにはハルシネーションという、あたかも真実を語るように真っ赤な噓を吐くことがあります。 LLMが文章を生成する過程でもお話しましたが、LLMは、確率で単語を選び、それを繰り返して文章を作成するので、正しいこと以外も出力します。 LLMが本当に正しいことを言っているのか、人間が確認する必要があります。 複雑な計算 何か計算してとLLMに入力したとして、LLMは実際に計算しているわけではなく、学習パターンに基づいて次来る単語を生成しているため、複雑な計算は間違えることがあります。 AIは計算を理解しているわけではなく、 「1+1って、2になること多いなあ」と思っている ということですね。 現実世界やデジタル環境の操作 LLMはテキストを生成するのみで、例えば部屋の電気は消してくれませんし、notionでドキュメントを作成してくれることはありません。 このように生成AIにはできないことがありますが、これはツールと組み合わせることで解消できる場合があります。 生成AI×ツール 生成AIとツールを組み合わせることで多くのことができるようになります。 セミナーでは、RAG、FunctionCalling、MCPについてご紹介しました。 RAG RAGはRetrieval Augmented Generationと呼ばれ、検索拡張生成等と訳される技術です。 生成AI×検索ツールですね。 生成AIが検索ツールを使用してデータを検索し、取得したデータを基に回答を行います。 RAGを活用することで、生成AIはリアルタイムの情報や学習していない独自の情報を手に入れることができます。 また、情報源が明確になるため、根拠のある回答をしてくれますし、根拠をユーザが確認することができるようになります。 前章で挙げた生成AIにできないことのうち以下の項目については解消できそうと思っていただけるのではないでしょうか。 最新の情報に関する回答 独自の情報に関する回答 情報の正確性の保証 Function Calling 次にFunctionCallingです。 FunctionCallingは生成AIに関数を呼び出させる機能です。 関数の実行はアプリケーション側で行うため生成AIのレスポンスを翻訳する部分は実装する必要がありますが、生成AIがどの関数をどんな引数で実行するのか判断してくれます。 例えば検索、計算、外部APIの使用、IoT連携等、様々な機能を生成AIと組み合わせることが可能です。 複雑な計算ができる関数を用意しておけば、生成AIが苦手な計算だけ関数にさせることもできますし、ロボットを動作させる関数なんてのを作成しておけば、生成AIにロボットを操作させることもできるというわけですね。 組み合わせ次第で強力なものが生まれそうな気がします。 Azure OpenAIでFunctionCallingを行う方法については こちら のブログ記事で解説してますので興味がある方はぜひご覧ください。 MCP MCPはAnthropic社が提唱した、生成AIとツールを繋ぐUSB-typeCのような共通規格です。 これまでFunctionCallingを用いたLLMアプリを作成した場合、あるツールを別のアプリでも使用したいとなった場合、アプリ間の言語が異なったり必要なライブラリが異なれば、関数を改修する必要がありました。 また、ツールリストの定義方法はLLMによって異なるため、アプリで使用するLLMが異なれば、その点を改修する必要が出てきます。 MCPを使用した場合、MCPクライアントというものを用意し、LLMアプリと別プロセスで動作するMCPサーバをツールとして扱うようにします。 そうすると、MCPサーバ1つ作成すれば、どのLLMアプリからも使用できるようになるので、アプリ毎に関数を書いたり、ツール定義を行う必要がなくなります。 また、MCPサーバを公開しているサービスは増えており、例えばnotionやblender、googleカレンダー等のMCPサーバを組み込むことが容易です。 公開されているMCPサーバについては こちら をご確認ください。 生成AIとツールを組み合わせる技術であるRAG、FunctionCalling、MCPについて解説を行いました。 続いてデモの解説に移ります。 Qumcum連携 具体的にFunctionCallingでQumcumを生成AIに操作させるデモを行いました。 QumcumはBluetoothによる通信が可能な小型ロボットです。 主な機能は距離センサや音検知、発声等がありますが、今回使用したのは頭、腕、足の回転です。 また、LLMとしてAzure OpenAIのモデルを使用しました。 Azure OpenAIについてはデプロイから実際にAPIを叩くまでをブログ記事にしていますので こちら をご確認ください。 シーケンス図は以下のようになります。 まず、プロンプトの分析をLLMにリクエストし、結果を構造化出力させています。これは分析結果(プロンプトから読み取れる感情、プロンプトに対するロボットの感情、プロンプトの要約等)をUIとして表示するために使用しています。 構造化レスポンスについてもブログにまとめているので、 こちらの記事を ご覧ください。 その後、分析結果とプロンプト本文をLLMに渡し、FunctionCallingを行います。 使用する関数を選択してもらい、アプリケーション側でロボット動作関数を実行しています。 デモ動画はこちらです。 このデモでは、入力したプロンプトからFunctionCallingによって関数が選び取られていることを表しています。 ロボットが万歳をする関数や、足踏み、首振りを行う関数が選び取られ、実行されているのがわかります。 今後の展望 具体的な展望ではないですが、今後できたらおもしろいなと考えていることは以下のようなことです。 テキスト入力から音声入力へ修正 Qumcumの発話機能を活用し、リアルタイム会話機能実装 今までの会話内容を記録し、RAGによって相棒、友人のような会話を可能に RAGを用いて生成AIの相棒を作るはらちゃんのブログは こちら を参照ください。 生成AIとツールを組み合わせることで、某未来から来たネコ型ロボットのような友人を自分の手で作ることができるかもしれませんね。 まとめ 生成AI×ツールによって、生成AI単体ではできなかったことが可能になります。 最新の情報に関する回答 独自の情報に関する回答 現実世界やデジタル環境の操作 等が可能です。 FunctionCallingやMCPを活用して新たな組み合わせによる新たな価値を生み出していきましょう。 閲覧いただきありがとうございました。 セミナーに参加してくださった皆さん、ご清聴ありがとうございました。 わかりやすく伝えられるセミナーを今後も行っていきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OSC福岡&広島2025で生成AI×ツールについてセミナー登壇してきました first appeared on SIOS Tech Lab .
アバター
ども!龍ちゃんです。 前回の記事 では Draw.io の公式 MCP Tool Server を検証しました。しっかり動くし、Mermaid 入力のトークン効率もよかったのでぱっと使う分には良かったですね。問題があったんでブログを書いているんですけどもwww 今回の内容です。 MCP Tool Server の問題 — ファイルが残らない Skill + VS Code 拡張機能のセットアップ VS Code 内で完結する作業の流れ(生成 → プレビュー → 編集 → エクスポート → Git 管理) MCP Tool Server との比較と使い分け Mermaid / PlantUML も含めた図解ツール全体の選び方 MCP Tool Server の問題: 設計図が Git 管理できない 検証していて問題になったことがあります。 Draw.io で描く図って、設計情報なんですよね。アーキテクチャ図、シーケンス図、フロー図。どれもシステムの設計を表現したもので、ソースコードと同じように GitHub に置いてバージョン管理したい。PR でレビューもしたい。 ところが MCP Tool Server は URL 方式です。図を生成すると app.diagrams.net の URL が返ってきて、ブラウザで開く。図のデータは URL に埋め込まれているだけで、手元にファイルが残らない。git add できるものがないんですよね。 MCP が悪いわけじゃないです。ブラウザで図を確認・共有する用途は十分見たいしてます。Slack に URL を貼れば相手もすぐに見られるし、受け取り側に環境も要らない。ただ、設計情報を Git で管理するという用途には合わないだけですね。 同じ jgraph/drawio-mcp リポジトリに、 .drawio ファイルをローカルに生成するアプローチがあります。これと VS Code の Draw.io 拡張機能を組み合わせると、生成からプレビュー、手動編集、エクスポート、Git 管理まで VS Code の中で完結するので、そちらの検証結果をブログにまとめています。 Skill + 拡張機能のセットアップ やることは2つだけです。Skill 定義ファイルを1つと VS Code の拡張機能を1つ追加する。MCP サーバーの起動も Node.js も要りません。 Skill のセットアップ jgraph/drawio-mcp リポジトリの skill-cli/SKILL.md を取得して、プロジェクトに配置します。 mkdir -p .claude/skills/drawio curl -sL https://raw.githubusercontent.com/jgraph/drawio-mcp/main/skill-cli/SKILL.md \ > .claude/skills/drawio/SKILL.md これだけです。MCP のように .mcp.json に設定を書いたり、 npx でサーバーを起動したりする必要はありません。 VS Code 拡張機能の追加 .drawio ファイルを VS Code で開けるようにするために、Draw.io の拡張機能を追加します。 devcontainer を使っている場合は devcontainer.json に追加するだけです。 { "customizations": { "vscode": { "extensions": [ "hediet.vscode-drawio" ] } } } ローカル環境なら VS Code のマーケットプレイスから hediet.vscode-drawio をインストールしてください。 この拡張機能を入れると、 .drawio ファイルを開いたときに VS Code 内に GUI エディタが表示されます。ドラッグ&ドロップで図形を動かしたり、色を変えたり、接続線を引いたりできます。ブラウザの draw.io と同じ操作感です。 PNG や SVG へのエクスポートも、この拡張機能のメニューから手動でできます。draw.io の CLI ツールは要りません。 なぜ「CLI」ではなく「拡張機能」なのか jgraph/drawio-mcp のリポジトリでは、このアプローチを「Skill + CLI」と呼んでいます。ここでいう CLI は draw.io のデスクトップアプリをコマンドラインで使うエクスポート機能( drawio -x -f png ... )のことです。 ただ、devcontainer 環境でこの CLI を動かそうとすると面倒なんですよね。draw.io のデスクトップアプリは Electron ベースなので、ヘッドレス環境で動かすには xvfb と大量の X11 依存パッケージが必要になる。Docker イメージが 500MB くらい膨らみます。 VS Code の Draw.io 拡張機能なら、devcontainer.json に1行追加するだけで済みます。プレビュー、編集、エクスポートが全部揃います。 devcontainer 環境での現実解は Skill + 拡張機能 です。 VS Code で完結する作業の流れ セットアップが終わったら、実際の作業の流れを見ていきましょう。ブラウザを開かずに、VS Code の中だけで図の生成から Git 管理まで完結します。 生成 → プレビュー → 編集 → エクスポート → Git 管理 流れはこうなります。 Claude Code に指示 → .drawio ファイル生成 → VS Code でプレビュー(Draw.io 拡張が自動で開く) → GUI で手動微調整(色・配置・接続) → 拡張機能から PNG/SVG にエクスポート → git add → commit → push 実際に Claude Code に指示するときはこんな感じです。 ログイン処理のフローチャートを .drawio で作って。 開始 → メールアドレス入力 → パスワード入力 → 認証API呼び出し → 成功/失敗の分岐 → 完了 特別なコマンドは要りません。 .drawio で作って と言えば、Skill の定義を参照して .drawio ファイルを生成してくれます。フローチャートに限らず、アーキテクチャ図やシーケンス図も同じように自然言語で指示するだけで作れます。 AI 生成 + 手動仕上げ Claude が生成する .drawio ファイルは、そのまま使えるレベルではあります。ノードの配置も接続も合っている。ただ、色やレイアウトの微調整は人間が GUI でやったほうが速いんですよね。 ここが Mermaid や PlantUML との大きな違いです。テキストベースのツールだと、色を変えたければソースコードを編集して再レンダリングする。Draw.io なら GUI で直接ドラッグして色を塗れば終わりです。Figma を触る感覚に近いです。 AI が 80% の構造を作って、人間が 20% の見た目を GUI で仕上げる。このハイブリッドが .drawio ファイルとして Git にコミットされるのが、この流れのポイントです。 GitHub での管理 .drawio ファイルは Git にそのままコミットできます。バージョン管理される。ブランチを切って PR でレビューもできる。 設計情報を Git 管理する最大のメリットは、PR で図をレビューできることです。設計変更があったら図も一緒に PR に含めて、レビュアーが確認できる。ただ、ここで1つ問題があります。 GitHub は .drawio ファイルをレンダリングしません。リポジトリ上で開くと生の XML が表示される。これだと PR を開いてもレビュアーは図が見られないんですよね。 解決策が .drawio.svg 形式です。VS Code の拡張機能から SVG にエクスポートすると、SVG の中に Draw.io の XML が埋め込まれます。GitHub 上では画像として表示されるので、PR でレビュアーが図を確認できる。しかも draw.io で開けば再編集もできます。 .drawio ファイル(編集用ソース)と .drawio.svg (GitHub 表示・レビュー用)の両方をコミットしておくのがおすすめです。 CI で自動変換したい場合は render-drawio-action も選択肢に入ります。 MCP vs Skill の比較 ここまで Skill + 拡張機能のセットアップとワークフローを見てきました。じゃあ MCP Tool Server はもう要らないのかというと、そんなことはないです。用途が違うだけなんですよね。 比較表 比較項目 MCP Tool Server Skill + 拡張機能 出力 URL → ブラウザで確認 .drawio ファイル → VS Code で確認 Git 管理 できない(ファイルが残らない) できる(.drawio をコミット) 入力フォーマット XML / Mermaid / CSV XML のみ コンテキスト消費 3ツール分の定義が常に載る 使うときだけ読み込まれる(普段はゼロ) 編集環境 ブラウザ(app.diagrams.net) VS Code 内 オフライン 初回ダウンロード後は可能 完全オフライン対応 セットアップ .mcp.json + Node.js SKILL.md 1つ + 拡張機能 エクスポート ブラウザから手動 拡張機能から手動 Mermaid 入力に対応しているのは MCP だけです。Mermaid はトークン効率が良くて、XML の 1/10 くらいで同じ図を表現できる。フローチャートで比べると、XML だと約 800 トークンかかるところが Mermaid なら約 100 トークンで済みます。 逆に、Git 管理ができるのは Skill + 拡張機能だけです。ここは明確に分かれます。 どちらを選ぶか 判断はシンプルです。 Git で管理したい → Skill + 拡張機能。これ一択 Mermaid から変換したい → MCP Tool Server URL を共有して相手にすぐ見せたい → MCP Tool Server VS Code の中で完結したい → Skill + 拡張機能 両方使い分けたい → 併用できる。同じリポジトリの別アプローチなので競合しない 僕の場合は、設計情報を GitHub で管理するのが前提なので Skill + 拡張機能をメインで使っています。Mermaid から Draw.io に変換したいときだけ MCP を使う、という使い分けですね。 全ツールの使い分け — Draw.io だけで考えない Draw.io の話をしてきましたが、図を作るツールは Draw.io だけじゃないです。Mermaid、PlantUML、HTML ベースの図解もある。目的に合わせて選ぶのが大事なんですよね。 Draw.io の立ち位置 Draw.io の強みは GUI で直感的に編集できることです。AI が生成した図をそのままドラッグして色を塗って形を変えられる。Mermaid や PlantUML だとソースコードを書き換えて再レンダリングしないといけないけど、Draw.io ならマウスで触るだけです。 デザインの自由度も段違いで、色、形状、アイコン、グラデーション、何でもいける。素の Mermaid / PlantUML だと見た目の調整に限界があって、 Mermaid を Tailwind で拡張する記事 と PlantUML を Tailwind で仕上げる記事 を書いたくらいなんですよね。Draw.io ならそもそもその問題が起きません。 ただ弱点もあります。 Git diff が見づらい : .drawio の中身は XML なので、diff を見ても座標やスタイル情報が大量に出てきて、何が変わったのかわかりにくい。Mermaid や PlantUML はテキストベースだから diff がきれいに出る GitHub でレンダリングされない : さっき書いた通り、 .drawio のままだと生 XML が表示される。 .drawio.svg で回避はできるけど、ひと手間かかる 使い分け表 ユースケース 推奨ツール 理由 ブログ記事のフロー図・概念図 HTML + Mermaid / PlantUML PNG が直接出る。テキストで完結 テキスト管理したい仕様書の図 Mermaid / PlantUML diff がきれい。テキストで完結 デザインにこだわりたい図 Draw.io(Skill + 拡張機能) GUI 編集、デザイン自由度が高い Mermaid / CSV からの変換 Draw.io MCP 変換に対応しているのはこれだけ URL を貼って即共有 Draw.io MCP リンク1つで相手が見られる ブログ記事の図を作るだけなら、正直 HTML + Mermaid で作る図解 や PlantUML で作る図解 のほうが手軽です。PNG が直接出てくるし、テキストだけで完結する。 Draw.io が活きるのは「デザインにこだわりたい」か「GUI で編集したい」場面です。設計書に載せる図とか、お客さんに見せる図とか、見た目が大事な場面ですね。 判断フロー 迷ったらこの流れで考えるとすっきりします。 仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン や 図解作成、AIに丸投げしたら「たまに自分より上手い」件 も合わせて読むと、図解ツール全体の使い分けが見えてくると思います。 まとめ Draw.io で描く図は設計情報です。設計情報なら GitHub で管理したい。MCP Tool Server は URL 方式で図が手元に残らないから、Git 管理には向かない。 だから Skill + VS Code 拡張機能を使う。SKILL.md を1つ置いて、devcontainer.json に拡張機能を1行追加する。これで .drawio ファイルの生成からプレビュー、GUI 編集、エクスポート、Git 管理まで VS Code で完結します。 MCP Tool Server が使えないわけじゃないです。Mermaid 変換や URL 共有には MCP のほうが向いている。同じリポジトリに両方のアプローチが用意されているのは、用途が違うからなんですよね。 ツールは目的で選ぶ。Git 管理が要るなら Skill + 拡張機能、共有が要るなら MCP。テキスト管理や diff 重視なら Mermaid / PlantUML もある。全部を1つで解決しようとしないで、場面に合わせて使い分けるのがいいんじゃないかなと思います。 ほなまた〜 関連ブログ Draw.io MCP シリーズ 前回の記事: Draw.io 公式 MCP でできること・セットアップガイド — MCP Tool Server のセットアップと Mermaid / CSV 入力の検証結果をまとめた記事です MCP と Skills の使い分け Claude Code MCP が遅い・重い問題、CLI + Skills で解決 — Notion・Playwright MCP の接続・トークン問題を CLI + Skills 移行で軽量化した話。今回の「用途の適合性」とは別の切り口です Claude Code: 公式MCPを補完するSkills設計パターン — 公式 MCP の不足機能を Skills で補完する設計パターン。MCP → CLI → Skill の判断基準を整理しています AI × 図解作成 図解作成、AIに丸投げしたら「たまに自分より上手い」件 — Claude Code Skill で HTML 図解を自動生成 → PNG 変換する流れ。気に入ったデザインを蓄積して AI のスキルを育てる方法も紹介 ClaudeでMermaid図作成を自動化!2時間→5分の劇的時短術 — Claude + Mermaid でフローチャートやシーケンス図を自動生成。Live Editor の活用術と日本語フォントの対処法 Claude Codeで仕様書のPlantUML図を自動生成 — PlantUML の各種図を Claude Code で自動生成し、VS Code Preview で確認する環境構築と実践例 仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン — Mermaid / PlantUML のソースコードと設計意図を HTML コメントで添える設計パターン。AI が推測ではなく正確に読める形式にする方法 図解のデザイン強化 Mermaidのデザインが物足りない?Tailwindで拡張して設計書品質に — Mermaid 図に Tailwind CSS の注釈パネルを組み合わせて設計書品質にアップグレード。PNG 変換手段とテンプレートも紹介 PlantUMLの表現力をTailwind CSSで設計書品質に仕上げる — PlantUML + Tailwind CSS で設計書品質を実現。skinparam 設定と2つのレイアウトテンプレート ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Draw.io公式Skillで設計図をGitHub管理 — VS Code完結のセットアップ first appeared on SIOS Tech Lab .
アバター
ども!最近は仕様書×AIの文脈で図解まわりの検証をやってる龍ちゃんです。 Draw.io の開発元である jgraph 社が、公式の MCP サーバーを出しました。 jgraph/drawio-mcp リポジトリで Apache 2.0 ライセンスで公開されています。 コミュニティ実装の MCP サーバーはいくつかありましたが、公式となると話が変わってきます。draw.io 本体と同じ開発元が直接メンテしてくれるので、コミュニティ版よりも長くメンテされやすいんですよね。 MCP の基本的な仕組みについては「 AIエージェントの進化が分からない?秘書で理解する3段階 」で解説しているので、「MCPってなに?」という方はそちらを先にどうぞ。 jgraph/drawio-mcp リポジトリ、中を見てみると実は4つのアプローチが入っていて、それぞれ対象環境や出力形式が異なります(全体像は後半で紹介します)。 この記事では、Claude Code ユーザーに一番スタンダードな MCP Tool Server を試していきます。セットアップ方法、3つの入力フォーマット(Mermaid / XML / CSV)の使用感、そして正直「できないこと」まで、検証した結果をまとめました。 今回の内容です。 MCP Tool Server のセットアップ( .mcp.json に数行追加するだけ) 3つの入力フォーマットを実際に試した比較 MCP Tool Server の「できないこと」を正直に語る 向いている人・向いていない人の判断材料 龍ちゃん できないことを紹介していますが、それの対処法に関しては「 Draw.io公式Skillで設計図をGitHub管理 — VS Code完結のセットアップ 」で紹介しています。併せてご一読ください! セットアップ: .mcp.json に数行追加するだけ Claude Code なら、ターミナルで以下のコマンドを実行するだけ。 claude mcp add -s project drawio -- npx @drawio/mcp これで .mcp.json が自動で作られます。手動で書く場合はこう。 { "mcpServers": { "drawio": { "type": "stdio", "command": "npx", "args": ["@drawio/mcp"] } } } 前提として Node.js が必要です(npx を使うので)。検証時の環境は Node.js v25.7.0、npm パッケージ @drawio/mcp v1.1.2 でした。 以前 MCP のトークン消費問題について記事を書いた んですが、draw.io MCP はツール定義が3つしかないので、コンテキストへの負担はかなり軽い部類です。Notion MCP みたいに大量のツールが常駐するタイプとは全然違います。 使えるようになる3つのツール ツール 入力 用途 open_drawio_mermaid Mermaid.js 構文 フローチャート・UML 全般 open_drawio_xml draw.io XML(mxGraphModel) 細かいスタイル制御が必要な図 open_drawio_csv draw.io CSV インポート形式 データ駆動の組織図・ツリー どのツールも lightbox (読み取り専用モード)と dark (ダークモード)のオプションが付いています。 設定はこれだけで、すぐに使い始められます。 使ってみた: 3つの入力フォーマット Mermaid で図を生成 まずは Mermaid から。Claude Code に「ロードバランサー構成図を Mermaid で描いて」とお願いしてみました。 すると Claude が Mermaid 構文を生成して、MCP ツール経由で draw.io に渡してくれます。数秒後にブラウザがパッと開いて、draw.io のエディタに図が表示されました。 これ、良いですね。Mermaid の構文自体がシンプルなので、5ノード程度の図なら入力トークンは約100程度で済みます。しかも Mermaid エンジンが自動レイアウトしてくれるので、ノードが重なったり変な配置になったりしない。ノード数が増えても構文が簡潔なぶん、トークン消費は抑えられます。 ブラウザで開いた draw.io エディタ上で、色やフォントを変えたりノードの位置を微調整したりもできます。AI が下書きして、人間が仕上げる感じですね。 XML で図を生成 次に XML。同じ構成図を XML フォーマットで試してみます。 XML だと draw.io の全プロパティを指定できるので、ノードの色、枠線のスタイル、フォントサイズまで細かく制御できます。Mermaid ではテーマに依存する部分も、XML なら自由自在。 ただし、トレードオフがあります。 同じ図を表現するのに XML は約800トークン使います。Mermaid が約100トークンだったので、ざっくり8倍。さらに、AI がノードの座標(x, y)を自分で計算して配置するので、レイアウトが微妙になることもあります。Mermaid なら自動でいい感じに並べてくれるのに、XML だとそこは AI 任せなんですよね。 トークンもレイアウトも Mermaid に分がありますが、「この色で、このフォントで、この位置に」って指定したいなら XML 一択です。 CSV で図を生成 最後に CSV。これは少し特殊で、draw.io 固有の CSV インポート形式に従う必要があります。 CSV ヘッダーに # label: %name% のようなメタ記法を書いて、データ行で親子関係を定義していく形式です。ツリー構造や組織図を作るには向いてるんですが、ただ汎用性はあまり高くないので、フローチャートや UML を CSV で書こうとは思わないですね。 フォーマット比較 3つ試した結果をまとめます。 比較項目 Mermaid XML CSV トークン効率 高(XML の約 1/10) 低(冗長) 中 レイアウト 自動 手動(AI が座標指定) 自動 スタイル制御 低(テーマ依存) 高 中 対応図形 flowchart, sequence, class, state, ER 等 全て ツリー・組織図 結論としては、特別な理由がなければ Mermaid を使うのが正解 です。トークン効率が XML の約 1/10、自動レイアウトで配置も綺麗、対応する図形タイプも広い。XML は「どうしても色やスタイルを細かく指定したい」ときの選択肢。CSV は組織図やツリーを大量のデータから生成したいときのニッチ枠です。 正直に言う: MCP Tool Server の「できないこと」 ここまで「Mermaid いいじゃん」って話をしてきましたが、MCP Tool Server には正直「できないこと」も結構あります。 まず仕組みを知っておく さっき Mermaid で図を生成したとき、ブラウザがパッと開きましたよね。あれ、実はローカルにファイルを作ってるわけじゃないんです。 MCP Tool Server は、AI が生成したコンテンツを圧縮・エンコードして app.diagrams.net の URL に全部詰め込んでいます( ソースコード )。その URL をブラウザで開くと draw.io エディタが表示される、という仕組みです。 つまり すべてが URL に埋め込まれている 。ローカルにファイルは一切残りません。 この「URL 方式」が、以下の制約の根本原因です。 できないこと 一番大きいのは、 ローカルにファイルが残らない ことです。 .drawio ファイルが生成されないので、Git で管理したり、VS Code の Draw.io 拡張で開いたりができません。 これに連動して、VS Code 内で作業を完結させることもできません。図を確認するにはブラウザに切り替える必要があるし、PNG や SVG が欲しければ draw.io エディタから手動でエクスポートする手間が発生します。GitHub に図を置きたい場合も、URL からは .drawio ファイルを取り出せないので、リポジトリ管理のワークフローとは噛み合わない。 もう1つの本質的な制約は、 差分更新ができない こと。URL は毎回まるごと新規生成されるので、「さっきの図のここだけ修正して」ということができません。修正するには図全体を再生成するか、ブラウザの draw.io エディタ上で手動で直す必要があります。 あとは環境面の話で、 app.diagrams.net に接続できないとそもそも動きません。オフラインでは使えないです。 一方で、URL だからこそ便利な面もある URL をそのまま Slack やチャットに貼れば、相手がブラウザで即座に図を見られます。受け取る側に draw.io のインストールは不要で、ブラウザさえあれば OK。しかも URL を開いた先の draw.io エディタでそのまま編集もできるんですよね。 ファイルを共有してもらって、ツールを入れて、開いて…みたいな手間がゼロ。検証で 27ノードのマイクロサービス構成図を作ったときも、URL 長は 1851 文字でブラウザの制限内に収まっていました。 jgraph/drawio-mcp の4つのアプローチ ここまで MCP Tool Server を試してきましたが、 jgraph/drawio-mcp リポジトリには4つのアプローチが用意されています。 アプローチ 対象環境 出力形式 MCP 必要 MCP Tool Server Claude Code, Cursor 等 URL → ブラウザで draw.io を開く ○ MCP App Server Claude Desktop 等 チャット内にインライン描画 ○ Skill + CLI Claude Code .drawio ファイルをローカル生成 × Project Instructions Claude Project Python コード実行で URL 生成 × 「MCP サーバー」と聞くと1つのツールを想像しますが、対象環境と用途に応じて手段が分かれています。MCP Tool Server と MCP App Server は MCP 対応クライアントが必要で、Skill + CLI と Project Instructions は MCP なしで動作します。 まとめ: 向いている人・向いていない人 Draw.io 公式 MCP Tool Server、使い所を選べば便利だけど万能ではない、というのが正直な感想です。 向いていない人 を先に挙げておきます。 VS Code で作業を完結させたい .drawio ファイルをローカルで管理したい GitHub リポジトリに図を保存したい 向いていない人向けには、上で紹介した Skill + CLI が選択肢になります。MCP なしで .drawio ファイルをローカルに生成できるので、VS Code + Git のワークフローに乗せられます。次回の記事で Skill + CLI のセットアップと MCP Tool Server との比較をやっていく予定です。 逆に、 向いている人 はこういう方です。 ブラウザで draw.io を使い慣れている Mermaid で図を書きたい(トークン効率が圧倒的) セットアップを最小限にしたい( .mcp.json に数行追加するだけ) URL を Slack やチャットで共有したい 最後にもう1つ。Draw.io は Mermaid や PlantUML とは違って GUI エディタなので、AI が生成した図を人間がブラウザ上で手で仕上げるワークフローが成立します。テキストベースのツールだと AI の出力がそのまま最終成果物になりがちですが、draw.io なら色・配置・形状を自由に調整できる。この「AI が 80% 作って、残りを人間が仕上げる」感覚は、他のダイアグラムツールにはないものでした。 ではまた! 関連ブログ MCP と Skills の使い分け Claude Code MCP が遅い・重い問題、CLI + Skills で解決 — Notion・Playwright MCP の接続・トークン問題を CLI + Skills 移行で軽量化した話。MCP のパフォーマンス面の課題と解決策を扱っています Claude Code: 公式MCPを補完するSkills設計パターン — 公式 MCP の不足機能を Skills で補完する設計パターン。MCP → CLI → Skill の判断基準を整理しています AIエージェントの進化が分からない?秘書で理解する3段階 — MCP の基本的な仕組みを秘書の比喩で解説。「MCP ってなに?」という方向けの入門記事です AI × 図解作成 図解作成、AIに丸投げしたら「たまに自分より上手い」件 — Claude Code Skill で HTML 図解を自動生成 → PNG 変換する流れ。気に入ったデザインを蓄積して AI のスキルを育てる方法も紹介 ClaudeでMermaid図作成を自動化!2時間→5分の劇的時短術 — Claude + Mermaid でフローチャートやシーケンス図を自動生成。Live Editor の活用術と日本語フォントの対処法 Claude Codeで仕様書のPlantUML図を自動生成 — PlantUML の各種図を Claude Code で自動生成し、VS Code Preview で確認する環境構築と実践例 仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン — Mermaid / PlantUML のソースコードと設計意図を HTML コメントで添える設計パターン。AI が推測ではなく正確に読める形式にする方法 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Draw.io公式MCPでできること・できないこと — 3フォーマット比較 first appeared on SIOS Tech Lab .
アバター
サイオステクノロジーのひろです。今回はAzureOpenAIを使用したFunctionCallingについて解説していきます。 このブログのゴール FunctionCallingとは何かわかる AzureOpenAIモデルを使用したFunctionCallingの実装方法がわかる FunctionCallingとは FunctionCallingはLLMに関数を使わせることができる機能です。 関数は予め作成しておく必要がありますが、このFunctionCallingを使用することで、LLMができることを拡張することが可能です。 例えば、LLMは文章の生成が可能で、様々な質問に回答してくれます。しかしなんでも正しい答えを教えてくれるわけではなく、最新の情報(例えば今日の天気等)やクローズドな情報には回答できません。 しかしながら、最新の情報を検索して取得する関数や、データベースを検索してクローズドな情報を取得する関数を用意し、FunctionCallingによってLLMに関数を使用させれば、回答させることも可能になります。 さらにそれどころではなく、外部APIを操作する関数を実行させればGoogleカレンダーへのイベント登録やメール送信も可能ですし、ロボットを動作させる関数を実行させれば現実へ干渉させることも可能になります。 組み合わせ次第で新たな価値を生み出すことができるかもしれません。 FunctionCallingでできることについて理解していただけたでしょうか。 ここから具体的な内容に入っていきたいと思います。 FunctionCallingのシーケンス どのようにLLMが関数を実行するかというと、以下のシーケンス図を見ていただければと思います。 まず、アプリケーションからLLMに対して使用可能な関数とプロンプトを渡します。 次に、LLMはプロンプトに沿って独自にどの関数を使用すべきか判断し、アプリケーションへ関数の呼び出しをリクエストします。 実行する関数を教えてもらったアプリケーションは関数を実行し、実行結果をLLMに渡します。 最終的に、LLMが実行結果をもとに回答を生成します。 このような流れでFunctionCallingが行われます。 最終的な回答を生成するまでに必要な情報が足りない場合は複数の関数を実行させることもあります。 例えば、「八王子の天気と現在時刻を教えて」とプロンプトを投げたとします。 その場合、FunctionCallingは並列で2つの関数(天気取得関数と時刻取得関数)を実行して回答します。 また、アプリケーション側で、最終的な回答がされるまでツールの実行を続ける実装を行った場合、LLMが続けて関数を実行できます。 例えば、「天気情報を取得してそれを表にまとめて」とプロンプトを投げたとします。 その場合、1回目のFunctionCallingで天気情報を取得し、2回目に表を操作する関数を使って天気情報を書きこむ等といった処理が可能になります。 生成AIが行っているのは関数と引数を渡すことのみで、関数の実行はアプリケーション側で行います。そのため、アプリケーション側でLLMから渡される関数と引数を読み取って、関数を実行する機能を実装する必要があります。 FunctionCallingの説明については以上です。 続いてAzure OpenAIにおけるFunctionCallingについて解説していきます。 Azure OpenAIのFunctionCalling Azure OpenAIのFunctionCallingは、APIを叩く際のパラメータとして、toolsとtool_choiceを追加することで可能になります。 Azure OpenAIモデルのデプロイ方法は こちら のブログで解説しております。 以下にサンプルコードを記載しています。また、サンプルコードは こちら のリポジトリで公開しています。実行方法をreadmeに記載しているのでクローンして是非試してみていただければと思います。 まずは今回FunctionCallingで使用する関数を作成しましょう。 今回非常に簡易的な、辞書型で値を返す関数を2つ作成します。 サンプルコード Python import datetime def current_time(location: str): # 簡易的に現在時刻を送信 current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return { "current_time": current_time, "timezone": location, } def current_weather(location: str): # ダミーの天気データを返す return { "location": location, "temperature": 15, "description": "晴れ", } 今回作成した関数では、地域名を引数として、疑似的にその地域の現在時刻を返す関数と天気情報を返す関数を作成しました。これらの関数をFunctionCallingで実行していきます。 次にFunctionCallingで渡すツールについてです。 渡すツール定義のJSONは以下のようになっています。 JSON { "type": "function", "function": { "name": "関数名", "description": "関数の説明(生成AIがこの説明を読んで関数を実行するか判断します)", "parameters": { "type": "object", "properties": { "【引数名】": { "type": "データ型(string, integer, booleanなど)", "description": "引数の説明" }, }, "required": [ "必須の引数名", ] } } } 関数名と関数の説明、引数の説明や引数のデータ型を定義して渡します。 ツール定義はJSONをそのまま記述すると量が多く、メンテナンスし辛いことがあるため、今回はPythonのライブラリであるPydanticを使用します。これにより、メンテナンスし易くなるだけでなく、後ほど引数のバリデーションに使用することができます。 以下はサンプルコードです。 Python from pydantic import BaseModel, Field from typing import Dict, Any class WeatherArgs(BaseModel): """get_current_weather関数の引数モデル""" location: str = Field( ..., description="天気情報を取得したい都市名(日本語)。例: '東京'", ) class TimeArgs(BaseModel): """get_current_time関数の引数モデル""" location: str = Field( ..., description="時刻を取得したい都市名(日本語)。例: '東京', 'ニューヨーク'", ) class FunctionSchemaManager: """複数の関数スキーマを管理するクラス""" @staticmethod def get_weather_tool() -> Dict[str, Any]: """天気取得ツールのスキーマ""" return { "type": "function", "function": { "name": "current_weather", "description": "指定された都市の現在の天気情報を取得します", "parameters": WeatherArgs.model_json_schema(), }, } @staticmethod def get_time_tool() -> Dict[str, Any]: """時刻取得ツールのスキーマ""" return { "type": "function", "function": { "name": "current_time", "description": "指定された都市の現在時刻を取得します", "parameters": TimeArgs.model_json_schema(), }, } @staticmethod def get_all_tools() -> list[Dict[str, Any]]: """利用可能な全てのツールを返す""" return [ FunctionSchemaManager.get_weather_tool(), FunctionSchemaManager.get_time_tool(), ] descriptionには引数の例を入れておくことで正しい引数が期待できます。 最後にFunctionCallingを行う部分のサンプルコードです。 Python import os import json from openai import AzureOpenAI from dotenv import load_dotenv from schemas import FunctionSchemaManager, TimeArgs, WeatherArgs from functions import current_time, current_weather # .envファイルから環境変数を読み込み load_dotenv() api_key = os.getenv("AZURE_OPENAI_API_KEY") endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") api_version = os.getenv("AZURE_OPENAI_API_VERSION") deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME") # Azure OpenAIクライアントの作成 client = AzureOpenAI( api_version=api_version, azure_endpoint=endpoint, api_key=api_key, ) # ツール取得 tools = FunctionSchemaManager.get_all_tools() messages = [ { "role": "system", "content": "あなたは優秀なアシスタントAIです。", }, {"role": "user", "content": "八王子の今の天気と時刻を教えてください。"}, ] # 1回目のFunctionCallingリクエスト response = client.chat.completions.create( messages=messages, max_tokens=1000, temperature=0.7, model=deployment, tools=tools, tool_choice="auto", ) response_message = response.choices[0].message messages.append(response_message) print("レスポンス:") print(response_message) # 関数実行 if response_message.tool_calls: for tool_call in response_message.tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) print(f"関数呼び出し: {function_name}") print(f"引数: {function_args}") if function_name == "current_weather": args = WeatherArgs.model_validate(function_args) # 引数チェック tool_response = current_weather(location=args.location) elif function_name == "current_time": args = TimeArgs.model_validate(function_args) # 引数チェック tool_response = current_time(location=args.location) else: tool_response = {"error": "不明な関数"} messages.append( { "tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": json.dumps(tool_response, ensure_ascii=False), } ) else: print("関数呼び出しはありませんでした。") # 最終回答生成 final_response = client.chat.completions.create( messages=messages, max_tokens=1000, temperature=0.7, model=deployment, ) print("最終レスポンス:") print(final_response.choices[0].message.content) 生成AIモデルからのレスポンスには使用する関数と引数が書かれているため、それを使用して関数の実行を行います。 Azure OpenAIのFunctionCallingは並列関数呼び出しが可能で、一回のレスポンスで複数の使用する関数名を返してくれます。 実行結果 実行結果は以下のようになりました。 レスポンス: ChatCompletionMessage(content=None, refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_iFwCt1t3Lzl2WMqQJDvP26Z7', function=Function(arguments='{"location": "八王子"}', name='current_weather'), type='function'), ChatCompletionMessageFunctionToolCall(id='call_6LbONbarZs3sCqoPiZstpieN', function=Function(arguments='{"location": "八王子"}', name='current_time'), type='function')]) 関数呼び出し: current_weather 引数: {'location': '八王子'} 関数呼び出し: current_time 引数: {'location': '八王子'} 最終レスポンス: 八王子の現在の天気は晴れで、気温は15度です。また、現在の時刻は2026年2月24日の13時36分です。 current_weatherとcurrent_timeの2つの関数を一度のレスポンスで呼び出していることがわかります。 また、関数の実行結果をもとにして回答してくれています。 messagesを変更することで、1つの関数だけ実行させることも可能です。 以下にmessagesを変更した場合の実行結果を示します。 時刻を聞くmessages Python messages = [ { "role": "system", "content": "あなたは優秀なアシスタントAIです。", }, {"role": "user", "content": "八王子の時刻を教えてください。"}, ] 実行結果 レスポンス: ChatCompletionMessage(content=None, refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_5m0PwZa4t2AIty1trBlKTGAs', function=Function(arguments='{"location":"八王子"}', name='current_time'), type='function')]) 関数呼び出し: current_time 引数: {'location': '八王子'} 最終レスポンス: 現在の八王子の時刻は、2026年2月24日 14:22です。 天気を聞くmessages Python messages = [ { "role": "system", "content": "あなたは優秀なアシスタントAIです。", }, {"role": "user", "content": "八王子の天気を教えてください。"}, ] 実行結果 レスポンス: ChatCompletionMessage(content=None, refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_MZmeJ11LfOQufB1dFzyztQEu', function=Function(arguments='{"location":"八王子"}', name='current_weather'), type='function')]) 関数呼び出し: current_weather 引数: {'location': '八王子'} 最終レスポンス: 現在の八王子の天気は晴れで、気温は15度です。 傘が必要か聞くmessages Python messages = [ { "role": "system", "content": "あなたは優秀なアシスタントAIです。", }, {"role": "user", "content": "今から八王子に行きます。傘はいるかな"}, ] 実行結果 レスポンス: ChatCompletionMessage(content=None, refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=[ChatCompletionMessageFunctionToolCall(id='call_Hoa7qoq13vJa4nVqzCdegVJp', function=Function(arguments='{"location":"八王子"}', name='current_weather'), type='function')]) 関数呼び出し: current_weather 引数: {'location': '八王子'} 最終レスポンス: 八王子の天気は晴れで、気温は15度です。今のところ傘は必要なさそうですが、念のため天気予報を確認しておくと良いかもしれません。安全に行ってきてください! 直接天気や時刻を聞かなくとも関数を実行して答えてくれます。 まとめ 今回はAzure OpenAIでFunctionCallingを行う方法についてまとめました。 Azure OpenAIのFunctionCallingは並列関数呼び出しが可能で、前章の例のように複数の関数を一度のレスポンスで呼び出すことができます。 どのような関数を実行させるかはアイデア次第です。是非FunctionCallingを使用して生成AIとツールを組み合わせてみてください。 今後もAzure OpenAIについて学んだことを共有していきたいと思います。 読んでいただきありがとうございました。 関連記事 これまでのAzure OpenAI入門 Azure OpenAI入門:モデルのデプロイとpythonからAPIを実行 AzureOpenAI入門:JSON形式のデータを出力させる 参考文献 https://learn.microsoft.com/ja-jp/azure/ai-foundry/openai/how-to/function-calling?view=foundry-classic&tabs=python-secure ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AzureOpenAI入門:FunctionCallingで生成AI×ツールを実現する first appeared on SIOS Tech Lab .
アバター
日々の業務で、見積書・請求書・報告書・管理表など、 Excelが中心になっている現場 は少なくありません。一方で、ファイルが増え続けるほど「探す」「集計する」「整える」作業が重くなり、 時間とミスのリスク が膨らみがちです。 当社では、Excelのユーザー体験はそのままに、 自然言語で検索・集計・可視化まで行えるAIエージェントシステムの構築 を行っています。 現在、 無料で体験いただけるトライアル環境 を公開中です。まずは触ってみて、貴社の課題をお聞かせください。 M365アカウントを持っていない方、アドインを社内規定で追加できない方に対しても、当社側でテスト用のアカウントを払い出すため、どなたでもトライアルに参加いただけます。 無料トライアルを試す 相談・お問い合わせ メール : ka-sasaki@sios.com AIエージェント開発やDXに関するご相談も広く受け付けています。 「こんなことできる?」というアイデアベースのご相談も歓迎です。 こんな課題を解決します 課題 AIで解決 複数ファイルから必要なデータを探すのに 何時間もかかる 自然言語で 横断検索 、数秒で回答 毎月の集計作業で 半日かかる 「先月の売上を店舗別にグラフ化して」で 自動処理 担当者しかわからない 属人化 AIが構造を理解し、 誰でも検索可能 に 各システムからCSVを出力して Excelで手作業集計 システム連携で リアルタイム分析 Excel AI エージェントでできること 1. 過去のExcel帳票をそのままデータとして活用 結合セル、複数行ヘッダー、階層構造 を持つ複雑な帳票も、AIが構造を自動解析。 ファイルを取り込むだけで、検索・分析対象になります。 見積書・請求書 売上報告書(複数行ヘッダー) 料金表(結合セル多数) 管理台帳・マスタデータ   2. 過去の類似Excel帳票からドラフト作成 ユースケース : 過去に作成した帳票を学習し、新規作成時のドラフトを自動生成。 例: 「昨年のA社向け見積書をベースに、今年の単価で新しい見積書のドラフトを作って」 入力作業の大幅削減と、過去ナレッジの活用を同時に実現します。 3. 自然言語で検索・集計・グラフ作成 AIエージェントが あいまいな指示も理解 して処理を実行します。 質問例 AIの処理 「店舗Aの決済手段別売上をグラフに」 データ検索 → 集計 → グラフ生成 「先月より売上が下がった店舗は?」 期間比較 → 条件抽出 → 一覧表示 「完了していない作業は何件?」 ステータス値を理解 → カウント 4. 既存システムとの連携 Excelだけに閉じない、企業全体のデータ活用 が可能です。 SQLエージェント : 社内データベース(SQL Server、Oracle、PostgreSQL等)と連携 MCP対応 : 外部API・サービスとの柔軟な接続 ※ システム連携は、貴社環境に合わせた個別構築となります トライアル環境について 体験できること デモ環境には、以下のサンプルデータが用意されています: サンプル 内容 特徴 デモマート 小売業の売上データ 売上報告書(結合セル)、商品・店舗マスタ、トランザクション デモリゾート ホテル・リゾートの予約データ 宿泊料金表(複数行ヘッダー)、施設・客室マスタ、予約データ 試せる機能: 自然言語でのデータ検索 複数ファイルを横断した集計 グラフ・表の自動作成 書式設定の指示(「完了行を緑色に」等) 類似帳票からのドラフト作成 入力済みデータ例 トライアルの制約事項 デモ環境は機能検証を目的としており、以下の制約があります: お客様独自のデータ投入には対応していません(サンプルデータでの検証となります) 「自社データで試したい」 という場合は、PoC(概念実証)としてご相談ください。 データの取り扱いについて 項目 内容 AI学習利用 入力されたデータはAIの学習には使用しません アクセス制御 他のユーザーに入力内容が共有されることはありません。 データ削除 トライアル終了後、一定期間で削除いたします 商用環境の導入形態 クラウド基盤での提供 本システムは クラウド基盤 で提供します。ExcelアドインはMicrosoft 365上で動作し、バックエンドのAI処理はクラウドで実行されます。 構成 内容 フロントエンド Microsoft Excel アドイン(Office.js) バックエンド クラウド基盤(AI処理・検索エンジン) セキュリティ 閉域接続の構成も相談可能 貴社専用のAI環境を構築します 当社はこの技術を基盤とした「貴社専用のAIエージェントシステム構築」を行っています。 ご要望 対応内容 既存システムとの連携 基幹システム、CRM、ERPとの接続構築 社内DBとの接続 SQLエージェントで既存DBを分析対象に 独自フォーマット対応 業界特有・社内独自の帳票に最適化 セキュリティ要件 閉域接続など、貴社ポリシーに準拠した構成 まずはトライアルで技術の可能性を体感いただき、「自社のあの業務で使えるか?」をご検討ください。 その後、無償で簡易要件ヒアリングを実施し、貴社に最適なシステム構成をご提案します。 デモ動画 https://tech-lab.sios.jp/wp-content/uploads/2026/02/c7a3cb943abf893c211da41841707cc6.mp4 よくある質問 Q. どんなExcelファイルでも対応できますか? 結合セル、複数行ヘッダー、階層構造を持つ複雑な帳票にも対応しています。見積書、請求書、売上報告書、料金表など、様々なフォーマットで動作確認済みです。 Q. 既存のExcelファイルをそのまま使えますか? はい。ファイルをシステムにインポートするだけで、検索・分析対象になります。Excelのフォーマットを変更する必要はありません。 Q. 社内の基幹システムとも連携できますか? はい。SQLエージェント技術により、SQL Server、Oracle、PostgreSQLなど主要なデータベースとの連携が可能です。連携構築は貴社環境に合わせた個別対応となります。 Q. セキュリティ面が気になります 入力されたデータはAIの学習には使用しません。また、閉域接続の構成も相談可能です。貴社のセキュリティポリシーに合わせた構成をご提案します。 Q. 費用感を知りたいです 貴社の要件に応じた個別見積もりとなります。まずはトライアルで技術を体感いただき、要件ヒアリングの上でお見積もりいたします。 お問い合わせ 無料トライアルを試す 相談・お問い合わせ メール : ka-sasaki@sios.com AIエージェント開発やDXに関するご相談も広く受け付けています。 「こんな業務を自動化したい」 「既存システムとAIを連携させたい」 「まずは話を聞いてみたい」 本トライアルではM365アカウントを持っていない方、アドインを社内規定で追加できない方に対しても、当社側でテスト用のアカウントを払い出すため、どなたでもトライアルに参加いただけます。 どんなご相談でもお気軽にお問い合わせください。   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Excel AI エージェント 無料トライアルを開始しました! first appeared on SIOS Tech Lab .
アバター
前回の記事ではSCANOSS CLIを使ったローカルスキャンの手順 を紹介しました。ローカルで動作確認ができたら、次はCI/CDパイプラインへの統合です。 本記事では、 SCANOSS Code Scan Action を使ったGitHub Actionsでのコードスキャン自動化の手順を紹介します。 この記事でわかること : GitHub Actionsでの基本的なスキャン設定と実行方法 ポリシーチェック(Copyleft / Undeclared)の設定と挙動 API Keyあり/なしの挙動差(GitHub ActionsではAPI Key必須) scanoss.jsonによるスキャン結果の制御とポリシーチェックへの影響 サブディレクトリスキャンやスキャンチューニングの活用方法 前提条件 GitHubリポジトリにActions実行権限があること SCANOSS API Keyを取得済みであること( SCANOSSとの契約 が必要) リポジトリのSettings > Secrets に SCANOSS_KEY を登録済みであること 基本スキャン(Non-Policy) 最もシンプルな構成です。ポリシーチェックなしで、コードスキャンのみを実行します。 # .github/workflows/scanoss-scan.yml name: SCANOSS Security Scan on: workflow_dispatch: permissions: contents: write pull-requests: write checks: write actions: read jobs: scanoss-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: scanoss/gha-code-scan@v1.5.0 with: api.key: ${{ secrets.SCANOSS_KEY }} 実行すると、以下のArtifactが自動生成されます。 Artifact 内容 scanoss-raw.json スキャン結果(JSON) scanoss-cyclonedx.json CycloneDX SBOM scanoss-spdxlite.json SPDX-Lite SBOM scanoss-sbom.csv CSV形式レポート 今回の検証では、実行時間は約33秒でした(Docker imageのpull含む)。 ポリシーチェック付きスキャン(Full Policy) ライセンスポリシーの自動チェックを有効にする構成です。 - uses: scanoss/gha-code-scan@v1.5.0 with: policies: copyleft, undeclared, dt matchAnnotations: false api.key: ${{ secrets.SCANOSS_KEY }} ポリシー チェック内容 copyleft コピーレフトライセンス(GPL等)の混入検出 undeclared 未宣言のOSSコンポーネントの検出 dt Dependency Track サーバーとの連携チェック チェック結果はGitHubのChecks APIを通じてCheck Runとして表示されます。今回の検証では、Copyleft: success、Undeclared: success、Dependency Track: failure(サーバー未構築のため)という結果でした。 matchAnnotations はスニペットマッチ検出時にコミットコメントを自動作成する機能です。デフォルトは true で、不要な場合は false を指定してください。 Dependency Trackポリシーについて dt ( Dependency Track )ポリシーを使うには、Dependency Trackサーバーの構築と以下のパラメータ設定が必要です。 deptrack.url deptrack.apikey deptrack.projectid (または deptrack.projectname + deptrack.projectversion ) 設定がない場合はwarningが出ますが、ワークフロー全体はsuccessで完了します。Dependency Track連携が不要であれば、 policies: copyleft, undeclared のように dt を外しても問題ありません。 API Keyは必須 ローカルスキャンではAPI Keyなしでも動作しましたが、 GitHub ActionsではAPI Keyが必須です 。 API Keyなしで実行した場合、以下のエラーが即座に返されます。 ERROR: SCANOSS API rejected the scan request due to service limits being exceeded ERROR: Details: {"error":"Rate limit exceeded","retry_after":411781} retry_after の値は約411,788秒(約5日)です。つまり約5日に1回しかリクエストできない計算になります。GitHub Actionsの共有IPからの無料APIへのアクセスがレート制限に達しているため、API Keyなしでは実質的にスキャンできません。(お試しでは十分です!あとは週次スキャンには対応できますね…) 項目 API Keyあり API Keyなし 接続先 Premium API api.osskb.org/scan/direct ステータス success failure(HTTP 503) 実行可否 可能 不可 サブディレクトリスキャン scanPath パラメータで、スキャン対象をリポジトリの特定ディレクトリに限定できます。 - uses: scanoss/gha-code-scan@v1.5.0 with: scanPath: ./application api.key: ${{ secrets.SCANOSS_KEY }} 今回の検証では、ルートスキャンとサブディレクトリスキャンでスキャン結果が大きく変わりました。 項目 ルートスキャン サブディレクトリスキャン スニペットマッチ 0件(filtered) 5件 検出ライセンス 0 3 ルートスキャンではMarkdown等のドキュメントファイルが大量に含まれるため、コードファイルのスニペットマッチがfilteredされていました。 scanPath でコードディレクトリのみに絞ることで、検出精度が向上します。モノレポ構成では積極的に活用してください。 なお、 scanPath を指定すると scanoss.json の読み込みパスも変わります。詳しくは後述の「scanoss.jsonによるスキャン制御」セクションを参照してください。 scanoss.jsonによるスキャン制御 リポジトリに scanoss.json を配置すると、スキャン結果のフィルタリングやポリシーチェックの挙動を制御できます。GitHub Actionsではデフォルトで自動読み込みが有効( scanossSettings: true )になっているため、ファイルを配置するだけで設定が反映されます。 読み込みの仕組み scanoss.json はスキャン対象ディレクトリの直下から自動的に読み込まれます。 scanPath 読み込まれるscanoss.json . (デフォルト) リポジトリルートの scanoss.json ./application application/scanoss.json scanPath を指定している場合、ルートの scanoss.json は読み込まれません。 settingsFilepath パラメータで明示的にパスを指定するか、 scanPath で指定したディレクトリに scanoss.json を配置してください。 設定できる内容 scanoss.json では以下の3つの制御が可能です( 公式ドキュメント )。 { "bom": { "include": [ { "purl": "pkg:github/owner/repo", "comment": "宣言済みコンポーネント" } ], "remove": [ { "path": "src/app.py", "purl": "pkg:github/owner/repo", "comment": "誤検出" } ] }, "skip": { "patterns": ["*.pyc", "__pycache__/**", "docs/**"] }, "settings": { "file_snippet": { "min_snippet_hits": 3, "min_snippet_lines": 5, "ranking_enabled": true, "ranking_threshold": 5, "honour_file_exts": true } } } セクション 効果 ポリシーへの影響 bom.include コンポーネントを「宣言済み」としてAPIに送信 Undeclared違反を解消できる bom.remove 指定したpath + purlの組み合わせを結果から除外 除外されたコンポーネントはポリシーチェック対象外になる skip.patterns マッチしたファイルをスキャン対象から除外 スキャンされないためポリシーチェックにも影響しない settings.file_snippet スニペットマッチの感度を調整(v1.5.0) 感度を下げることで誤検出を減らせる Undeclaredポリシーとの連携 undeclared ポリシーは、スキャンで検出されたOSSコンポーネントが bom.include で宣言されていない場合に違反として報告します。 運用の流れは以下の通りです。 Full Policyスキャンを実行し、Undeclared違反が報告される 違反コンポーネントのPURLをスキャン結果から確認する 正しく使用しているものであれば、 bom.include にPURLを追加する 再スキャンでUndeclared違反が解消されることを確認する スキャンチューニング(v1.5.0) v1.5.0から、 scanoss.json の settings.file_snippet セクションでスニペットマッチの感度を調整できるようになりました。最小ヒット数や最小行数の閾値を設定することで、誤検出を減らすことができます。 チューニングパラメータはGitHub Actionの入力パラメータではなく、 scanoss.json で設定します。詳細は 公式ドキュメント(Scan Tuning Parameters) を参照してください。 推奨する運用フロー scanoss.json なしでスキャンを実行し、検出結果を確認する 誤検出があれば bom.remove で除外、正規利用のOSSは bom.include で宣言する 不要なファイル(テストデータ、ドキュメント等)は skip.patterns で除外する スニペットの誤検出が多い場合は settings.file_snippet で感度を調整する scanoss.json をリポジトリにコミットし、以降のCI/CDスキャンに反映させる 段階的な導入ステップ API Keyの準備 : SCANOSSとの契約後、GitHub Secretsに SCANOSS_KEY を登録 Non-Policyで開始 : 基本スキャンで動作確認( workflow_dispatch トリガー) 結果の確認 : Artifactから scanoss-raw.json をダウンロードして内容確認 Full Policyに拡張 : policies: copyleft, undeclared を追加 チューニング : 誤検出が多い場合は scanoss.json で感度を調整 PRトリガーに変更 : workflow_dispatch → pull_request に変更して自動化 まとめ SCANOSS GitHub Actionsを使ったCI/CDでのコードスキャン自動化の手順を紹介しました。 項目 ポイント API Key GitHub ActionsではAPI Key必須(無料APIはレート制限で使用不可) ポリシーチェック copyleft / undeclaredで自動チェック。Dependency Trackはサーバーが別途必要 scanPath サブディレクトリ指定で検出精度が向上。scanoss.jsonの読み込みパスに注意 チューニング v1.5.0でスニペットマッチの感度調整が可能に(scanoss.jsonで設定) バージョン v1.5.0推奨。v1.4.0から後方互換性あり 参考資料 関連リンク SCANOSS Code Scan Action(GitHub リポジトリ) SCANOSS Code Scan Action(GitHub Marketplace) SCANOSS 公式ドキュメント scanoss-py 公式ドキュメント SCANOSS Pricing Dependency Track ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SCANOSS GitHub Actionsでコードスキャンを自動化 first appeared on SIOS Tech Lab .
アバター