Key Findings
  • 從 SimpleVectorStore 切換到 PGVector 只需改 application.yml——Service 層零程式碼修改,這就是 Spring AI 抽象層的威力
  • PGVector = PostgreSQL + pgvector 擴充,不需要額外的向量資料庫服務——用你已有的 PostgreSQL 基礎設施,Docker 一行啟動
  • Metadata 權限管控透過 FilterExpression 實現行級安全——不同部門只能查詢被授權的文件,機密文件只有特定角色可存取
  • Spring Boot Actuator + Micrometer 提供 AI 應用監控——追蹤請求量、回應延遲、Token 用量,是控制成本與保障 SLA 的基礎

從開發到生產:為什麼需要 PGVector?

前面課程使用的 SimpleVectorStore 是記憶體儲存——應用重啟資料就消失。企業應用需要持久化[1]

特性SimpleVectorStorePGVector
儲存記憶體PostgreSQL 持久化
重啟後資料消失資料保留
資料量< 10,000 筆百萬筆級
多節點不支援共用資料庫
Metadata 過濾基本完整 SQL 能力
適合開發 / 測試生產環境

PGVector[2] 的最大優勢:不需要額外引入新的資料庫服務。如果你的企業已經在用 PostgreSQL,加一個 pgvector 擴充就能搞定向量搜尋。

Step 1:Docker 部署 PGVector

# docker-compose.yml
version: '3.8'
services:
  pgvector:
    image: pgvector/pgvector:pg16
    container_name: pgvector
    environment:
      POSTGRES_DB: vectordb
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"
    volumes:
      - pgvector_data:/var/lib/postgresql/data

volumes:
  pgvector_data:
# 啟動
docker compose up -d

# 驗證 pgvector 擴充
docker exec pgvector psql -U postgres -d vectordb \
  -c "CREATE EXTENSION IF NOT EXISTS vector; SELECT extversion FROM pg_extension WHERE extname='vector';"

預期輸出:

 extversion
------------
 0.8.2
(1 row)

Step 2:零程式碼切換到 PGVector

這是 Spring AI 最優雅的設計——從 SimpleVectorStore 切換到 PGVector,Service 層完全不用改

# application-pgvector.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vectordb
    username: postgres
    password: postgres
  ai:
    vectorstore:
      pgvector:
        dimensions: 1536           # text-embedding-3-small
        index-type: hnsw           # 高效能近似搜尋索引
        distance-type: cosine      # 餘弦相似度
        initialize-schema: true    # 自動建表
# 以 PGVector 模式啟動
./gradlew bootRun --args='--spring.profiles.active=pgvector'

Spring Boot 啟動時會自動建立 vector_store 表,並使用 HNSW 索引加速搜尋。你的 ChatServiceChatController 不需要任何修改——因為它們都是對 VectorStore 介面程式設計,不依賴具體實作。

驗證資料持久化

# 查看向量資料表
docker exec pgvector psql -U postgres -d vectordb \
  -c "SELECT count(*) as total_rows FROM vector_store;"

# 預覽儲存的文件
docker exec pgvector psql -U postgres -d vectordb \
  -c "SELECT substring(content, 1, 60) as preview,
             metadata->>'source' as source
      FROM vector_store LIMIT 5;"

重啟 Spring Boot 後再查——資料依然存在。這就是持久化的威力。

Step 3:PDF 文件解析與入庫

企業最常見的文件格式是 PDF。使用 Apache Tika[4] 自動解析:

@Service
class DocumentService(private val vectorStore: VectorStore) {

    private val splitter = TokenTextSplitter.builder()
        .withChunkSize(500)
        .withMinChunkSizeChars(100)
        .build()

    fun processAndStore(file: MultipartFile, category: String?): List<Document> {
        // Step 1: Tika 自動偵測格式並解析(PDF/Word/PPT/Excel 都支援)
        val reader = TikaDocumentReader(InputStreamResource(file.inputStream))
        val rawDocs = reader.read()

        // Step 2: 切割成小段落
        val chunks = splitter.split(rawDocs)

        // Step 3: 附加 Metadata
        chunks.forEach { doc ->
            doc.metadata["source"] = file.originalFilename ?: "unknown"
            doc.metadata["category"] = category ?: "general"
            doc.metadata["uploadTime"] = java.time.Instant.now().toString()
        }

        // Step 4: 存入向量資料庫
        vectorStore.add(chunks)
        return chunks
    }
}
# 上傳 PDF
curl -X POST http://localhost:8080/api/documents/upload \
  -F "[email protected]" \
  -F "category=員工手冊"

Tika 會自動提取 PDF 中的文字(包括表格),TokenTextSplitter 切割後存入 PGVector。整個流程全自動。

Step 4:權限管控——不同角色看不同文件

企業知識庫最重要的需求之一:工程部不該看到財務的薪資報表,實習生不該查到機密文件。Spring AI 的 FilterExpression 可以實現行級安全:

@Service
class SecureRagService(
    private val chatClientBuilder: ChatClient.Builder,
    private val vectorStore: VectorStore
) {
    fun ask(question: String, user: UserInfo): String {
        val b = FilterExpressionBuilder()

        // 建立權限過濾條件
        val filter = b.and(
            // 只能看自己部門或全公司文件
            b.or(
                b.eq("department", user.department),
                b.eq("department", "全公司")
            ),
            // 只能看被授權的機密等級
            b.in("accessLevel", *user.accessLevels.toTypedArray())
        ).build()

        val chatClient = chatClientBuilder
            .defaultAdvisors(
                QuestionAnswerAdvisor.builder(vectorStore)
                    .searchRequest(SearchRequest.builder()
                        .topK(3)
                        .filterExpression(filter)
                        .build())
                    .build()
            )
            .build()

        return chatClient.prompt()
            .system("根據知識庫回答問題,找不到就說不知道。")
            .user(question)
            .call().content() ?: "(無回應)"
    }
}

data class UserInfo(
    val name: String,
    val department: String,           // "工程部" / "財務部" / "人資部"
    val accessLevels: List<String>    // ["public", "internal"] 或 ["public", "internal", "confidential"]
)

權限控制的效果:

使用者問「薪資結構」問「部署流程」
工程師 Alice
(工程部, internal)
只回傳「全公司」的薪資概要完整回答(工程部文件)
財務主管 Bob
(財務部, confidential)
完整薪資報表(含機密數據)只回傳「全公司」的部署概要
實習生 Charlie
(工程部, public)
僅公開的薪資福利資訊僅公開的技術文件

Step 5:完整企業 RAG 架構

┌─────────────────────────────────────────────────────┐
│  Frontend(Vue 3 / React)                           │
│  聊天介面 / 文件上傳 / 管理後台                        │
└──────────────────┬──────────────────────────────────┘
                   │ REST API + SSE
┌──────────────────┴──────────────────────────────────┐
│  Spring Boot Backend                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────┐ │
│  │ChatController│  │DocController │  │ AuthFilter  │ │
│  └──────┬───────┘  └──────┬───────┘  └─────┬──────┘ │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌─────┴──────┐ │
│  │ ChatService  │  │ DocService   │  │UserService │ │
│  │ +Memory +RAG │  │ +Tika +Split │  │ +JWT       │ │
│  └──────┬───────┘  └──────┬───────┘  └────────────┘ │
│         │                  │                          │
│  Spring AI Core: ChatClient / VectorStore / Embedding │
└─────────┬──────────────────┬────────────────────────┘
          │                  │
   ┌──────┴──────┐    ┌─────┴──────┐
   │ OpenAI API  │    │  PGVector  │
   │ (GPT-4o)    │    │(PostgreSQL)│
   └─────────────┘    └────────────┘

Step 6:監控與可觀測性

生產環境需要監控 AI 應用的健康狀態與成本[3]

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health, metrics, info
  metrics:
    tags:
      application: spring-ai-rag

自訂 AI 指標:

@Service
class MonitoredChatService(
    private val chatService: ChatService,
    private val meterRegistry: MeterRegistry
) {
    private val chatCounter = Counter.builder("ai.chat.requests")
        .description("AI 聊天請求次數")
        .register(meterRegistry)

    private val chatTimer = Timer.builder("ai.chat.duration")
        .description("AI 聊天回應時間")
        .register(meterRegistry)

    fun chat(message: String, sessionId: String): ChatResult {
        chatCounter.increment()
        return chatTimer.record<ChatResult> {
            chatService.chat(message, sessionId)
        }!!
    }
}

關鍵監控指標:

指標用途警報閾值
ai.chat.requests請求量追蹤突然暴增可能被濫用
ai.chat.duration回應延遲> 10 秒需告警
ai.token.usageToken 成本控制日用量超預算
ai.rag.search.duration向量搜尋效能> 500ms 需優化索引
pgvector.pool.activeDB 連線池接近上限需擴容

本課重點回顧

概念重點
PGVectorPostgreSQL + pgvector 擴充,Docker 一行啟動
零程式碼切換改 application.yml 即可,Service 層不用動
PDF 解析Tika + TokenTextSplitter + VectorStore
權限管控Metadata + FilterExpression 實現行級安全
企業架構Controller → Service → Spring AI → PGVector + OpenAI
監控Actuator + Micrometer 自訂 AI 指標

下一步:Lesson 12 — 雲端部署

企業級 RAG 系統已就緒,最後一步是部署到雲端:

  • Docker 容器化 — 多階段建構、映像最佳化
  • Google Cloud Run — 無伺服器部署、自動擴展
  • CI/CD Pipeline — GitHub Actions 自動建構與部署

企業級系統就緒,最後一步!

PGVector 持久化、權限管控都完成了,最後一步——Docker 打包、Cloud Run 部署、CI/CD 自動化。

前往 Lesson 12:雲端部署 →