VSCodeのGenerate alt textを使って代替テキストを自動生成する

VSCodeエクステンションのGitHub Copilot Chat 0.27にて、画像の代替テキストを自動生成できる機能が追加された

この機能を使用するには、HTMLファイルまたはMarkdownファイル内の画像がある行までカーソルを移したうえで、キーボードショートカットの⌘.を使うか、行の上部に表示される電球アイコンを選択する。alt属性の値が空であれば「Generate alt text」、すでに存在すれば「Refine alt text」がコードアクションとして表示される。

アクションを実行するとインラインチャットが立ち上がり、「Generate alt text」の場合は次のプロンプトが実行される。

Create an alt text description that is helpful for screen readers and people who are blind or have visual impairment. Never start alt text with "Image of..." or "Picture of...". Please clearly identify the primary subject or subjects of the image. Describe what the subject is doing, if applicable. Please add a short description of the wider environment. If there is text in the image please transcribe and include it. Please describe the emotional tone of the image, if applicable. Do not use single or double quotes in the alt text.

「Refine alt text」であれば次のようになる。

Refine the existing alt text for clarity and usefulness for screen readers. Create an alt text description that is helpful for screen readers and people who are blind or have visual impairment. Never start alt text with "Image of..." or "Picture of...". Please clearly identify the primary subject or subjects of the image. Describe what the subject is doing, if applicable. Please add a short description of the wider environment. If there is text in the image please transcribe and include it. Please describe the emotional tone of the image, if applicable. Do not use single or double quotes in the alt text.

チャットからのレスポンスが返ってきたタイミングで、その結果が値として挿入される。

自動生成の使いどころと注意点

いくつかの記事や画像に対してこの機能を試してみたが、内容にかかわらずそこそこの精度の出力ができた。たとえば複雑な図表でも、プロンプトが優秀なのかかなり正確に内容を出力できる。少なくとも、ほとんどの人にとっては自力で記述するよりも良い結果になる可能性が高い。注意点もあるので順を追って説明する。

まず、この自動生成機能は、ブログなどの記事中で登場する画像のために使うのが適切だと思われる。代替テキストの書き方にはいくつかパターンがあるが、altディシジョンツリーに則れば、「意味のある画像」もしくは「複雑な画像」に当てはまる例に適用するのに向いている。

それ以外のパターンについては、この自動生成を用いると冗長になりがちであり、人が自ら記述したほうがうまくいく。たとえば単なる「文字画像」や、ボタンのなかで使用するアイコンなど(「機能を持つ画像」)については、制作者自身にとっては記述内容は自明なのでわざわざAIに頼る必要もないだろう。言い換えれば、自動生成ができるとしても、altディシジョンツリーくらいは最低限理解しておく必要がある。

この自動生成では、対象の画像に含まれる情報だけでなく、同ファイルの記述内容も考慮した結果になる。このおかげで、単なる画像単体の説明ではなく、ある程度前後の文脈も踏まえた出力ができているようだ。したがって、同一ファイルに本文もいっしょに記述された形式であることが望ましい。つまり、マークダウンファイルで記事データが管理されているブログなどに向いている。先に本文を書き切ってから、最後に代替テキストを生成すると精度が上がるだろう。

あとは、代替テキストがたまに英語で出力されてしまうことがある。プロンプトが英語であるせいでそれに引っ張られてしまうようだ。インラインチャットで続けて「as japanese」などと要求すれば日本語にしてくれる。

運が悪いと、マークアップの代替テキスト部分だけでなく、タグ全体を丸ごと出力結果で置き換えてしまうことがある。プロンプトが改善されればこのような現象は回避できるかもしれないが、とりあえず今の時点ではそのたび手動で直していくのが早い。

そして、一般的に良いモデルを使うほど良い結果になる。Claude 3.7 SonnetとClaude Sonnet 4で結果を比較してみたが、後者のほうが明らかに的確な内容が出力された。

Generate alt textの実装を調べる

この機能はコードアクションとして提供されているものだが、たとえばVSCode以外のエディタやその他のAIエージェントなど、別の経路からも実行できると便利だ。そこで、これがどのように実装されているかを理解するため、エクステンションのソースコードを調べてみる。

GitHub Copilotのリポジトリは公開されていないので、ローカルにある~/.vscode/extensions/を対象に「Generate alt text」の文字列を検索すると、~/.vscode/extensions/github.copilot-chat-0.27.0/dist/extension.jsのなかにその記述が見つかった。

extension.jsはminifyされているため、Prettierでフォーマットする。検索で見つかった該当箇所は次のようになっていた。

async provideAltTextQuickFix(e, t) {
  let n = e.lineAt(t.start.line).text,
    o = kbe(n),
    s = kbe(n, !0);
  if (!(!o && !s)) {
    if (o) {
      let a = this.isValidUrl(o),
        c = a ? o : s$.default.resolve(s$.default.dirname(e.uri.fsPath), o);
      return {
        title: Ao.l10n.t("Generate alt text"),
        kind: Ao.CodeActionKind.QuickFix,
        resolvedImagePath: c,
        type: "generate",
        isUrl: a,
        isAI: !0,
      };
    } else if (s) {
      let a = this.isValidUrl(s),
        c = a ? s : s$.default.resolve(s$.default.dirname(e.uri.fsPath), s);
      return {
        title: Ao.l10n.t("Refine alt text"),
        kind: Ao.CodeActionKind.QuickFix,
        resolvedImagePath: c,
        type: "refine",
        isUrl: a,
        isAI: !0,
      };
    }
  }
}

このprovideAltTextQuickFixが呼び出されている場所を調べると、次のソースコードが出てくる。

async provideCodeActions(e, t, n) {
  if (
    !this.configurationService.getConfig(ee.EnableCodeActions) ||
    (await this.ignoreService.isCopilotIgnored(e.uri))
  )
    return;
  let s = [],
    a = Ao.window.activeTextEditor;
  if (!a) return s;
  let c = await this.provideAltTextQuickFix(e, t);
  if (
    (c &&
      ((c.command = {
        title: c.title,
        command: "github.copilot.chat.generateAltText",
        arguments: [
          {
            type: c.type,
            resolvedImagePath: c.resolvedImagePath,
            isUrl: c.isUrl,
          },
        ],
      }),
      s.push(c)),
    this.reviewService.isCodeFeedbackEnabled() && !a.selection.isEmpty)
  ) {

github.copilot.chat.generateAltTextというコマンドに目をつけて調べると、次のような定義が見つかった。

p.add(
  Ot.commands.registerCommand("github.copilot.chat.generateAltText", N),
),
N = async (M) => {
  if (
    M &&
    typeof M == "object" &&
    "isUrl" in M &&
    "resolvedImagePath" in M &&
    typeof M.resolvedImagePath == "string" &&
    "type" in M
  ) {
    let Y =
        'Create an alt text description that is helpful for screen readers and people who are blind or have visual impairment. Never start alt text with "Image of..." or "Picture of...". Please clearly identify the primary subject or subjects of the image. Describe what the subject is doing, if applicable. Please add a short description of the wider environment. If there is text in the image please transcribe and include it. Please describe the emotional tone of the image, if applicable. Do not use single or double quotes in the alt text.',
      k =
        M.type === "generate"
          ? Y
          : `Refine the existing alt text for clarity and usefulness for screen readers. ${Y}`,
      D = M.isUrl
        ? me.parse(M.resolvedImagePath)
        : me.file(M.resolvedImagePath);
    return Ot.commands.executeCommand("vscode.editorChat.start", {
      message: k,
      attachments: [D],
      autoSend: !0,
      initialRange: Ot.window.activeTextEditor?.selection,
    });
  }
},

どうやらこの実装では、インラインチャットに対して、先ほどのプロンプトといっしょに画像もコンテキストとして渡しているようだ。

試しにこのプロンプトだけを別のところにコピペして実行してみたらあまり良い結果にならなかったのだが、それは画像のコンテキストもいっしょに渡していないことが原因だったと分かった。当然と言えば当然だが。

これらを踏まえて、良い代替テキストを出力するために重要なポイントをまとめよう。