2025年8月10日 星期日

自動化生成現代全端 AI 應用專案:Laravel, Vue.js, FastAPI 與 Docker Compose 的關鍵技術與實現

自動化生成現代全端 AI 應用專案:Laravel, Vue.js, FastAPI 與 Docker Compose 的關鍵技術與實現

前言

在快速變遷的軟體開發領域,高效的專案初始化與配置是成功的關鍵。當面對由多個技術棧組成的複雜全端應用程式,特別是涉及人工智慧微服務時,手動設置可能會耗時且容易出錯。本文將深入探討如何透過自動化流程,快速且一致地生成一個現代化的全端 AI 應用專案骨架,該專案整合了 Laravel 後端、Vue.js 前端、FastAPI AI 微服務,並利用 Docker Compose 進行容器化部署。

您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/workflow-ai-platform.git

專案亮點

本專案集成了多項關鍵技術,提供以下核心亮點:

  • 全端整合:無縫協作的 Laravel (PHP) 後端、Vue.js (JavaScript) 前端和 FastAPI (Python) AI 微服務。

  • 智慧文件管理:支援文件上傳、AI 摘要(OpenAI LLM),以及基於 OpenAI Embedding 和 Qdrant 的語意搜尋功能。

  • AI 語音助理:整合 Faster-Whisper 模型實現高效語音轉錄,並結合 LangChain 構建 RAG (檢索增強生成) 流程,提供智能對話回應及完整的對話歷史記錄。

  • 安全認證:採用 Laravel Sanctum 實現安全的 API Token 認證,並透過 Pinia 在前端管理認證狀態。

  • 資料持久化:使用 MySQL 儲存應用數據,並利用 Qdrant 作為高效能向量資料庫。

  • 自動化 API 文件:透過 Laravel Scribe 自動生成清晰互動的後端 API 文檔,FastAPI 則提供 Swagger UI。

  • 全面測試覆蓋:包含前端 (Vitest 單元測試、Cypress E2E 測試) 和後端 (PHPUnit) 以及 AI 微服務 (Pytest) 的自動化測試,確保應用程式的穩定性和可靠性。

  • 便捷部署:基於 Docker Compose 的容器化方案,實現開發環境的快速搭建和一致性部署。

專案架構概覽

我們將生成的專案是一個典型的微服務架構,其核心組件包括:

  • Laravel (PHP):作為強大的後端 API,負責用戶認證、文件管理以及協調與 AI 微服務的通訊。

  • Vue.js (JavaScript):作為響應式的前端介面,提供用戶上傳文件、語音互動及展示結果的功能。

  • FastAPI (Python):專門的 AI 微服務,處理諸如語音轉錄(Faster-Whisper)、文件向量化(OpenAI Embedding)、語意搜尋(Qdrant)和檢索增強生成(RAG)等 AI 核心功能。

  • MySQL & Qdrant:分別作為關係型資料庫和向量資料庫,提供數據持久化。

  • Docker Compose:用於定義和運行多容器 Docker 應用程式,簡化部署。

  • Caddy:可選的反向代理服務,用於統一各服務的訪問入口。

這種架構確保了各服務的解耦、獨立部署和可擴展性。

自動化專案啟動流程解析

為了實現專案的快速啟動和環境一致性,我們採用了一套自動化流程來生成專案骨架和初始代碼。這套流程旨在消除手動配置的繁瑣與潛在錯誤,讓開發者能迅速進入開發狀態。

自動化流程的核心功能

這套自動化流程將負責完成以下關鍵任務:

  1. 專案目錄結構建立:

    流程會自動創建 workflow-ai-platform 作為專案主目錄,並在其內部建立 backend、frontend、ai-orchestrator 等主要服務目錄,以及 data-volumes 用於數據持久化。

  2. 根目錄配置檔案生成:

    流程會生成專案級別的配置文件,包括:

    • .env.example:提供所有服務共享的環境變數範本,例如 OpenAI API Key、資料庫連接配置、AI Orchestrator 的內部 URL 等。

    • Caddyfile:配置 Caddy 作為可選的反向代理,用於統一各服務的外部訪問入口,例如將 localhost:5173 的前端服務映射到 localhost:8081

    • README.md:提供專案的概覽、快速啟動指南、API 文檔地址和測試方法等關鍵資訊。

  3. Docker Compose 配置檔案創建:

    流程會生成核心的 docker-compose.yml 檔案,詳細定義了構成整個應用程式的多個 Docker 服務,包括它們的構建上下文、端口映射、卷掛載和服務間的依賴關係。這包括了 backend (Laravel)、frontend (Vue.js)、ai-orchestrator (FastAPI)、qdrant (向量資料庫) 和 database (MySQL)。

  4. AI 微服務 (ai-orchestrator) 檔案與代碼生成:

    流程將生成 ai-orchestrator 目錄下的所有必要文件,包括:

    • app/main.py:FastAPI 應用的主入口點,定義了語音轉錄、文件上傳通知、語意搜尋和 AI 回應的 API 端點。它負責全局加載 Faster-Whisper 模型。

    • app/services/document_service.py:實現文件的向量化、Qdrant 資料庫操作和文件摘要生成邏輯。

    • app/services/rag_pipeline.py:負責構建檢索增強生成(RAG)流程,利用 LangChain 整合檢索結果和對話歷史來生成智能回應。

    • requirements.txt:列出所有 Python 依賴(如 fastapiuvicornqdrant-clientlangchainopenaifaster-whisper 等)。

    • Dockerfile:定義 ai-orchestrator 服務的 Docker 映像構建步驟。

    • tests/:包含針對核心服務的 Pytest 測試案例,這些測試會模擬外部依賴(如 OpenAI 和 Qdrant)以確保測試的獨立性。

  5. 後端 (backend) 檔案與代碼生成:

    流程會生成 backend 目錄下的 Laravel 專案結構和核心代碼:

    • composer.json:定義 PHP 依賴,包括 laravel/sanctum(用於 API 認證)和 knuckleswtf/scribe(用於自動化 API 文檔生成)。

    • app/Http/Controllers/:生成 AuthController.php (處理認證)、DocumentController.php (處理文件上傳和搜尋) 和 VoiceController.php (處理語音輸入和回應)。

    • app/Models/:定義 UserDocumentVoice 等 Eloquent 模型。

    • database/migrations/database/seeders/:用於資料庫表結構定義和測試數據填充。

    • routes/api.php:定義所有後端 API 路由。

    • Dockerfilenginx/default.confetc/supervisor/conf.d/supervisord.conf:用於後端服務的 Docker 容器化和進程管理。

    • tests/Feature/:包含 Laravel 的功能測試,覆蓋認證、文件和語音模組,並使用 Laravel 的 Http::fake() 模擬外部服務調用。

    • storage/responses/:為 Scribe API 文檔提供的範例響應文件。

  6. 前端 (frontend) 檔案與代碼生成:

    流程將生成 frontend 目錄下的 Vue.js 專案結構和核心代碼:

    • package.json:定義前端的 Node.js 依賴,包括 Vue.js、Pinia(狀態管理)、Vue Router、TailwindCSS(樣式框架)以及 Vitest(單元測試)和 Cypress(E2E 測試)。

    • src/App.vuesrc/main.js:Vue 應用程式的入口點。

    • src/router/index.js:Vue Router 配置,管理前端頁面導航和路由守衛。

    • src/stores/auth.js:使用 Pinia 管理用戶認證狀態和 API Token。

    • src/views/:包含 DocumentsView.vue (文件管理介面) 和 VoiceAssistantView.vue (語音助理介面) 等主要頁面組件。

    • cypress.config.jscypress/:Cypress 端到端測試的配置和測試腳本,用於模擬用戶在瀏覽器中的真實互動。

    • vite.config.jsDockerfile:Vite 開發伺服器配置和前端服務的 Docker 映像構建過程。

關鍵程式碼片段

本節將展示專案中各模組的關鍵程式碼片段,幫助讀者快速理解其核心邏輯。

1. 後端:Laravel 用戶認證控制器 (backend/app/Http/Controllers/AuthController.php)

此程式碼展示了用戶註冊的核心邏輯,包括數據驗證、密碼加密和 Sanctum API Token 的生成。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Log;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $request->validate([
            'name'     => 'required|string|max:255',
            'email'    => 'required|email|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ]);

        $user  = User::create([
            'name'     => $request->name,
            'email'    => $request->email,
            'password' => Hash::make($request->password),
        ]);
        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'message' => 'User registered successfully',
            'token'   => $token,
            'user'    => $user,
        ], 201);
    }
}

2. AI 微服務:FastAPI 語音轉錄 (ai-orchestrator/app/main.py)

此程式碼片段展示了 FastAPI 服務如何接收音頻文件,並使用 faster_whisper 進行語音轉錄的邏輯。

from fastapi import FastAPI, UploadFile, File, HTTPException
import logging
import io
import os
from faster_whisper import WhisperModel

logger = logging.getLogger(__name__)
app = FastAPI(title="AI Orchestrator Microservice")

WHISPER_MODEL_NAME = os.getenv("WHISPER_MODEL", "tiny")
WHISPER_DEVICE = os.getenv("WHISPER_DEVICE", "cpu")
WHISPER_COMPUTE_TYPE = os.getenv("WHISPER_COMPUTE_TYPE", "int8")
WHISPER_DOWNLOAD_ROOT = "./data/whisper_models"

whisper_model = None
try:
    whisper_model = WhisperModel(
        WHISPER_MODEL_NAME,
        device=WHISPER_DEVICE,
        compute_type=WHISPER_COMPUTE_TYPE,
        download_root=WHISPER_DOWNLOAD_ROOT
    )
    logger.info(f"Faster-Whisper model '{WHISPER_MODEL_NAME}' loaded successfully.")
except Exception as e:
    logger.error(f"載入 Faster-Whisper 模型失敗: {e}", exc_info=True)

@app.post("/voice/transcribe")
async def transcribe_voice(audio_file: UploadFile = File(...)):
    if whisper_model is None:
        raise HTTPException(status_code=503, detail="語音轉錄服務暫時不可用,模型載入失敗。")
    if audio_file.content_type not in ["audio/webm", "audio/mp3", "audio/wav", "audio/ogg"]:
         raise HTTPException(status_code=400, detail=f"不支援的音頻格式: {audio_file.content_type}")

    try:
        audio_bytes = await audio_file.read()
        audio_stream = io.BytesIO(audio_bytes)
        segments, info = whisper_model.transcribe(audio_stream, beam_size=5)
        
        transcribed_text = ""
        for segment in segments:
            transcribed_text += segment.text + " "
        
        return {"transcribed_text": transcribed_text.strip()}
    except Exception as e:
        logger.error(f"語音轉錄失敗: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=f"語音轉錄失敗: {e}")

3. 前端:Vue.js 語音助理組件 (frontend/src/views/VoiceAssistantView.vue)

此程式碼片段展示了前端如何使用 MediaRecorder 錄製語音,並將其發送到後端進行處理。

<template>
  <div class="flex flex-col items-center space-y-4">
    <button @click="toggleRecording" :class="{ 'bg-red-600': isRecording, 'bg-green-600': !isRecording }"
      :disabled="processing" class="w-32 h-32 rounded-full text-white flex items-center justify-center shadow-lg">
      <svg v-if="!isRecording" class="w-16 h-16" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
      <svg v-else class="w-16 h-16 animate-pulse" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">...</svg>
    </button>
    <p v-if="processing" class="text-indigo-600 font-semibold text-lg">AI 思考中...</p>
    <p v-else-if="isRecording" class="text-red-500 font-semibold text-lg">錄音中...</p>
    <p v-else class="text-gray-600 text-lg">點擊麥克風開始錄音</p>
    <p v-if="error" class="text-red-500 text-center mt-2">{{ error }}</p>
  </div>
  <div class="mt-8 p-6 border rounded-lg shadow-sm max-h-96 overflow-y-auto">
    <h2 class="text-2xl font-semibold text-gray-800 mb-4">對話歷史:</h2>
    <div v-for="(message, index) in conversationHistory" :key="index">
      <div v-if="message.speaker === 'user'" class="text-right">
        <span class="inline-block bg-blue-100 rounded-lg px-4 py-2">{{ message.text }}</span>
      </div>
      <div v-else class="text-left">
        <span class="inline-block bg-green-100 rounded-lg px-4 py-2">{{ message.text }}</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import axios from 'axios';
import { useAuthStore } from '../stores/auth';

const authStore = useAuthStore();
const API_URL = import.meta.env.VITE_API_URL;

const isRecording = ref(false);
const processing = ref(false);
const mediaRecorder = ref(null);
const audioChunks = ref([]);
const error = ref(null);
const conversationHistory = ref([]);
const currentUserId = ref(localStorage.getItem('userId') || 'guest_user');

onMounted(async () => {
  await fetchConversationHistory();
});

onUnmounted(() => {
  if (isRecording.value && mediaRecorder.value) {
    mediaRecorder.value.stop();
    isRecording.value = false;
  }
});

const fetchConversationHistory = async () => { /* ... fetch logic ... */ };

const toggleRecording = async () => {
  if (processing.value) return;
  if (!isRecording.value) {
    error.value = null;
    audioChunks.value = [];
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      mediaRecorder.value = new MediaRecorder(stream, { mimeType: 'audio/webm' });

      mediaRecorder.value.ondataavailable = (event) => { audioChunks.value.push(event.data); };
      mediaRecorder.value.onstop = async () => {
        processing.value = true;
        isRecording.value = false;
        const audioBlob = new Blob(audioChunks.value, { type: 'audio/webm' });
        await sendAudioToBackend(audioBlob);
        stream.getTracks().forEach(track => track.stop());
        processing.value = false;
      };
      mediaRecorder.value.start();
      isRecording.value = true;
    } catch (err) {
      error.value = '無法獲取麥克風權限或開始錄音。';
    }
  } else {
    if (mediaRecorder.value && mediaRecorder.value.state === 'recording') {
      mediaRecorder.value.stop();
    }
  }
};

const sendAudioToBackend = async (audioBlob) => { /* ... send logic ... */ };
</script>

<style scoped>
button { transition: all 0.2s ease-in-out; }
button:hover { transform: scale(1.05); }
button:disabled { opacity: 0.7; cursor: not-allowed; }
</style>

啟動專案的步驟

自動化流程執行完畢後,啟動整個專案變得非常簡單:

  1. 複製環境變數範本:將專案根目錄下的 .env.example 檔案複製為 .env,然後開啟 .env 檔案,務必填寫您的 OPENAI_API_KEY

  2. 構建並啟動所有服務:在專案根目錄執行 docker compose builddocker compose up -d。Docker Compose 將根據 docker-compose.yml 定義自動構建映像並啟動所有服務。

  3. Laravel 後端初始化:透過 Docker 命令進入 backend 容器內部,執行 Laravel 的初始化命令,包括 php artisan key:generate (生成應用程式金鑰)、php artisan migrate (執行資料庫遷移)、php artisan db:seed (可選,填充假數據) 和 php artisan scribe:generate (生成 API 文檔)。

  4. 訪問應用程式:一旦所有服務啟動並初始化完成,您可以透過瀏覽器訪問前端應用程式(預設 http://localhost:5173),以及後端 API 文檔(Scribe 於 http://localhost:8000/docs)和 AI 微服務 API 文檔(Swagger UI 於 http://localhost:8001/docs)。

  5. Caddy 反向代理 (可選):如果您希望透過 Caddy 進行統一的服務訪問,請確保已安裝 Caddy,然後在專案根目錄運行 caddy run --config Caddyfile

測試策略

本專案實施了多層次的測試策略,以確保各個模組的功能正確性和系統的穩定性:

  • 前端單元測試 (Vitest):針對 Vue 組件、Pinia Store 和其他前端邏輯單元進行快速、隔離的測試,驗證每個小型組件或功能的行為是否符合預期。

  • 前端端到端測試 (Cypress E2E):模擬真實用戶在瀏覽器中的操作流程,從用戶界面層面驗證整個應用程式的完整功能,包括用戶認證、文件上傳、語音互動等關鍵流程。這些測試會與後端服務進行實際交互。

  • 後端測試 (PHPUnit):針對 Laravel 控制器、API 端點和服務邏輯進行功能測試和單元測試,確保 API 的正確性、數據處理邏輯以及與外部服務(如 AI Orchestrator)的集成。測試中會利用 Laravel 的 Http::fake() 來模擬外部 API 的響應,以保證測試的獨立性。

  • AI 微服務測試 (Pytest):針對 FastAPI 服務中的 AI 邏輯、模型加載和第三方集成(如 Qdrant、OpenAI)進行單元測試和集成測試,確保 AI 功能的準確性和穩定性。

所有這些測試都被設計為可以集成到 CI/CD 流程中,實現自動化測試,從而確保每次代碼提交都能得到充分驗證。

開發筆記與注意事項

  • OpenAI API Key:這是整個 AI 功能的核心。請務必確保在 .env 檔案中正確設定了您的 OPENAI_API_KEY,否則所有依賴 OpenAI 服務的 AI 功能將無法正常運作。

  • Faster-Whisper 模型:AI Orchestrator 會在首次運行 faster-whisper 時自動下載語音轉錄模型。您可以透過修改根目錄 .env 中的 WHISPER_MODEL 參數,來選擇不同大小的模型(如 tiny, base, small, medium, large-v2, large-v3),以平衡性能和準確度。同時,WHISPER_DEVICEWHISPER_COMPUTE_TYPE 參數可用於優化 CPU 或 GPU 環境下的模型運行。

  • Laravel Sanctum 與 CORS 配置:為了確保前端應用程式與 Laravel 後端之間的認證和跨域請求正常工作,.env.example 中已預配置 SANCTUM_STATEFUL_DOMAINSSESSION_DOMAIN。請確保這些設定與您的前端應用程式的實際網址相符。

  • 前端路由保護:Vue.js 前端應用中的敏感路由(例如 /documents/voice)已設置了路由守衛。這表示只有經過認證(即已登入)的用戶才能訪問這些頁面,未登入的用戶將被自動重定向到登入頁面,保障應用程式的安全性。

  • 日誌記錄:專案中的各個微服務都配置了詳細的日誌記錄機制。這對於開發過程中的除錯、監控應用程式運行狀態以及在生產環境中排查問題至關重要。

結論

透過這種基於自動化流程的專案生成方法,我們能夠以前所未有的效率和一致性,快速建立一個由多個現代技術棧組成的複雜全端 AI 應用專案。這種方法不僅顯著減少了手動配置帶來的錯誤和時間成本,更重要的是,它為開發團隊提供了一個標準化、可重複的起點,使得開發者能夠將更多的精力投入到核心業務邏輯的創新和 AI 功能的優化上,而非重複性的環境搭建工作。這不僅極大地提升了開發效率,也為專案的長期可維護性、可擴展性和安全性奠定了堅實的基礎。

沒有留言:

張貼留言

熱門文章