Laravel 13 构建 AI 驱动应用实战 摘要 本文将通过一个完整的实战案例,演示如何使用 Laravel 13 构建 AI 驱动的智能客服系统。涵盖内容包括:
项目架构设计 Laravel AI SDK 集成 知识库与 RAG 实现 多轮对话管理 前端界面开发 部署与优化 本文适合希望构建 AI 应用的 Laravel 开发者。
1. 项目概述 1.1 功能需求 智能问答:基于知识库回答问题 多轮对话:保持上下文记忆 工具调用:查询订单、用户信息等 实时响应:流式输出答案 管理后台:知识库管理 1.2 技术栈 Laravel 13 Laravel AI SDK PostgreSQL + pgvector Livewire 4 Tailwind CSS 4 2. 项目初始化 2.1 创建项目 1 2 composer create-project laravel/laravel ai-customer-service cd ai-customer-service
2.2 安装依赖 1 2 composer require laravel/ai composer require livewire/livewire
2.3 配置环境 1 2 3 4 5 6 7 8 9 10 # .env AI_PROVIDER=openai OPENAI_API_KEY=sk-... DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=ai_customer_service DB_USERNAME=postgres DB_PASSWORD=secret
3. 数据库设计 3.1 知识库表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php use Illuminate \Database \Migrations \Migration ;use Illuminate \Database \Schema \Blueprint ;use Illuminate \Support \Facades \Schema ;use Illuminate \Support \Facades \DB ;return new class extends Migration { public function up ( ): void { DB::statement ('CREATE EXTENSION IF NOT EXISTS vector' ); Schema ::create ('knowledge_items' , function (Blueprint $table ) { $table ->id (); $table ->string ('title' ); $table ->text ('content' ); $table ->string ('category' ); $table ->vector ('embedding' , 1536 ); $table ->timestamps (); }); DB::statement ('CREATE INDEX knowledge_items_embedding_idx ON knowledge_items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)' ); } };
3.2 对话表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Schema ::create ('conversations' , function (Blueprint $table ) { $table ->id (); $table ->foreignId ('user_id' )->nullable (); $table ->string ('session_id' ); $table ->timestamps (); }); Schema ::create ('messages' , function (Blueprint $table ) { $table ->id (); $table ->foreignId ('conversation_id' ); $table ->enum ('role ', ['user ', 'assistant ']); $table ->text ('content '); $table ->timestamps (); });
4. AI 代理实现 4.1 创建客服代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php namespace App \Ai \Agents ;use Laravel \Ai \Agent ;use Laravel \Ai \Tools \SimilaritySearch ;class CustomerServiceAgent extends Agent { protected string $name = 'Customer Service Assistant' ; protected string $instructions = <<<INSTRUCTIONS You are a helpful customer service assistant. Your role is to: - Answer questions based on the knowledge base - Help customers with their orders - Provide product information - Escalate complex issues to human agents Guidelines: - Be friendly and professional - Use the knowledge base for accurate information - If unsure, admit and offer to connect with a human agent INSTRUCTIONS ; protected string $model = 'gpt-4' ; protected int $maxTokens = 1000 ; public function tools ( ): array { return [ SimilaritySearch ::make () ->table ('knowledge_items' ) ->embeddingColumn ('embedding' ) ->contentColumn ('content' ) ->description ('Search knowledge base for relevant information' ), ]; } }
4.2 工具定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php namespace App \Ai \Tools ;use Laravel \Ai \Tool ;use App \Models \Order ;class GetOrderInfo extends Tool { public function name ( ): string { return 'get_order_info' ; } public function description ( ): string { return 'Get order information by order number' ; } public function parameters ( ): array { return [ 'order_number' => [ 'type' => 'string' , 'description' => 'Order number' , 'required' => true , ], ]; } public function execute (array $arguments ): array { $order = Order ::where ('order_number' , $arguments ['order_number' ])->first (); if (!$order ) { return ['error' => 'Order not found' ]; } return [ 'order_number' => $order ->order_number, 'status' => $order ->status, 'total' => $order ->total, 'items' => $order ->items->map (fn($item ) => [ 'name' => $item ->product_name, 'quantity' => $item ->quantity, 'price' => $item ->price, ]), ]; } }
5. 控制器实现 5.1 对话控制器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <?php namespace App \Http \Controllers ;use App \Ai \Agents \CustomerServiceAgent ;use App \Models \Conversation ;use App \Models \Message ;use Illuminate \Http \Request ;class ChatController extends Controller { public function chat (Request $request ) { $validated = $request ->validate ([ 'message' => 'required|string' , 'session_id' => 'required|string' , ]); $conversation = Conversation ::firstOrCreate ( ['session_id' => $validated ['session_id' ]], ['user_id' => auth ()->id ()] ); $conversation ->messages ()->create ([ 'role' => 'user' , 'content' => $validated ['message' ], ]); $history = $conversation ->messages () ->latest () ->take (10 ) ->get () ->reverse () ->map (fn($m ) => [ 'role' => $m ->role, 'content' => $m ->content, ]) ->toArray (); $response = CustomerServiceAgent ::make () ->withHistory ($history ) ->prompt ($validated ['message' ]); $conversation ->messages ()->create ([ 'role' => 'assistant' , 'content' => (string ) $response , ]); return response ()->json ([ 'message' => (string ) $response , 'tool_calls' => $response ->toolCalls (), ]); } public function stream (Request $request ) { $validated = $request ->validate ([ 'message' => 'required|string' , 'session_id' => 'required|string' , ]); return response ()->stream (function () use ($validated ) { foreach (CustomerServiceAgent ::make ()->stream ($validated ['message ']) as $chunk ) { echo json_encode (['chunk ' => $chunk ]) . "\n "; flush (); } }); } }
6. Livewire 组件 6.1 聊天组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 <?php namespace App \Http \Livewire ;use Livewire \Component ;use App \Ai \Agents \CustomerServiceAgent ;use App \Models \Conversation ;use App \Models \Message ;class ChatAssistant extends Component { public string $message = '' ; public array $messages = []; public string $sessionId ; public bool $typing = false ; public function mount ( ) { $this ->sessionId = session ()->getId (); $this ->loadMessages (); } public function loadMessages ( ) { $conversation = Conversation ::where ('session_id' , $this ->sessionId)->first (); if ($conversation ) { $this ->messages = $conversation ->messages () ->orderBy ('created_at' ) ->get () ->map (fn($m ) => [ 'role' => $m ->role, 'content' => $m ->content, ]) ->toArray (); } } public function send ( ) { if (empty ($this ->message)) { return ; } $this ->messages[] = [ 'role' => 'user' , 'content' => $this ->message, ]; $userMessage = $this ->message; $this ->message = '' ; $this ->typing = true ; $this ->stream ('messages' , function () use ($userMessage ) { $response = ''; foreach (CustomerServiceAgent ::make ()->stream ($userMessage ) as $chunk ) { $response .= $chunk ; yield array_merge ($this ->messages, [ ['role' => 'assistant' , 'content' => $response ], ]); } $this ->saveConversation ($userMessage , $response ); }); $this ->typing = false ; } private function saveConversation (string $userMessage , string $assistantMessage ) { $conversation = Conversation ::firstOrCreate ( ['session_id' => $this ->sessionId] ); $conversation ->messages ()->createMany ([ ['role' => 'user' , 'content' => $userMessage ], ['role' => 'assistant' , 'content' => $assistantMessage ], ]); } public function render ( ) { return view ('livewire.chat-assistant' ); } }
6.2 视图模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <div class="chat-container h-screen flex flex-col bg-gray-50"> <div class="header bg-blue-600 text-white p-4"> <h1 class="text-xl font-bold">Customer Service</h1> </div> <div class="messages flex-1 overflow-y-auto p-4 space-y-4"> @foreach($messages as $msg) <div class="flex {{ $msg['role'] === 'user' ? 'justify-end' : 'justify-start' }}"> <div class="max-w-md p-3 rounded-lg {{ $msg['role'] === 'user' ? 'bg-blue-600 text-white' : 'bg-white shadow' }}"> {{ $msg['content'] }} </div> </div> @endforeach @if($typing) <div class="flex justify-start"> <div class="bg-white shadow p-3 rounded-lg"> <span class="animate-pulse">Typing...</span> </div> </div> @endif </div> <div class="input-area p-4 bg-white border-t"> <form wire:submit.prevent="send" class="flex gap-2"> <input type="text" wire:model="message" placeholder="Type your message..." class="flex-1 border rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" {{ $typing ? 'disabled' : '' }} > <button type="submit" class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50" {{ $typing ? 'disabled' : '' }} > Send </button> </form> </div> </div>
7. 知识库管理 7.1 知识库模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php namespace App \Models ;use Illuminate \Database \Eloquent \Model ;use Laravel \Ai \Embeddings ;class KnowledgeItem extends Model { protected $fillable = ['title' , 'content' , 'category' , 'embedding' ]; protected $casts = [ 'embedding' => 'vector' , ]; protected static function booted ( ): void { static ::creating (function (self $item ) { if (empty ($item ->embedding)) { $item ->embedding = Embeddings ::from ($item ->content)->generate ()->vector; } }); static ::updating (function (self $item ) { if ($item ->isDirty ('content' )) { $item ->embedding = Embeddings ::from ($item ->content)->generate ()->vector; } }); } }
7.2 知识库导入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php namespace App \Services ;use App \Models \KnowledgeItem ;use Laravel \Ai \Embeddings ;class KnowledgeImportService { public function import (array $items ): void { $texts = array_column ($items , 'content' ); $embeddings = Embeddings ::batch ($texts )->generate (); foreach ($items as $index => $item ) { KnowledgeItem ::create ([ 'title' => $item ['title' ], 'content' => $item ['content' ], 'category' => $item ['category' ] ?? 'general' , 'embedding' => $embeddings [$index ]->vector, ]); } } }
8. 部署配置 8.1 队列配置 1 2 3 4 5 6 7 8 9 10 'connections' => [ 'redis' => [ 'driver' => 'redis' , 'connection' => 'default' , 'queue' => env ('REDIS_QUEUE' , 'default' ), 'retry_after' => 90 , 'block_for' => null , ], ],
8.2 Horizon 配置 1 2 composer require laravel/horizon php artisan horizon:install
8.3 监控配置 1 2 3 4 5 6 7 8 9 10 'environments' => [ 'production' => [ 'supervisor-1' => [ 'maxProcesses' => 10 , 'balanceMaxShift' => 1 , 'balanceCooldown' => 3 , ], ], ],
9. 总结 通过本实战案例,我们构建了一个完整的 AI 驱动智能客服系统:
Laravel AI SDK :简化 AI 功能集成pgvector :高效的向量搜索RAG :基于知识库的智能问答Livewire :实时流式响应队列 :异步任务处理这个项目展示了 Laravel 13 在 AI 应用开发中的强大能力。
参考资料