N&S Logo

レリバンスエンジニアリング実装編:Mike King理論に基づく次世代AI検索最適化システムの構築

更新: 8/1
読了: 約47
字数: 18,496文字
レリバンスエンジニアリング実装編:Mike King理論に基づく次世代AI検索最適化システムの構築

はじめに

AI検索エンジンの普及により、従来のSEO手法では太刀打ちできない時代が到来しました。ChatGPT、Perplexity、Claude、Geminiなどの生成AIが検索の主役となる中、レリバンスエンジニアリング(Relevance Engineering) が注目を集めています。

本記事では、Mike King理論に基づく世界最高峰のレリバンスエンジニアリング実装方法を詳解します。単なる理論ではなく、実際に運用可能な具体的なコード実装まで踏み込んだ実践的な内容となっています。


Mike King理論とレリバンスエンジニアリングの基礎

レリバンスエンジニアリングとは

レリバンスエンジニアリング(RE)は、iPullRankのMike King氏が提唱したAI検索時代の最適化手法です。従来のキーワード中心のSEOから、セマンティックな関連性とエンティティ関係 に基づく最適化へのパラダイムシフトを指します。

核心概念:2つの並行システム

レリバンスエンジニアリングの実装で最も重要なのは、以下の2つのシステムを並行して構築することです:

  1. 面(表層)システム: 構造化データ + フラグメントID
  2. 裏(深層)システム: エンティティマップのベクトル化 + フラグメントID

この双方にフラグメントIDを埋め込むことで、LLMの認識能力を飛躍的に向上させます。


システム設計の全体像

アーキテクチャ概要

┌─────────────────────────────────────────────────────────────┐
│                    AI検索エンジン層                         │
│         ChatGPT | Perplexity | Claude | Gemini              │
└─────────────────────┬───────────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────────┐
│                フラグメントID統合層                        │
│              AI_OPTIMIZED_FRAGMENT_IDS                      │
└─────────────┬───────────────────────────────┬───────────────┘
              │                               │
┌─────────────▼───────────────┐ ┌─────────────▼───────────────┐
│      表層システム              │ │      深層システム              │
│   構造化データ + JSON-LD      │ │  エンティティマップ + Vector  │
│     Fragment ID付与            │ │     Fragment ID付与            │
└─────────────────────────────┘ └─────────────────────────────┘

技術スタック

  • 構造化データ: Schema.org 16.0+ 準拠
  • ベクトル化: OpenAI Embeddings (text-embedding-3-large)
  • ベクトルDB: pgvector (PostgreSQL拡張)
  • AI検索対応: 5大エンジン完全対応
  • 言語: TypeScript
  • フレームワーク: Next.js 14+

1. エンティティ関係システムの実装

エンティティ定義の基盤構造

// Mike King理論準拠: 統一エンティティ関係性システム
export interface EntityRelationship {
  '@id': string;
  '@type': string;
  name: string;
  knowsAbout: string[];
  relatedTo: string[];
  sameAs?: string[];
  mentions?: string[];
  about?: string[];
}

export interface ServiceEntity extends EntityRelationship {
  serviceType: string;
  provider: {
    '@id': string;
  };
  hasOfferCatalog?: {
    '@type': string;
    itemListElement: Array<{
      '@type': string;
      item: {
        '@id': string;
      };
    }>;
  };
}

中央エンティティの実装

export const ORGANIZATION_ENTITY: EntityRelationship = {
  '@id': 'https://example.com/#organization',
  '@type': 'Organization',
  name: '株式会社サンプル',
  knowsAbout: [
    // Mike King理論: レリバンスエンジニアリング
    'レリバンスエンジニアリング',
    'Relevance Engineering',
    'AI検索最適化',
    'セマンティック検索',
    
    // AI・システム開発領域
    'AIエージェント開発',
    'RAG(Retrieval-Augmented Generation)',
    'ベクトル化技術',
    'LLM(大規模言語モデル)',
    
    // SEO・マーケティング領域
    'AIO対策',
    'AI Overviews最適化',
    'ChatGPT SEO',
    'Perplexity最適化',
    'JSON-LD実装',
    'Schema.org',
    '構造化データ'
  ],
  relatedTo: [
    'https://example.com/service-a#service',
    'https://example.com/service-b#service',
    'https://example.com/service-c#service'
  ],
  sameAs: [
    'https://twitter.com/example',
    'https://www.linkedin.com/company/example'
  ]
};

サービスエンティティの関係性定義

export const SERVICE_ENTITIES: ServiceEntity[] = [
  {
    '@id': 'https://example.com/ai-service#service',
    '@type': 'Service',
    name: 'AIシステム開発サービス',
    serviceType: 'AISystemDevelopment',
    provider: { '@id': 'https://example.com/#organization' },
    knowsAbout: [
      'Next.js開発',
      'React開発',
      'TypeScript',
      'OpenAI API',
      'Claude API',
      'ベクトル検索',
      'RAGシステム'
    ],
    relatedTo: [
      'https://example.com/seo-service#service',
      'https://example.com/vector-service#service'
    ],
    mentions: [
      'AI自動化',
      'インテリジェント処理',
      'データ分析'
    ]
  }
];

エンティティ関係性マップ

export const ENTITY_RELATIONSHIP_MAP = {
  // システム開発 ↔ AIサービス
  systemToAI: {
    relation: 'enables',
    description: 'システム開発がAIサービスの実装基盤を提供'
  },
  
  // AIサービス ↔ SEO最適化
  aiToSEO: {
    relation: 'optimizes',
    description: 'AIサービスがSEO最適化の検索可視性を向上'
  },
  
  // ベクトル検索 ↔ AIサービス
  vectorToAI: {
    relation: 'powers',
    description: 'ベクトル検索がAIサービスの知識基盤を提供'
  }
};

2. AI検索最適化フラグメントIDシステム

フラグメントID定義構造

export interface AIOptimizedFragmentId {
  /** Fragment ID */
  id: string;
  
  /** セクション名 */
  sectionName: string;
  
  /** AI検索クエリ対応 */
  targetQueries: string[];
  
  /** 含まれるエンティティ */
  entities: DetailedMentions[];
  
  /** セマンティック重要度 */
  semanticWeight: number;
  
  /** 階層レベル (h1=1, h2=2, etc.) */
  hierarchyLevel: number;
  
  /** 関連Fragment IDs */
  relatedFragments: string[];
}

export interface DetailedMentions {
  entity: string;
  entityType: 'Organization' | 'Service' | 'Technology' | 'Methodology';
  context: 'implements' | 'provides' | 'uses' | 'related';
  importance: number; // 1-10
  relatedKnowledge: string[];
  searchIntents: string[];
  coOccurringTerms: string[];
}

AI最適化フラグメントIDの実装

export const AI_OPTIMIZED_FRAGMENT_IDS: AIOptimizedFragmentId[] = [
  {
    id: 'ai-search-optimization-overview',
    sectionName: 'AI検索エンジン最適化の概要',
    targetQueries: [
      'AI検索 最適化',
      'ChatGPT SEO',
      'Perplexity 対策',
      'AI Overviews 最適化'
    ],
    entities: [
      {
        entity: 'レリバンスエンジニアリング',
        entityType: 'Methodology',
        context: 'implements',
        importance: 10,
        relatedKnowledge: ['Mike King理論', 'AI検索最適化'],
        searchIntents: ['レリバンスエンジニアリング とは', 'AI SEO 手法'],
        coOccurringTerms: ['Mike King', 'iPullRank', 'Semantic SEO']
      }
    ],
    semanticWeight: 0.95,
    hierarchyLevel: 2,
    relatedFragments: ['mike-king-theory', 'relevance-engineering-expertise']
  },
  {
    id: 'rag-system-architecture',
    sectionName: 'RAGシステムアーキテクチャ',
    targetQueries: [
      'RAG システム 構築',
      'ベクトル検索 実装',
      '文書検索AI 開発'
    ],
    entities: [
      {
        entity: 'Triple RAGアーキテクチャ',
        entityType: 'Technology',
        context: 'implements',
        importance: 9,
        relatedKnowledge: ['RAG', 'ベクトル検索', 'LLM統合'],
        searchIntents: ['RAG アーキテクチャ', 'マルチRAG システム'],
        coOccurringTerms: ['Vector Database', 'Embeddings', 'LLM']
      }
    ],
    semanticWeight: 0.9,
    hierarchyLevel: 2,
    relatedFragments: ['rag-technology-expertise', 'vector-db-implementation']
  }
];

3. 統合システムクラスの実装

export class CompleteAIEnhancedUnifiedIntegrationSystem {  
  /**
   * Fragment ID連携強化分析
   */
  private analyzeFragmentIdEnhancement(context: PageContext): any {
    const contextualFragmentIds = this.generateContextualFragmentIds(context);
    
    // 関連Fragment ID取得
    const relevantFragments = AI_OPTIMIZED_FRAGMENT_IDS.filter(fragment =>
      contextualFragmentIds.includes(fragment.id)
    );
    
    // セマンティック重み付け
    const semanticWeighting: Record<string, number> = {};
    relevantFragments.forEach(fragment => {
      semanticWeighting[fragment.id] = fragment.semanticWeight;
    });
    
    // クエリターゲティング
    const queryTargeting: Record<string, string[]> = {};
    relevantFragments.forEach(fragment => {
      queryTargeting[fragment.id] = fragment.targetQueries;
    });
    
    // 階層マッピング
    const hierarchyMapping: Record<number, string[]> = {};
    relevantFragments.forEach(fragment => {
      if (!hierarchyMapping[fragment.hierarchyLevel]) {
        hierarchyMapping[fragment.hierarchyLevel] = [];
      }
      hierarchyMapping[fragment.hierarchyLevel].push(fragment.id);
    });
    
    return {
      totalFragments: relevantFragments.length,
      semanticWeighting,
      queryTargeting,
      hierarchyMapping
    };
  }

  /**
   * コンテキスト適応Fragment ID生成
   */
  private generateContextualFragmentIds(context: PageContext): string[] {
    const fragmentIds: string[] = [];
    
    // カテゴリベースのFragment ID
    if (context.category === 'ai-seo' || context.keywords.includes('SEO')) {
      fragmentIds.push('ai-search-optimization-overview', 'mike-king-theory');
    }
    
    if (context.category === 'vector-rag' || context.keywords.includes('RAG')) {
      fragmentIds.push('rag-system-architecture', 'rag-technology-expertise');
    }
    
    // 技術キーワードベース
    if (context.keywords.some(k => ['Next.js', 'TypeScript', 'React'].includes(k))) {
      fragmentIds.push('web-development-expertise', 'typescript-expertise');
    }
    
    if (context.keywords.some(k => ['AI', '機械学習', 'ChatGPT'].includes(k))) {
      fragmentIds.push('openai-integration', 'claude-integration');
    }
    
    return Array.from(new Set(fragmentIds)); // 重複除去
  }
}

4. ベクトル化システムの実装

コンテンツ抽出システム

export class ContentExtractor {
  /**
   * レリバンスエンジニアリング関連ファイルを抽出
   */
  private async extractStructuredDataFiles(): Promise<ExtractedContent[]> {
    const contents: ExtractedContent[] = [];
    const structuredDataPath = join(this.basePath, 'lib/structured-data');
    
    try {
      const files = readdirSync(structuredDataPath);
      
      for (const file of files) {
        if (file.endsWith('.ts')) {
          const filePath = join(structuredDataPath, file);
          const fileContent = readFileSync(filePath, 'utf-8');
          
          // TypeScriptファイルからコメントと関数定義を抽出
          const extractedData = this.extractTechnicalContent(fileContent, file);
          
          contents.push({
            url: `/technical/structured-data/${file.replace('.ts', '')}`,
            title: extractedData.title,
            description: extractedData.description,
            content: extractedData.content,
            metadata: {
              type: 'structured-data',
              category: 'relevance-engineering',
              lastModified: this.getFileLastModified(filePath),
              wordCount: extractedData.content.split(/\s+/).length,
              headings: extractedData.headings,
              technicalConcepts: extractedData.technicalConcepts
            }
          });
        }
      }
    } catch (error) {
      console.error('Error extracting structured data files:', error);
    }
    
    return contents;
  }
}

OpenAI Embeddings処理

export class OpenAIEmbeddings {
  private openai: OpenAI;

  constructor() {
    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
  }

  /**
   * 単一テキストのベクトル化
   */
  async embedSingle(text: string): Promise<number[]> {
    try {
      const response = await this.openai.embeddings.create({
        model: 'text-embedding-3-large',
        input: text,
        dimensions: 1536
      });

      return response.data[0].embedding;
    } catch (error) {
      console.error('OpenAI Embeddings エラー:', error);
      throw error;
    }
  }

  /**
   * ExtractedContentをVectorDataに変換
   */
  async processExtractedContent(contents: ExtractedContent[]): Promise<VectorData[]> {
    const vectorData: VectorData[] = [];
    
    for (const content of contents) {
      try {
        // コンテンツを適切なサイズに分割
        const chunks = this.splitContent(content.content, content.title);
        
        // 各チャンクをベクトル化
        const embeddings = await this.embedBatch(chunks);
        
        // VectorDataオブジェクトを作成
        for (let i = 0; i < chunks.length; i++) {
          const id = `${content.url}-chunk-${i}`;
          vectorData.push({
            id,
            content: chunks[i],
            metadata: {
              url: content.url,
              title: content.title,
              type: content.metadata.type,
              section: i > 0 ? `section-${i}` : undefined,
              wordCount: chunks[i].split(/\s+/).length,
              createdAt: new Date().toISOString(),
            },
            embedding: embeddings[i],
          });
        }
        
        console.log(`✅ ベクトル化完了: ${content.title} (${chunks.length}チャンク)`);
      } catch (error) {
        console.error(`❌ ベクトル化エラー: ${content.title}`, error);
      }
    }

    return vectorData;
  }
}

ベクトルストア実装

export class SupabaseVectorStore {
  private supabase;

  constructor() {
    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

    if (!supabaseUrl || !supabaseKey) {
      throw new Error('Supabase環境変数が設定されていません。');
    }

    this.supabase = createClient(supabaseUrl, supabaseKey);
  }

  /**
   * 単一のベクトルデータを保存
   */
  async saveVector(vectorData: VectorData): Promise<{ success: boolean; id?: number; error?: string }> {
    try {
      // VectorDataをSupabaseVectorDataに変換
      const supabaseData: Omit<SupabaseVectorData, 'id'> = {
        page_slug: this.extractPageSlug(vectorData.metadata.url),
        content_type: vectorData.metadata.type,
        section_title: vectorData.metadata.title,
        content_chunk: vectorData.content,
        embedding: vectorData.embedding,
        metadata: {
          original_url: vectorData.metadata.url,
          word_count: vectorData.metadata.wordCount,
          section: vectorData.metadata.section,
          created_at: vectorData.metadata.createdAt
        },
        fragment_id: this.generateFragmentId(vectorData.id),
        service_id: this.extractServiceId(vectorData.metadata.url),
        relevance_score: 1.0
      };

      const { data, error } = await this.supabase
        .from('company_vectors')
        .insert(supabaseData)
        .select('id')
        .single();

      if (error) {
        console.error('ベクトル保存エラー:', error);
        return { success: false, error: error.message };
      }

      console.log(`✅ ベクトル保存成功: ID ${data.id}`);
      return { success: true, id: data.id };

    } catch (error) {
      console.error('ベクトル保存例外:', error);
      return { 
        success: false, 
        error: error instanceof Error ? error.message : 'Unknown error' 
      };
    }
  }

  /**
   * Fragment ID生成
   */
  private generateFragmentId(vectorId: string): string {
    // vectorId: "https://example.com/ai-service-chunk-0"
    const parts = vectorId.split('/');
    const lastPart = parts[parts.length - 1]; // "ai-service-chunk-0"
    return `fragment-${lastPart}`;
  }
}

5. データベース設計

pgvectorテーブル設計

-- Company Vectors テーブルの作成
CREATE TABLE IF NOT EXISTS company_vectors (
  id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  content_id text NOT NULL,
  content_type text NOT NULL DEFAULT 'business_info',
  content text NOT NULL,
  embedding vector(1536) NOT NULL,
  source text NOT NULL DEFAULT 'company_data',
  source_url text,
  relevance_score float8 DEFAULT 0,
  business_type text,
  industry text,
  region text,
  keywords text[] DEFAULT '{}',
  metadata jsonb DEFAULT '{}',
  fragment_id text,
  service_id text,
  created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
  updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL
);

-- インデックス作成
CREATE INDEX IF NOT EXISTS company_vectors_embedding_idx ON company_vectors USING hnsw (embedding vector_cosine_ops);
CREATE INDEX IF NOT EXISTS company_vectors_content_type_idx ON company_vectors(content_type);
CREATE INDEX IF NOT EXISTS company_vectors_fragment_id_idx ON company_vectors(fragment_id);
CREATE INDEX IF NOT EXISTS company_vectors_service_id_idx ON company_vectors(service_id);
CREATE INDEX IF NOT EXISTS company_vectors_keywords_idx ON company_vectors USING gin(keywords);
CREATE INDEX IF NOT EXISTS company_vectors_metadata_idx ON company_vectors USING gin(metadata);

ベクトル検索関数

-- Company Vectors用のベクトル検索関数
CREATE OR REPLACE FUNCTION match_company_vectors(
  query_embedding vector(1536),
  match_threshold float DEFAULT 0.5,
  match_count int DEFAULT 10
)
RETURNS TABLE (
  id bigint,
  content_id text,
  content_type text,
  content text,
  source text,
  source_url text,
  relevance_score float8,
  business_type text,
  industry text,
  region text,
  keywords text[],
  metadata jsonb,
  fragment_id text,
  service_id text,
  created_at timestamp with time zone,
  similarity float
)
LANGUAGE plpgsql
AS $$
BEGIN
  RETURN QUERY
  SELECT
    cv.id,
    cv.content_id,
    cv.content_type,
    cv.content,
    cv.source,
    cv.source_url,
    cv.relevance_score,
    cv.business_type,
    cv.industry,
    cv.region,
    cv.keywords,
    cv.metadata,
    cv.fragment_id,
    cv.service_id,
    cv.created_at,
    1 - (cv.embedding <=> query_embedding) as similarity
  FROM company_vectors cv
  WHERE 1 - (cv.embedding <=> query_embedding) > match_threshold
  ORDER BY cv.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

6. 構造化データ出力システム

Fragment ID最適化スキーマ(Next.js例)

// Next.jsページでの実装例
export default async function HomePage() {
  // Fragment ID最適化構造化データ生成
  const fragmentIdOptimizationSchema = {
    "@context": "https://schema.org",
    "@type": "WebPage",
    "name": "AI検索最適化サービス",
    "description": "レリバンスエンジニアリング実装によるAI検索エンジン最適化",
    
    // Fragment ID情報
    "hasPart": [
      {
        "@type": "WebPageElement",
        "@id": "#ai-search-optimization-overview",
        "name": "AI検索エンジン最適化の概要",
        "description": "Mike King理論に基づくレリバンスエンジニアリング実装",
        "keywords": ["AI検索", "ChatGPT SEO", "Perplexity対策"],
        "about": {
          "@type": "Thing",
          "name": "レリバンスエンジニアリング",
          "sameAs": "https://ipullrank.com/relevance-engineering"
        }
      },
      {
        "@type": "WebPageElement", 
        "@id": "#rag-system-architecture",
        "name": "RAGシステムアーキテクチャ",
        "description": "ベクトル検索とLLM統合によるRAGシステム構築",
        "keywords": ["RAG", "ベクトル検索", "Embeddings"],
        "about": {
          "@type": "SoftwareApplication",
          "name": "Triple RAGアーキテクチャ"
        }
      }
    ],
    
    // セマンティック接続性スコア
    "semanticConnectivity": {
      "totalSemanticWeight": 1.85,
      "averageSemanticWeight": 0.925,
      "hierarchyComplexity": 2,
      "entityDensity": 15
    },
    
    // AI検索エンジン最適化情報
    "potentialAction": [
      {
        "@type": "SearchAction",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://example.com/search?q={search_term_string}"
        },
        "query-input": "required name=search_term_string"
      }
    ]
  };

  return (
    <main>
      {/* Fragment ID最適化構造化データ */}
      <Script
        id="fragment-id-optimization-schema"
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(fragmentIdOptimizationSchema, null, 2)
        }}
      />

      {/* Table of Contents(Fragment ID ナビゲーション) */}
      <div className="bg-black py-4 border-b border-gray-800 pt-20">
        <div className="container mx-auto px-4">
          <TableOfContents items={tocItems} compact={true} />
        </div>
      </div>
      
      {/* メインコンテンツ(AI検索最適化 + Fragment ID対応) */}
      <section id="ai-search-optimization-overview" className="scroll-mt-20">
        <HeroSection />
      </section>

      <section id="rag-system-architecture" className="scroll-mt-20">
        <RAGSystemSection />
      </section>
    </main>
  );
}

7. 全コンテンツベクトル化API

統合ベクトル化エンドポイント

// app/api/vectorize-all-content/route.ts
import { NextResponse } from 'next/server';
import { ContentExtractor } from '@/lib/vector/content-extractor';
import { OpenAIEmbeddings } from '@/lib/vector/openai-embeddings';
import { SupabaseVectorStore } from '@/lib/vector/supabase-vector-store';

export async function POST() {
  try {
    console.log('🚀 全コンテンツベクトル化開始...');
    
    // 1. コンテンツ抽出
    const contentExtractor = new ContentExtractor();
    const contents = await contentExtractor.extractAllContent();
    console.log(`📄 抽出されたコンテンツ数: ${contents.length}`);
    
    // 2. OpenAI Embeddings初期化
    const embeddings = new OpenAIEmbeddings();
    
    // 3. SupabaseVectorStore初期化
    const vectorStore = new SupabaseVectorStore();
    
    // 4. 全コンテンツをベクトル化
    console.log(`🔄 ${contents.length}個のコンテンツをベクトル化中...`);
    const allVectors = await embeddings.processExtractedContent(contents);
    
    console.log(`🎯 総ベクトル数: ${allVectors.length}`);
    
    // 5. 全ベクトルを保存
    console.log('💾 全ベクトルを保存中...');
    const saveResults = [];
    let successCount = 0;
    let failureCount = 0;
    
    for (const vector of allVectors) {
      try {
        const result = await vectorStore.saveVector(vector);
        saveResults.push(result);
        successCount++;
        console.log(`✅ ベクトル保存成功: ${vector.metadata.title} (ID: ${result.id})`);
      } catch (error) {
        console.error(`❌ ベクトル保存エラー: ${vector.metadata.title}`, error);
        failureCount++;
      }
    }
    
    console.log(`✅ 全コンテンツベクトル化完了!成功: ${successCount}, 失敗: ${failureCount}`);
    
    return NextResponse.json({
      success: true,
      message: '全コンテンツベクトル化完了',
      results: {
        totalContent: contents.length,
        totalVectors: allVectors.length,
        saveResults: {
          success: successCount,
          failed: failureCount,
          total: allVectors.length
        },
        contentBreakdown: {
          structured: contents.filter(c => c.metadata.type === 'structured-data').length,
          services: contents.filter(c => c.metadata.type === 'service').length,
          others: contents.filter(c => !['structured-data', 'service'].includes(c.metadata.type)).length
        }
      }
    });
    
  } catch (error) {
    console.error('❌ 全コンテンツベクトル化エラー:', error);
    return NextResponse.json({
      success: false,
      error: error instanceof Error ? error.message : '不明なエラー'
    }, { status: 500 });
  }
}

8. ベクトル検索API実装

RAG検索エンドポイント

// app/api/search-rag/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { supabase } from '@/lib/supabase';
import { OpenAIEmbeddings } from '@/lib/vector/openai-embeddings';

export async function POST(request: NextRequest) {
  try {
    const { query, limit = 5 } = await request.json();

    if (!query) {
      return NextResponse.json(
        { error: 'クエリが指定されていません' },
        { status: 400 }
      );
    }

    console.log(`🔍 RAG検索開始: "${query}"`);

    // 1. クエリをベクトル化
    const embeddings = new OpenAIEmbeddings();
    const queryVector = await embeddings.embedSingle(query);

    // 2. ベクトル検索実行
    const { data: searchResults, error: searchError } = await supabase
      .rpc('match_company_vectors', {
        query_embedding: queryVector,
        match_threshold: 0.5,
        match_count: limit
      });

    if (searchError) {
      console.error('ベクトル検索エラー:', searchError);
      return NextResponse.json(
        { error: 'ベクトル検索に失敗しました' },
        { status: 500 }
      );
    }

    console.log(`✅ ${searchResults.length}件の結果を取得`);

    // 3. 結果をフォーマット
    const formattedResults = searchResults.map((result: any) => ({
      id: result.id,
      content: result.content,
      title: result.metadata?.title || 'タイトル不明',
      url: result.source_url,
      similarity: result.similarity,
      fragmentId: result.fragment_id,
      serviceId: result.service_id,
      contentType: result.content_type,
      businessType: result.business_type,
      keywords: result.keywords
    }));

    return NextResponse.json({
      success: true,
      query,
      results: formattedResults,
      totalResults: searchResults.length
    });

  } catch (error) {
    console.error('RAG検索API エラー:', error);
    return NextResponse.json(
      { error: 'RAG検索処理中にエラーが発生しました' },
      { status: 500 }
    );
  }
}

運用上の重要なポイント

パフォーマンス最適化

  1. バッチ処理: 大量のコンテンツは分割してベクトル化
  2. キャッシュ戦略: Fragment IDベースのキャッシュ
  3. インデックス最適化: pgvectorのHNSWインデックス活用

監視・メンテナンス

  1. 品質監視: セマンティックウェイトの定期検証
  2. パフォーマンス監視: ベクトル検索レスポンス時間
  3. コンテンツ更新: エンティティ関係の定期見直し

スケーラビリティ対応

  1. マルチテナント対応: サービスIDベースの分離
  2. 地域対応: 言語別Fragment ID
  3. 拡張性: 新しいAI検索エンジンへの対応

まとめ

  • 二重構造アプローチ: 構造化データとベクトル化の並行実装
  • Fragment ID統合: 両システムへの一貫したID付与
  • AI検索エンジン対応: 5大エンジンの特性に合わせた最適化
  • エンティティ関係性: セマンティックな関連性の構造化
  • スケーラブル設計: 企業レベルでの運用を前提とした設計

このシステムにより、従来のSEOでは到達できない次元のAI検索最適化が実現できます。特に、構造化データとベクトル化にフラグメントIDを統合することで、LLMの理解能力を飛躍的に向上させることが可能です。AI検索時代において、レリバンスエンジニアリングは必須の技術です。本実装を参考に、プロジェクトでの最適化を前進させてください。

著者について

原田賢治

原田賢治

代表取締役・AI技術責任者

Mike King理論に基づくレリバンスエンジニアリング専門家。生成AI検索最適化、ChatGPT・Perplexity対応のGEO実装、企業向けAI研修を手がける。 15年以上のAI・システム開発経験を持ち、全国で企業のDX・AI活用、退職代行サービスを支援。