🤖

LLMO:AIがあなたのサイトを選ぶための最適化戦略

に公開

TL;DR

  • LLMO は Large Language Model Optimization の略で、生成 AI が自社コンテンツを優先的に参照するための最適化技術
  • 検索行動の変化により「ゼロクリック時代」が到来し、従来の SEO だけでは不十分に
  • Web 開発者として必要な対応は「LLMs.txt ファイルの実装」「構造化データのマークアップ」「AI チャットボット連携」の 3 点
  • LLMs.txt は Markdown 形式のファイルで、AI に最適化されたサイトマップとして機能する
  • 実装効果の測定には GA4 と測定用のスクリプトを活用し、定期的な改善サイクルを回す

LLMO とは?

LLMO(Large Language Model Optimization)とは、ChatGPT や Gemini などの大規模言語モデル(LLM)が生成する回答に、自社の情報(商品、サービス、ブランドなど)が優先的に表示されるように最適化する技術です。AIO(AI Optimization)とも呼ばれ、従来の SEO(検索エンジン最適化)が検索エンジンの検索結果を最適化するのに対し、LLMO は生成 AI の回答結果を最適化します。

具体的には:

  • 生成 AI が自社の Web コンテンツを正確に理解できるようにする
  • 質問に対する回答の情報源として自社サイトが引用される確率を高める
  • AI が自社ブランドや製品を適切に言及するように促す

この対策により、ChatGPT や Perplexity などの生成 AI ツールが質問に回答する際に、「~によると」や「参照:~」として自社サイトが引用される可能性が高まります。

LLMO が必要となった背景

ゼロクリック時代の到来

検索行動に大きな変化が起きています。従来は Google などの検索エンジンで検索し、検索結果から Web サイトに訪問するというフローが主流でした。しかし現在は、以下の変化が急速に進行しています:

  1. 生成 AI 検索の普及: ChatGPT や Perplexity などの AI ツールが大衆化し、直接質問して回答を得るユーザーが増加
  2. ゼロクリック検索の増加: 「AI による回答」のような検索結果ページ上で完結し、Web サイトへのクリックが発生しない検索行動の増加
  3. 間接的な情報接触: ユーザーが生成 AI 経由で情報を入手するため、Web サイトへの直接訪問が減少

これらの変化により、従来の SEO 戦略だけでは十分な効果を得ることが難しくなっています。ユーザーが Web サイトに訪問せずに AI から情報を得るため、「AI があなたの Web サイトをどれだけ参照するか」が重要な指標となったのです。

生成 AI の情報収集プロセスと課題

生成 AI が Web から情報を収集する際のプロセスは以下の通りです:

  1. クローラーが Web を巡回して関連ページを発見
  2. 発見したページから内容を抽出・理解
  3. 理解した情報を基に回答を生成

このプロセスには膨大なリソースが必要なため、対策を取らないと以下の問題が発生します:

  • クローラーに見つけてもらえないページが存在する
  • 情報が正しく読み込まれない(誤解釈される)
  • 重要情報が回答に反映されない

これらの課題を解決するために、生成 AI が効率的に情報を理解できるよう最適化する「LLMO」の重要性が高まっています。

【Web 開発エンジニア向け】LLMO 対策

具体的に何をするか?

Web 開発エンジニアとして、LLMO に対応するためには主に以下の 3 つの施策を実装する必要があります:

1. LLMs.txt ファイルの実装

LLMs.txt とは、robots.txt に似た概念で、生成 AI が効率的にサイト情報を理解するためのガイドを提供するファイルです。このファイルを Web サイトのルートディレクトリに設置することで、AI が優先的に読み込むべきコンテンツを指定できます。

# サイト名
> サイトの簡潔な説明(50文字以内)

## 主要コンテンツ
- [ページタイトル1](URL1): 簡潔な説明
- [ページタイトル2](URL2): 簡潔な説明

## その他重要情報
- 会社概要
- よくある質問

2. 構造化データのマークアップ実装

Schema.org に基づいた構造化データを実装することで、生成 AI がコンテンツの意味や関係性を正確に理解できるようになります。特に重要なスキーマタイプは:

  • FAQPage: よくある質問と回答
  • HowTo: 手順説明
  • Product: 製品情報
  • Organization: 組織情報
  • LocalBusiness: 店舗情報

3. AI チャットボット連携

自社 Web サイトにチャットボットを導入し、自社の製品情報や FAQ を学習させることで、ユーザーの質問に対して的確な回答を提供できるようになります。これにより:

  • ユーザー体験の向上
  • 自社情報の正確な拡散
  • AI による参照率の向上

が期待できます。

LLMs.txt の作り方、生成 AI でのプロンプト例

LLMs.txt の基本構造

LLMs.txt ファイルは以下の要素で構成されます:

  1. H1 見出し: サイト名(# サイト名
  2. 引用文: サイトの簡潔な説明(> 説明文
  3. H2 セクション: コンテンツカテゴリ(## セクション名
  4. リスト: 重要ページへのリンクと説明(- [ページ名](URL): 説明

サンプル LLMs.txt

# XXXXWeb サービス

> 最新の Web 開発技術と AI ソリューションを提供する技術企業

## 主要製品・サービス

- [AI 開発プラットフォーム](https://XXXX.com/ai-platform): エンタープライズ向け AI 開発・運用環境
- [クラウドインフラ管理ツール](https://XXXX.com/cloud-tool): マルチクラウド対応の統合管理ソリューション
- [開発者向け API](https://XXXX.com/api): 200 種類以上のエンドポイントを提供する RESTful API

## 技術情報

- [開発者ドキュメント](https://XXXX.com/docs): API リファレンスと実装ガイド
- [技術ブログ](https://XXXX.com/blog): エンジニアによる技術解説と事例紹介
- [ナレッジベース](https://XXXX.com/kb): 一般的な質問と詳細な回答

## 会社情報

- [会社概要](https://XXXX.com/about): ミッション、ビジョン、沿革
- [チーム](https://XXXX.com/team): エグゼクティブとエンジニアリングリーダー
- [採用情報](https://XXXX.com/careers): エンジニアリングポジションとカルチャー

生成 AI による LLMs.txt 生成プロンプト

ChatGPT や Claude などの生成 AI を使って LLMs.txt を作成する際の効果的なプロンプト例:

# LLMs.txt生成プロンプト

以下の情報を基に、AI最適化されたLLMs.txtファイルを生成してください。

## 入力情報
- サイト名: [サイト名を入力]
- サイトURL: [メインドメインを入力]
- サイト概要: [50文字以内でサイトの説明を入力]
- 主要ページ(優先度順):
  1. [ページ名1]: [URL1] - [25文字以内の説明]
  2. [ページ名2]: [URL2] - [25文字以内の説明]
  3. [続く...]
- 会社・組織情報ページ: [URL]
- よくある質問ページ: [URL]

## 出力形式要件
1. Markdown形式で作成
2. H1見出しにサイト名
3. 引用文にサイト概要
4. H2「主要コンテンツ」セクションに重要ページをリスト
5. H2「会社情報」セクションに組織情報
6. コンパクトかつ情報量の多い形式を維持

適用方法、コード例

実装ステップ 1: LLMs.txt ファイルの設置

  1. LLMs.txt ファイルを Markdown 形式で作成
  2. Web サイトのルートディレクトリに配置
  3. LLMs.txt ファイルが HTTP 200 ステータスコードで正常に返されることを確認

以下は Next.js で LLMs.txt を動的に生成・配信する実装例です:

// pages/api/llms.txt.js
import { getAllPages, getSiteMetadata } from '../lib/api';

export default async function handler(req, res) {
  const siteMetadata = await getSiteMetadata();
  const pages = await getAllPages();

  // Markdown形式でLLMs.txtを構築
  let content = `# ${siteMetadata.title}\n\n`;
  content += `> ${siteMetadata.description}\n\n`;

  // 主要コンテンツセクション
  content += `## 主要コンテンツ\n`;
  pages.slice(0, 10).forEach(page => {
    content += `- [${page.title}](${siteMetadata.siteUrl}${page.path}): ${page.summary}\n`;
  });

  // その他情報
  content += `\n## サイト情報\n`;
  content += `- [会社概要](${siteMetadata.siteUrl}/about): 会社の詳細情報とミッション\n`;
  content += `- [FAQ](${siteMetadata.siteUrl}/faq): よくある質問と回答\n`;

  // Markdownとして返す
  res.setHeader('Content-Type', 'text/markdown');
  res.status(200).send(content);
}

実装ステップ 2: 構造化データマークアップの追加

Schema.org に基づいた構造化データを実装します。以下は React での FAQ マークアップ実装例です:

import Head from 'next/head';

export default function FAQPage({ faqs }) {
  const faqSchema = {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    mainEntity: faqs.map(faq => ({
      '@type': 'Question',
      name: faq.question,
      acceptedAnswer: {
        '@type': 'Answer',
        text: faq.answer,
      },
    })),
  };

  return (
    <>
      <Head>
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
        />
      </Head>
      <div className="faq-container">
        <h1>よくある質問</h1>
        {faqs.map((faq, index) => (
          <div key={index} className="faq-item">
            <h2>{faq.question}</h2>
            <div dangerouslySetInnerHTML={{ __html: faq.answer }} />
          </div>
        ))}
      </div>
    </>
  );
}

実装ステップ 3: AI チャットボット連携

OpenAI API などを活用して、自社情報を学習したカスタムチャットボットを実装する例:

// pages/api/chat.js
import { OpenAIApi, Configuration } from 'openai';
import { getCompanyData } from '../lib/company-data';

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { message } = req.body;

  // 自社データ取得
  const companyData = await getCompanyData();

  try {
    const completion = await openai.createChatCompletion({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: `あなたは${companyData.companyName}の公式AIアシスタントです。
                   以下の情報を基に質問に答えてください:
                   ${companyData.products}
                   ${companyData.faqs}
                   ${companyData.policies}`,
        },
        { role: 'user', content: message },
      ],
    });

    res.status(200).json({
      response: completion.data.choices[0].message.content,
    });
  } catch (error) {
    console.error('OpenAI API error:', error);
    res.status(500).json({ error: 'AI処理中にエラーが発生しました' });
  }
}

効果測定方法、コード例

LLMO 施策の効果を測定するためには、以下の方法が有効です

GA4 の測定 ID を取得する方法

  1. Google Analytics にログイン
  • Google Analyticsにアクセスし、Google アカウントでログインします。
  1. 該当するプロパティを選択
  • 左側のナビゲーション内の「管理」タブをクリックします(⚙️ アイコン)。
  • 中央の列(プロパティ列)で、測定 ID を取得したいプロパティを選択します。
    • プロパティがまだない場合は、「プロパティを作成」をクリックして新規作成します。
  1. 測定 ID の確認方法
  • プロパティ列の「データストリーム」をクリックします。
  • 既存のウェブデータストリームをクリックします(または「ストリームを追加」→「ウェブ」を選択して新規作成)。
  • データストリームの詳細画面で「測定 ID」が表示されます。
  • 測定 ID は通常「G-XXXXXXXXXX」の形式です。
  1. 新規プロパティ作成時
  • 新しいプロパティを作成した場合、セットアップ手順の中で「データストリームの設定」ステップで測定 ID が表示されます。
  1. トラッキングコードの確認方法(代替方法)
  • データストリーム詳細画面で「タグ設定の手順」→「自分で設定」を選択すると、完全なトラッキングコードが表示されます。
  • このコード内にgtag('config', 'G-XXXXXXXXXX')の形式で測定 ID が含まれています。

1. GA4 によるトラッキング

GA4 を使用して、生成 AI 経由のトラフィックを追跡します:

// _app.tsx (Next.js)
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import Script from 'next/script';
import { AppProps } from 'next/app';

// グローバル window に gtag を追加
declare global {
  interface Window {
    gtag: (command: string, targetId: string, config?: Record<string, any>) => void;
    dataLayer: any[];
  }
}

export default function MyApp({ Component, pageProps }: AppProps): JSX.Element {
  const router = useRouter();

  useEffect(() => {
    // ページビュー計測
    const handleRouteChange = (url: string): void => {
      window.gtag('config', '[測定ID]', {
        page_path: url,
      });
    };

    // AIリファラーのカスタムイベント計測
    const trackAIReferrer = (): void => {
      const referrer = document.referrer;
      const aiSources: string[] = ['chat.openai.com', 'perplexity.ai', 'claude.ai'];

      if (aiSources.some(source => referrer.includes(source))) {
        window.gtag('event', 'ai_referral', {
          ai_source: referrer,
        });
      }
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    trackAIReferrer();

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, [router.events]);

  return (
    <>
      {/* Google Analytics */}
      <Script
        strategy="afterInteractive"
        src={`https://www.googletagmanager.com/gtag/js?id=[測定ID]`}
      />
      <Script
        id="gtag-init"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '[測定ID]');
          `,
        }}
      />
      <Component {...pageProps} />
    </>
  );
}

2. 生成 AI 回答監視

目的は、生成 AI ツールのウェブサイト(ChatGPT、Claude、Perplexity など)から自分のサイトに訪れたユーザーを検出することです。

計測のようなユースケースを想定しています。

  1. ユーザーが ChatGPT などの AI サービスを使用中
  2. AI の回答内に自分のサイトへのリンクが含まれている
  3. ユーザーがそのリンクをクリックして自分のサイトに訪問
  4. このとき「リファラー(参照元)」として、クリック元のドメイン(例:chat.openai.com)が記録される
  5. このリファラー情報をチェックして「AI からの流入」を検出・計測する

今回は Go で geminiAPI を利用したサンプルです。

package main

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"log" // Added for error logging
	"os"
	"strings"
	"time"

	"github.com/google/generative-ai-go/genai" // Added Gemini SDK
	"github.com/joho/godotenv"                 // Added for .env file handling
	"google.golang.org/api/option"             // Added for API key option
)

// 結果保存用の構造体
type QueryResult struct {
	Query             string
	Timestamp         string
	CompanyMentioned  bool
	ProductsMentioned string
	URLMentioned      bool
	FullResponse      string
}

func main() {

	// .envファイルから環境変数を読み込み
	if err := godotenv.Load(); err != nil {
		log.Fatal("Error loading .env file")
	}
	geminiAPIKey := os.Getenv("GEMINI_API_KEY")
	geminiModelName := os.Getenv("GEMINI_API_MODEL")
	domainName := os.Getenv("DOMAIN_NAME")
	companyName := os.Getenv("COMPANY_NAME")
	productNames := strings.Split(os.Getenv("PRODUCT_NAMES"), ",")
	targetQueries := strings.Split(os.Getenv("TARGET_QUERIES"), "|")



	if geminiAPIKey == "" || geminiModelName == "" || domainName == "" {
					log.Fatal("環境変数 GEMINI_API_KEY, GEMINI_API_MODEL, DOMAIN_NAME のいずれかが設定されていません。")


	}

	// Gemini クライアントの初期化
	ctx := context.Background()
	client, err := genai.NewClient(ctx, option.WithAPIKey(geminiAPIKey))
	if err != nil {
		log.Fatalf("Gemini クライアントの作成に失敗しました: %v", err)
	}
	defer client.Close()

	// 固定の設定値

	if companyName == "" || len(productNames) == 0 || len(targetQueries) == 0 {
		log.Fatal("環境変数 COMPANY_NAME, PRODUCT_NAMES, TARGET_QUERIES のいずれかが設定されていません。")
	}

	// 結果保存用スライス
	var results []QueryResult

	// 各クエリをテスト
	for _, query := range targetQueries {
		// APIリクエスト (Gemini API を使用)
		answerText, err := callGeminiAPI(ctx, client, geminiModelName, query) // 関数呼び出しを修正
		if err != nil {
			log.Printf("クエリ '%s' の処理中にエラーが発生しました: %v\n", query, err) // エラーログに変更
			continue
		}

		// 会社名/製品名の言及をチェック
		companyMentioned := strings.Contains(strings.ToLower(answerText), strings.ToLower(companyName))
		var mentionedProducts []string
		for _, product := range productNames {
			if strings.Contains(strings.ToLower(answerText), strings.ToLower(product)) {
				mentionedProducts = append(mentionedProducts, product)
			}
		}

		// 製品名の結合
		productsMentioned := strings.Join(mentionedProducts, ", ")
		if productsMentioned == "" {
			productsMentioned = "なし"
		}

		// URLの言及をチェック
		urlMentioned := strings.Contains(strings.ToLower(answerText), strings.ToLower(domainName))

		// 結果を記録
		result := QueryResult{
			Query:             query,
			Timestamp:         time.Now().Format(time.RFC3339),
			CompanyMentioned:  companyMentioned,
			ProductsMentioned: productsMentioned,
			URLMentioned:      urlMentioned,
			FullResponse:      answerText,
		}
		results = append(results, result)

		// レート制限対策
		time.Sleep(1 * time.Second)
	}

	// 結果をCSVに保存
	saveResultsToCSV(results)

	// 集計結果を表示
	printSummary(results)
}

// Gemini APIを呼び出す関数
func callGeminiAPI(ctx context.Context, client *genai.Client, modelName, query string) (string, error) {
	// タイムアウト設定 (必要に応じて調整)
	ctx, cancel := context.WithTimeout(ctx, 60*time.Second) // タイムアウトを60秒に延長
	defer cancel()

	model := client.GenerativeModel(modelName)
	resp, err := model.GenerateContent(ctx, genai.Text(query))
	if err != nil {
		return "", fmt.Errorf("gemini API 呼び出しエラー: %w", err)
	}

	// レスポンスからテキストを抽出
	// Candidates が空、または最初の Candidate の Content が nil、または Parts が空の場合のエラーハンドリング
	if len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil || len(resp.Candidates[0].Content.Parts) == 0 {
		// レスポンスの内容をログに出力して詳細を確認できるようにする
		respJSON, _ := json.MarshalIndent(resp, "", "  ")
		log.Printf("Gemini API から予期しないレスポンスを受け取りました:\n%s", string(respJSON))
		return "", fmt.Errorf("gemini API から有効な回答が得られませんでした")
	}

	// Parts の最初の要素が Text であることを期待
	// (より堅牢にするには、Part の型をチェックすることも検討)
	if textPart, ok := resp.Candidates[0].Content.Parts[0].(genai.Text); ok {
		return string(textPart), nil
	}

	return "", fmt.Errorf("gemini API レスポンスの最初の Part がテキストではありませんでした")
}

// 結果をCSVに保存する関数
func saveResultsToCSV(results []QueryResult) error {
	filename := fmt.Sprintf("llmo_monitoring_%s.csv", time.Now().Format("20060102"))
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// ヘッダー行を書き込み
	header := []string{"query", "timestamp", "company_mentioned", "products_mentioned", "url_mentioned", "full_response"}
	if err := writer.Write(header); err != nil {
		return err
	}

	// データ行を書き込み
	for _, result := range results {
		companyMentioned := "false"
		if result.CompanyMentioned {
			companyMentioned = "true"
		}

		urlMentioned := "false"
		if result.URLMentioned {
			urlMentioned = "true"
		}

		row := []string{
			result.Query,
			result.Timestamp,
			companyMentioned,
			result.ProductsMentioned,
			urlMentioned,
			result.FullResponse,
		}

		if err := writer.Write(row); err != nil {
			return err
		}
	}

	fmt.Printf("Results saved to %s\n", filename)
	return nil
}

// 集計結果を表示する関数
func printSummary(results []QueryResult) {
	if len(results) == 0 {
		fmt.Println("No results to summarize")
		return
	}

	// 各指標のカウント
	companyMentionCount := 0
	productMentionCount := 0
	urlMentionCount := 0

	for _, result := range results {
		if result.CompanyMentioned {
			companyMentionCount++
		}
		if result.ProductsMentioned != "なし" {
			productMentionCount++
		}
		if result.URLMentioned {
			urlMentionCount++
		}
	}

	// 割合の計算
	totalQueries := len(results)
	companyMentionRate := float64(companyMentionCount) / float64(totalQueries) * 100
	productMentionRate := float64(productMentionCount) / float64(totalQueries) * 100
	urlMentionRate := float64(urlMentionCount) / float64(totalQueries) * 100

	// 結果表示
	fmt.Printf("会社名言及率: %.1f%%\n", companyMentionRate)
	fmt.Printf("製品言及率: %.1f%%\n", productMentionRate)
	fmt.Printf("URL言及率: %.1f%%\n", urlMentionRate)
}

測定対象などはご都合に合わせて変更してください。

今回の例での各数値についてはこのような指標になります。

  • 会社名言及率: AI の回答に自社名が含まれる割合。概ね 30−40%以上で認知されていると判断。
  • 製品言及率: AI の回答に自社製品が含まれる割合。概ね 20−30%以上で認知されていると判断。
  • URL 言及率: AI の回答に自社 Web サイトの URL が含まれる割合。概ね 10−20%以上で認知されていると判断。

測定対象次第で競合のチェックも可能です。

実装についてはこちらのリポジトリを参考にしてください。

https://github.com/tKwbr999/llmo-analysis

3. 効果測定と改善サイクル

LLMO 施策の効果測定と改善のサイクルを回すことが重要です:

  1. ベースライン測定: 施策実施前の状態を記録
  2. 定期測定: 週次または月次で同一クエリの測定を継続
  3. 改善施策: 測定結果に基づいて LLMs.txt やスキーママークアップを改善
  4. 再測定: 施策後の効果を確認
  5. 継続的改善: サイクルを継続して最適化を進める

今後の LLMO との付き合い方

SEO と LLMO の統合アプローチ

LLMO は SEO と対立するものではなく、補完するものです。最適なデジタル戦略には両方を統合することが重要です:

  1. 基盤としての SEO: 従来の検索エンジン最適化は引き続き重要
  2. 拡張としての LLMO: 生成 AI 向けの最適化で新たな接点を創出
  3. 統合的なコンテンツ戦略: 両方のチャネルを意識したコンテンツ設計

AI トレンドへの継続的対応

生成 AI 技術は急速に進化しています。以下の点に注意して継続的に対応していくことが必要です:

  1. 標準化の動向: W3C などによる標準化の進展を注視
  2. AI プラットフォームの変化: OpenAI、Google、Anthropic などの方針変更に対応
  3. ユーザー行動の変化: 検索から AI チャットへの移行度合いを把握

未来の AI 連携シナリオ

将来的には以下のような発展が予想されます:

  1. AI エージェントとの直接連携: 自社の API を AI エージェントに直接接続
  2. パーソナライズドコンテンツ: ユーザー特性に応じた AI 生成コンテンツの提供
  3. マルチモーダル LLMO: 画像・音声・動画を含めた総合的な AI 最適化

まとめ

LLMO は「ゼロクリック時代」に対応するための必須戦略となりつつあります。Web 開発エンジニアとして、LLMs.txt ファイルの実装、構造化データのマークアップ、AI チャットボット連携などの施策を実施することで、生成 AI 時代の新たな接点を創出できます。

重要なのは、一度の実装で終わりではなく、継続的な測定と改善のサイクルを回し続けることです。SEO と LLMO を統合的に捉え、ユーザーがどのチャネルで情報を得るにしても、自社の情報が正確に届くよう最適化していきましょう。

生成 AI 技術は日々進化していますが、基本的なアプローチは「AI がコンテンツを理解しやすくする」という点に尽きます。

この原則を押さえつつ、新たな技術や標準に柔軟に対応していくことで、生成 AI 時代の Web プレゼンスを確立していきましょう。

参考文献・リソース

GitHubで編集を提案

Discussion