跳轉到

RAG 語意搜尋範例(Enterprise)

需求套件nextpdf/enterprise 難度:進階

先決條件

composer require nextpdf/enterprise

架構概觀

PDF 文件
    ▼ NextPDF Enterprise
┌──────────────────────┐
│  PDF 文字提取         │  ─── 保留結構(段落、標題、表格)
│  + 結構分析           │  ─── 章節偵測、頁碼記錄
└──────────┬───────────┘
    ▼ 文字分塊
┌──────────────────────┐
│  Semantic Chunker     │  ─── 依語義邊界切分(非固定字元數)
│  (Enterprise)         │  ─── 保留上下文重疊
└──────────┬───────────┘
    ▼ 向量化(外部服務)
┌──────────────────────┐
│  Embedding API        │  ─── OpenAI / Anthropic / 本地模型
└──────────┬───────────┘
    ▼ 儲存
┌──────────────────────┐
│  Vector Database      │  ─── Qdrant / Pinecone / pgvector
└──────────────────────┘

完整程式碼

一、PDF 文字提取與分塊

<?php

declare(strict_types=1);

use NextPDF\Enterprise\Extraction\PdfTextExtractor;
use NextPDF\Enterprise\Extraction\ValueObjects\ExtractionOptions;
use NextPDF\Enterprise\Rag\SemanticChunker;
use NextPDF\Enterprise\Rag\ValueObjects\ChunkingOptions;
use NextPDF\Enterprise\Rag\ValueObjects\TextChunk;

// ─── 文字提取 ─────────────────────────────────────────────────
$extractor = PdfTextExtractor::create();

$extractedDoc = $extractor->extract(
    path:    '/documents/technical-manual.pdf',
    options: ExtractionOptions::create()
        ->withStructurePreservation(true)   // 保留標題/段落結構
        ->withTableExtraction(true)         // 提取表格內容
        ->withPageMetadata(true)            // 記錄頁碼
        ->withLanguageDetection(true),      // 自動偵測語言
);

// ─── 語義分塊 ─────────────────────────────────────────────────
$chunker = SemanticChunker::create();

$chunks = $chunker->chunk(
    document: $extractedDoc,
    options:  ChunkingOptions::create()
        ->withMaxChunkSize(512)          // 最大 token 數
        ->withOverlap(50)                // 重疊 token 數
        ->withSentenceBoundary(true)     // 在句子邊界切分
        ->withPreserveCodeBlocks(true)   // 完整保留程式碼區塊
        ->withPreserveTables(true),      // 完整保留表格
);

foreach ($chunks as $chunk) {
    echo "Chunk ID: {$chunk->id()}\n";
    echo "Page: {$chunk->sourcePageRange()}\n";
    echo "Section: {$chunk->sectionTitle()}\n";
    echo "Text: {$chunk->text()}\n";
    echo "---\n";
}

二、向量化與索引

use NextPDF\Enterprise\Rag\VectorIndexer;
use NextPDF\Enterprise\Rag\Embeddings\OpenAiEmbeddingProvider;
use NextPDF\Enterprise\Rag\Storage\QdrantVectorStore;

// ─── 嵌入提供者(OpenAI)────────────────────────────────────
$embeddingProvider = OpenAiEmbeddingProvider::create(
    apiKey:     $_ENV['OPENAI_API_KEY'],
    model:      'text-embedding-3-large',
    dimensions: 1536,
);

// ─── 向量資料庫(Qdrant)────────────────────────────────────
$vectorStore = QdrantVectorStore::create(
    host:           $_ENV['QDRANT_HOST'],
    apiKey:         $_ENV['QDRANT_API_KEY'],
    collectionName: 'nextpdf-documents',
    vectorSize:     1536,
);

// ─── 建立索引 ─────────────────────────────────────────────────
$indexer = VectorIndexer::create(
    embeddingProvider: $embeddingProvider,
    vectorStore:       $vectorStore,
);

$result = $indexer->index(
    chunks:      $chunks,
    documentId:  'technical-manual-v2',
    metadata:    [
        'filename'    => 'technical-manual.pdf',
        'version'     => '2.0',
        'language'    => 'zh-TW',
        'indexed_at'  => date('c'),
    ],
    batchSize:   50,   // 每批次 50 個向量
);

echo "Indexed {$result->indexedCount()} chunks.\n";
echo "Failed: {$result->failedCount()}\n";

三、語意搜尋與 RAG 查詢

use NextPDF\Enterprise\Rag\SemanticSearcher;
use NextPDF\Enterprise\Rag\ValueObjects\SearchQuery;
use NextPDF\Enterprise\Rag\ValueObjects\SearchOptions;

$searcher = SemanticSearcher::create(
    embeddingProvider: $embeddingProvider,
    vectorStore:       $vectorStore,
);

// ─── 語意搜尋 ─────────────────────────────────────────────────
$results = $searcher->search(
    query:   SearchQuery::fromText('如何在 PHP 中生成 PDF 數位簽章?'),
    options: SearchOptions::create()
        ->withTopK(5)                   // 返回最相關的 5 個結果
        ->withMinScore(0.7)             // 最低相似度分數
        ->withFilter([                  // 元資料過濾
            'language' => 'zh-TW',
        ])
        ->withReranking(true),          // 啟用重排序
);

// ─── 建構 RAG 提示詞 ──────────────────────────────────────────
$context = implode("\n\n---\n\n", array_map(
    static fn($r) => "來源:{$r->metadata()['filename']} 第 {$r->sourcePageRange()} 頁\n{$r->text()}",
    $results->items(),
));

$prompt = <<<PROMPT
請根據以下 NextPDF 文件內容回答問題。

問題:如何在 PHP 中生成 PDF 數位簽章?

相關文件內容:
{$context}

請提供具體的 PHP 程式碼範例,並說明每個步驟。
PROMPT;

// 將 $prompt 傳送給 LLM(Anthropic Claude、OpenAI GPT 等)
// $response = $llmClient->chat($prompt);

批次處理多個 PDF

$files = glob('/documents/*.pdf');

foreach ($files as $file) {
    $extracted = $extractor->extract(path: $file);
    $chunks    = $chunker->chunk(document: $extracted);
    $indexer->index(chunks: $chunks, documentId: md5($file));
}

程式碼說明

延伸閱讀