iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Build on Google AI

用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務系列 第 26 篇

Day 26 -【進階檢索與向量檢索】Qdrant + Gemini Embedding 打造跨資產 Multi-Modal Hybrid RAG 混合檢索系統

  • 分享至 

  • xImage
  •  

在昨天的 [Day 25] 中,我們利用 Cloud Tasks + Cloud Pub/Sub 打通了長影音背景非同步提煉與 Webhook 通知管線,將伺服器響應延遲極速降至 85ms。

當使用者在 OmniVibe AI 平台上累積了數十甚至數百筆影片、Podcast 音訊與 PDF 文檔後,單一檔案的提煉與問答已經無法滿足商業情境。使用者常會提出跨檔案的綜合性疑問,例如:

  • 「在我上個月提煉過的所有科技 Podcast 中,有哪些集數討論過 HBM4 記憶體架構?」
  • 「幫我比對上次上傳的演講影片與這份 PDF 白皮書,兩者對 2026 年 AI 晶片成長率的預測有何差異?」

今天,我們將引進 Qdrant 向量資料庫 + Google Gemini Embedding API,實作一套具備元資料過濾(Payload Filtering)與高準確度的 Multi-Modal Hybrid RAG(跨資產混合檢索系統),讓 OmniVibe AI 從「單檔提煉工具」升級為「企業級個人知識大腦」!


🏗️ 跨資產 Multi-Modal Hybrid RAG 架構設計

跨資產檢索的核心挑戰在於:必須在海量切片(Chunks)中,既要保持語意相關性(Dense Vector),又要精確鎖定特定的使用者資產屬性(Payload Metadata),最後由 Gemini 1.5 Pro 進行多來源的資料歸納與引文標註(Source Citations)。

graph TD
    subgraph 1. 向量切片建庫 (Ingestion Pipeline)
        Asset[媒體資產 (PDF / 影音逐字稿)] --> Chunking[語意切片 Semantic Chunking]
        Chunking --> EmbedModel[Gemini text-embedding-004]
        EmbedModel --> DenseVector[768維 稠密向量]
        DenseVector --> QdrantUpsert[Qdrant 寫入 Point + Payload]
    end

    subgraph 2. 跨資產混合檢索 (Hybrid Retrieval)
        UserQuery[跨資產提問 Query] --> QEmbed[Gemini Embedding]
        QEmbed --> QdrantSearch[Qdrant 向量搜尋 + Payload Filter (userId)]
        QdrantSearch --> TopK[篩選 Top-K 最相關 Context Chunks]
    end

    subgraph 3. 歸納與引文生成 (Synthesis & Citation)
        TopK --> SynthesisPrompt[構築帶有來源標記的 RAG Prompt]
        SynthesisPrompt --> GeminiPro[Gemini 1.5 Pro 大腦]
        GeminiPro --> FinalResponse[回傳精準解答 + 附帶來源影片時間軸/頁碼]
    end


🛠️ 第一步:實作向量切片與 Qdrant 入庫管線 (src/lib/rag/ingest.ts)

我們在處理完檔案後,將內容依據語意進行 Chunk 切片,使用 Gemini text-embedding-004 生成向量,並將檔案名稱、時間軸標記(Timestamp)與使用者 ID 寫入 Qdrant Payload:

// src/lib/rag/ingest.ts
import { QdrantClient } from '@qdrant/js-client-rest';
import { GoogleGenerativeAI } from '@google/generative-ai';

const qdrant = new QdrantClient({
  url: process.env.QDRANT_URL || 'http://localhost:6333',
  apiKey: process.env.QDRANT_API_KEY,
});

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
const COLLECTION_NAME = 'omnivibe_knowledge_rag';

export interface AssetChunk {
  chunkId: string;
  assetId: string;
  userId: string;
  fileName: string;
  mimeType: string;
  text: string;
  timestampRange?: string; // 例如 "05:20 - 07:15" 或 "Page 12"
}

/**
 * 取得文本的 768 維 Embedding 向量
 */
async function generateEmbedding(text: string): Promise<number[]> {
  const model = genAI.getGenerativeModel({ model: 'text-embedding-004' });
  const result = await model.embedContent(text);
  return result.embedding.values;
}

/**
 * 將資產切片大量寫入 Qdrant 向量資料庫
 */
export async function ingestAssetChunks(chunks: AssetChunk[]) {
  console.log(`[RAG Ingestion] 開始處理解析 ${chunks.length} 個資產切片...`);

  const points = await Promise.all(
    chunks.map(async (chunk) => {
      const vector = await generateEmbedding(chunk.text);
      return {
        id: chunk.chunkId,
        vector,
        payload: {
          assetId: chunk.assetId,
          userId: chunk.userId,
          fileName: chunk.fileName,
          mimeType: chunk.mimeType,
          text: chunk.text,
          timestampRange: chunk.timestampRange || 'N/A',
          createdAt: new Date().toISOString(),
        },
      };
    })
  );

  // 寫入 Qdrant Collection
  await qdrant.upsert(COLLECTION_NAME, {
    wait: true,
    points,
  });

  console.log(`[RAG Ingestion] 成功將 ${points.length} 個 Point 入庫至 Qdrant!`);
}


⚡ 第二步:實作 Qdrant 多重過濾混合檢索 (src/lib/rag/retrieval.ts)

為了避免檢索到其他租戶的敏感資料,我們利用 Qdrant 的 Payload Filtering,強制限定在當前 userId 的資產集合中進行語意相干度比對:

// src/lib/rag/retrieval.ts
import { QdrantClient } from '@qdrant/js-client-rest';
import { GoogleGenerativeAI } from '@google/generative-ai';

const qdrant = new QdrantClient({
  url: process.env.QDRANT_URL || 'http://localhost:6333',
  apiKey: process.env.QDRANT_API_KEY,
});

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
const COLLECTION_NAME = 'omnivibe_knowledge_rag';

export interface RetrievedContext {
  fileName: string;
  timestampRange: string;
  text: string;
  score: number;
}

/**
 * 進行跨資產向量混合檢索
 */
export async function retrieveRelevantContexts(
  userId: string,
  userQuery: string,
  topK: number = 5
): Promise<RetrievedContext[]> {
  // 1. 將使用者的查詢問題轉為向量
  const embeddingModel = genAI.getGenerativeModel({ model: 'text-embedding-004' });
  const queryEmbedding = await embeddingModel.embedContent(userQuery);

  // 2. 於 Qdrant 進行向量比對,並透過 Payload 嚴格限制只能檢索該 userId 的資料
  const searchResults = await qdrant.search(COLLECTION_NAME, {
    vector: queryEmbedding.embedding.values,
    filter: {
      must: [
        { key: 'userId', match: { value: userId } }, // 租戶安全隔離
      ],
    },
    limit: topK,
    score_threshold: 0.65, // 相關性品質門檻
  });

  return searchResults.map((hit) => ({
    fileName: hit.payload?.fileName as string,
    timestampRange: hit.payload?.timestampRange as string,
    text: hit.payload?.text as string,
    score: hit.score,
  }));
}


🧠 第三步:結合 Gemini 1.5 Pro 生成帶有引文的出處解答 (src/app/api/ai/cross-asset-rag/route.ts)

最後,我們建立跨資產 RAG 整合 API 端點。將檢索出的片段加上來源 metadata 餵給 Gemini 1.5 Pro,指示其回答問題並明確標註出處:

// src/app/api/ai/cross-asset-rag/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { retrieveRelevantContexts } from '@/lib/rag/retrieval';
import { GoogleGenerativeAI } from '@google/generative-ai';

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');

export async function POST(req: NextRequest) {
  try {
    const { userId, query } = await req.json();

    if (!userId || !query) {
      return NextResponse.json({ error: '缺少必要的 userId 或 query' }, { status: 400 });
    }

    // 1. 自 Qdrant 檢索最相關的 Top-5 知識片段
    const contexts = await retrieveRelevantContexts(userId, query, 5);

    if (contexts.length === 0) {
      return NextResponse.json({
        answer: '在您的個人資產庫中未找到與該問題相關的資料,請嘗試上傳更多相關影音或文檔。',
        sources: [],
      });
    }

    // 2. 格式化 Context 片段並標註來源
    const formattedContext = contexts
      .map(
        (c, idx) =>
          `[來源 ${idx + 1}]: 檔案【${c.fileName}】 (時間/位置: ${c.timestampRange})\n內容: ${c.text}`
      )
      .join('\n\n---\n\n');

    // 3. 呼叫 Gemini 1.5 Pro 進行資料綜合歸納與引文標註
    const model = genAI.getGenerativeModel({
      model: 'gemini-1.5-pro',
      systemInstruction: `
你是一位精準的跨資產知識助理。請完全根據提供的 Context 資料來回答使用者的問題。
【回答規範】:
1. 你的回答必須精確、客觀,嚴禁捏造 Context 中未提及的事實。
2. 在引用特定觀點或數據時,請務必在句末以 [來源 X: 檔案名稱 (時間/位置)] 標註出處來源。
3. 若 Context 中的資料不足以完整回答問題,請誠實說明。
`,
    });

    const prompt = `
以下是從您的個人資產庫中檢索出的相關知識片段:

${formattedContext}

使用者問題:${query}

請提供條理清晰的回答並標明資料引文出處:
`;

    const result = await model.generateContent(prompt);
    const answerText = result.response.text();

    return NextResponse.json({
      answer: answerText,
      sources: contexts.map((c) => ({
        fileName: c.fileName,
        timestampRange: c.timestampRange,
        relevanceScore: (c.score * 100).toFixed(1) + '%',
      })),
    });
  } catch (error: any) {
    console.error('[Cross Asset RAG Error]:', error);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}


🧪 實測驗證:跨檔案精準檢索與來源引文渲染

我們在上傳了 3 部 Podcast 影音(音訊)與 2 份 PDF 白皮書後,發起跨資產提問:

提問:「在我上傳的檔案中,專家們對於 2026 年 HBM4 記憶體的技術突破有什麼共識與不同看法?」

🤖 Gemini 1.5 Pro 回傳解答:

根據您個人資產庫中的記錄,專家們對 2026 年 HBM4 技術的看法如下:

1. 共識部分:
   - 記憶體頻寬將突破 1.5 TB/s,全面成為解決大模型推論 Memory-Wall 的核心 [來源 1: semiconductor_report_2026.pdf (Page 14)]。
   - 基礎底板 (Base Die) 將全面轉向 12nm/5nm 先進邏輯製程打造 [來源 2: tech_podcast_ep42.mp3 (12:35 - 14:10)]。

2. 觀點差異:
   - 關於熱耗散 (Thermal Management) 的解決方案,PDF 報告傾向採用浸沒式液冷技術 [來源 1: semiconductor_report_2026.pdf (Page 18)];而 Podcast 中的專家則認為傳統微通道散熱仍能維持至 2027 年 [來源 2: tech_podcast_ep42.mp3 (18:50 - 20:15)]。


🎯 總結與明日預告

今天我們成功實現了 Multi-Modal Hybrid RAG 跨資產知識檢索系統:

  1. 結合 **Gemini text-embedding-004** 與 Qdrant 向量資料庫,建構具備多租戶安全隔離的知識檢索庫。
  2. 透過 Metadata Payload Filtering,確保使用者能在毫秒級內精準跨檔案檢索個人的影片、音訊與 PDF 精華。
  3. 利用 Gemini 1.5 Pro 自動進行多來源內容的合成與 嚴謹引文標註 (Source Citations),徹底打通個人知識庫的檢索閉環。

有了強大的提煉與跨檔案知識庫檢索能力後,如果 AI 還能主動幫我們「採取行動(Take Action)」——例如自動生成 Google Slides 簡報、寄送摘要 Email、或是自動建立 Google 行事曆行程,產品價值將迎來爆炸性成長。

👉 明天(Day 27),我們將進入【Agentic AI 工具調用篇】:實戰 Gemini 1.5 Function Calling 與 Agent 代理機制!看我們如何讓 AI 自動為你操作外部 API 與自動化工作流!

我們明天見!🔥


上一篇
Day 25 -【微服務與非同步任務】Cloud Tasks + Cloud Pub/Sub 實戰長影音背景非同步提煉與 Webhook 即時通知
下一篇
Day 27 -【Agentic AI 工具調用】Gemini 1.5 Function Calling 實戰:從內容提煉到自動發送 Email 與建立日曆行程
系列文
用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言