EMBEDDING_MODEL_ID_FIX.md 6.6 KB

Embedding モデル ID 連携の修正

🐛 問題の記述

混合検索失敗: NotFoundException: ModelConfig with ID "embedding-3" not found or not owned by user.

🔍 問題の分析

混同されやすい概念

システム内には2種類の異なる「ID」が存在します:

  1. モデル設定テーブルの ID (ModelConfig.id)

    • データベースの主キー
    • 例:"embedding-3", "default-embedding"
    • 用途:ModelConfigService.findOne(id, userId)
  2. モデル識別子 (ModelConfig.modelId)

    • AI ベンダー側でのモデル名
    • 例:"text-embedding-3-large", "text-embedding-ada-002"
    • 用途:AI API 呼び出し時のパラメータ

データフロー

ユーザー設定: user_setting.selectedEmbeddingId = "embedding-3"  ✅ テーブルID

フロントエンド: settings.selectedEmbeddingId
    ↓ 転送
バックエンド Controller: selectedEmbeddingId = "embedding-3"
    ↓ 転送
ChatService: embeddingModel.id = "embedding-3"  ✅ 正常
    ↓ 転送
hybridSearch: embeddingModelId = "embedding-3"
    ↓ 転送
EmbeddingService.getEmbeddings(embeddingModelId)
    ↓ 呼び出し
ModelConfigService.findOne("embedding-3", userId)  ✅ 正常

以前の誤り

ChatService.ts (誤り):

// 182行目付近
searchResults = await this.hybridSearch(
  [message],
  userId,
  embeddingModel.modelId,  // ❌ 誤り! "text-embedding-3-large" を渡してしまっていた
);

hybridSearch (受信側):

private async hybridSearch(
  keywords: string[],
  userId: string,
  embeddingModelId?: string,  // "text-embedding-3-large" を受け取ってしまう
)

EmbeddingService (期待値):

async getEmbeddings(
  texts: string[],
  userId: string,
  embeddingModelConfigId: string,  // 本来は "embedding-3" を期待
) {
  const modelConfig = await this.modelConfigService.findOne(
    embeddingModelConfigId,  // ❌ "text-embedding-3-large" で検索しても見つからない!
    userId,
  );
}

✅ 修正内容

修正箇所

server/src/chat/chat.service.ts:

// 182行目付近
searchResults = await this.hybridSearch(
  [message],
  userId,
  embeddingModel.id,  // ✅ テーブルID "embedding-3" を使用するように変更
);

修正後のフロー

1. ユーザーが埋め込みモデルを選択: text-embedding-3-large
   ↓
2. システムが user_setting テーブルに保存:
   selectedEmbeddingId = "embedding-3"  (ModelConfig テーブルの主キー)
   ↓
3. フロントエンドがチャットリクエストを送信:
   { selectedEmbeddingId: "embedding-3" }
   ↓
4. バックエンド Controller が受信:
   selectedEmbeddingId = "embedding-3"
   ↓
5. ChatService がモデルを検索:
   embeddingModel = models.find(m => m.id === "embedding-3")
   // 結果: { id: "embedding-3", modelId: "text-embedding-3-large", ... }
   ↓
6. ChatService が hybridSearch を呼び出し:
   hybridSearch(..., embeddingModel.id)  // "embedding-3" を渡す
   ↓
7. hybridSearch が EmbeddingService を呼び出し:
   getEmbeddings(..., "embedding-3")
   ↓
8. EmbeddingService が設定を検索:
   findOne("embedding-3", userId)  // ✅ 設定が見つかる
   ↓
9. AI API を呼び出し:
   model: "text-embedding-3-large"  // modelId を用いて API を実行

📊 ID の対応関係

シーン 使用するフィールド 用途
ユーザー設定 user_setting.selectedEmbeddingId "embedding-3" ユーザーの選択を保存
設定の検索 ModelConfig.id "embedding-3" データベースクエリ
API 呼び出し ModelConfig.modelId "text-embedding-3-large" AI ベンダーのインターフェース

🔑 重要な原則

1. データベース操作にはテーブル ID を使用する

// ✅ 正解
const model = await modelConfigService.findOne(modelId, userId);  // modelId = "embedding-3"

// ❌ 誤り
const model = await modelConfigService.findOne(modelId, userId);  // modelId = "text-embedding-3-large"

2. API 呼び出しにはモデル識別子を使用する

// ✅ 正解
fetch(apiUrl, {
  body: JSON.stringify({
    model: modelConfig.modelId,  // "text-embedding-3-large"
  }),
});

3. 内部的な受け渡しにはテーブル ID を使用する

// ✅ 正解
embeddingService.getEmbeddings(texts, userId, modelConfig.id);  // "embedding-3"

// ❌ 誤り
embeddingService.getEmbeddings(texts, userId, modelConfig.modelId);  // "text-embedding-3-large"

🧪 検証

テスト手順

  1. ユーザー設定の確認

    SELECT selectedEmbeddingId FROM user_setting WHERE userId = 'xxx';
    -- 期待値: "embedding-3" (テーブルID)
    
  2. モデル設定の確認

    SELECT id, modelId, name FROM model_config WHERE userId = 'xxx';
    -- 期待値: embedding-3 | text-embedding-3-large | Text Embedding 3 Large
    
  3. チャットメッセージの送信

    • バックエンドログを確認
    • 期待される出力: "使用嵌入模型: Text Embedding 3 Large text-embedding-3-large ID: embedding-3"
  4. 埋め込みベクトルの生成確認

    • ログに "从 Text Embedding 3 Large 获取到 X 个嵌入向量" と表示されること

期待されるログ出力

=== ChatService.streamChat ===
User ID: user-123
Selected Embedding ID: embedding-3
ID に基づいてモデルを検索: embedding-3
使用するモデル: Text Embedding 3 Large text-embedding-3-large ID: embedding-3
埋め込みベクトルを生成中...
Text Embedding 3 Large から 1 個の埋め込みベクトルを取得しました。次元数: 2560

📁 修正されたファイル

  • server/src/chat/chat.service.ts - 182行目。 embeddingModel.modelId ではなく embeddingModel.id を渡すように変更。

💡 学んだ教訓

  1. 2種類の ID を区別すること:テーブル主キー vs モデル識別子
  2. パラメータ名を明確にすることembeddingModelConfigId vs embeddingModelId
  3. 呼び出し先の期待値を確認することEmbeddingService がどのタイプの ID を求めているか
  4. ログ出力の工夫:デバッグを容易にするため、両方の ID を出力する

    console.log('使用するモデル:', embeddingModel.name, embeddingModel.modelId, 'ID:', embeddingModel.id);
    // 出力: 使用するモデル: Text Embedding 3 Large text-embedding-3-large ID: embedding-3