实现 Laravel(主程序)与 Python(大模型交互)的网页实时聊天功能
你需要实现Laravel(主程序)+ Python(大模型交互)的网页实时聊天功能,核心诉求是前端和Laravel交互后,由Python调用大模型并将结果实时推送给前端,核心难点是Python与Laravel之间的实时通信和前端的消息实时接收。
结合PHP/Laravel的生态特点、Python的大模型处理优势,以及网页聊天的实时性、易用性、可维护性要求,推荐一套生产级落地方案:前端WebSocket → Laravel WebSocket服务 → 进程间通信(Laravel→Python) → Python调用大模型 → 反向推送(Python→Laravel) → WebSocket推送给前端,这套方案适配流式输出(大模型逐字返回)和普通一次性返回,是当前该场景的最优解。
核心设计原则
前端实时性:用WebSocket替代轮询,实现消息的实时推送(Laravel生态有成熟的WebSocket组件,无需手写);
语言解耦:Laravel和Python各司其职,Laravel负责前端交互、会话管理、用户鉴权,Python负责大模型调用、流式处理、结果解析,两者通过轻量进程间通信交互,避免语言耦合;
流式友好:适配大模型流式输出(逐字返回),让前端有“打字机”效果,提升交互体验;
易维护性:基于Laravel和Python的成熟生态组件开发,减少手写底层代码,降低维护成本;
可扩展性:后续可轻松扩展多大模型支持、消息记录、限流等功能。
一、整体架构设计(核心流程)
先明确全链路角色和数据流向,从前端发起聊天到最终收到AI回复,共7个核心步骤,适配流式输出(大模型主流方式)和一次性输出:
核心角色
前端:Vue/React/Blade 聊天页面,通过WebSocket和Laravel建立长连接,发送用户消息、接收AI流式回复;
Laravel:① 提供WebSocket服务(鉴权、会话管理);② 接收前端消息后转发给Python;③ 接收Python的流式结果并推送给前端;④ 负责聊天记录存储、用户鉴权等业务逻辑;
Python:① 接收Laravel的消息指令;② 调用大模型(OpenAI/文心一言/通义千问等,支持流式调用);③ 将大模型的流式结果实时回传给Laravel;
通信中间件:Laravel和Python之间用Redis Pub/Sub做进程间通信(轻量、高效、适配流式,Laravel和Python都有成熟的Redis客户端,落地成本最低)。
全链路核心流程(流式输出)
1. 前端 → WebSocket → Laravel WebSocket服务:发送用户聊天消息(带用户ID/会话ID),并完成鉴权;
2. Laravel → Redis Pub/Sub(指定频道):将用户消息、用户ID、会话ID封装为指令,发布到Redis专属频道;
3. Python → 监听Redis该频道:实时消费Laravel发布的指令,解析出用户消息;
4. Python → 大模型API:**流式调用**大模型(如OpenAI的stream=True),获取逐字的AI回复;
5. Python → Redis Pub/Sub(用户/会话专属频道):将大模型的流式结果(单字/短句)实时发布到Redis;
6. Laravel → 监听Redis专属频道:消费Python的流式结果,关联到对应的前端WebSocket连接;
7. Laravel → WebSocket → 前端:将流式结果实时推送给前端,实现“打字机”效果。
关键设计亮点
Redis频道分片:为每个用户/会话创建专属Redis频道,避免消息广播风暴,保证消息精准推送;
流式端到端:从Python调用大模型到前端展示,全程流式传输,无中间缓存,延迟最低;
完全解耦:Laravel和Python仅通过Redis通信,无直接进程依赖,可独立部署、扩容、升级(如Python服务挂了,不影响Laravel主程序);
鉴权统一:所有前端请求的鉴权都在Laravel层完成,Python无需处理用户鉴权,专注大模型交互。
二、技术栈选型(Laravel+Python生态最优解)
结合Laravel的成熟组件和Python的大模型生态,选择开箱即用、低开发成本的技术栈,避免手写底层代码:
1. 前端层
核心:WebSocket客户端(原生WebSocket/axios-websocket/socket.io-client);
效果:实现聊天消息的发送、流式接收、“打字机”效果渲染;
适配:无论前端是Laravel Blade、Vue、React,都能快速集成。
2. Laravel层(核心组件)
| 功能需求 | 选型组件 | 核心优势 |
|---|---|---|
| WebSocket服务 | Laravel WebSockets | Laravel官方生态,支持Pusher协议,内置鉴权、会话管理、消息广播,无需手写WebSocket服务; |
| Redis客户端 | Laravel原生Redis Facade | 开箱即用,支持Pub/Sub、String等所有Redis操作,和Laravel配置无缝集成; |
| 消息广播 | Laravel Broadcast | 配合Laravel WebSockets,实现WebSocket消息的精准推送,支持按用户/频道广播; |
| 聊天记录存储 | Laravel Eloquent ORM | 快速实现消息的增删改查,支持关联用户/会话,可对接MySQL/PostgreSQL; |
| 鉴权 | Laravel Passport/Sanctum | 统一的用户鉴权,WebSocket连接时验证用户身份,避免非法连接; |
3. Python层(核心库)
| 功能需求 | 选型库 | 核心优势 |
|---|---|---|
| Redis客户端 | redis-py(v4+) | 支持异步IO(asyncio),适配高并发,支持Redis Pub/Sub的流式监听; |
| 大模型调用 | 各厂商官方SDK/OpenAI SDK | 如openai(支持流式)、dashscope(通义千问)、erniebot(文心一言),开箱即用; |
| 异步IO | asyncio | 实现Python的异步监听Redis、异步调用大模型,提升并发处理能力(支持上万并发会话); |
| 消息序列化 | json/msgpack | 轻量的消息序列化,适配Laravel和Python之间的指令解析; |
4. 中间件
Redis:5.x+,核心用于Laravel和Python的Pub/Sub通信,推荐主从+哨兵做高可用(生产环境),单机即可满足开发/测试需求;
作用:进程间通信、临时消息中转,无需持久化(流式消息无需持久化,聊天记录由Laravel存储)。
三、分步落地实现(从开发到测试,可直接照搬)
按前端 → Laravel → Redis → Python的顺序落地,每一步都提供核心代码/配置,确保开箱即用,先实现基础流式聊天功能,再补充聊天记录、鉴权等业务逻辑。
前置准备
搭建Laravel项目(推荐9.x+,兼容Laravel WebSockets),配置好Redis(
.env文件中设置REDIS_HOST/REDIS_PORT);搭建Python环境(3.9+),安装核心依赖:
pip install redis openai python-dotenv(openai可替换为其他大模型SDK);启动Redis服务,确保Laravel和Python都能正常连接。
第一步:Laravel端开发(核心:WebSocket服务+Redis通信)
Laravel端是承上启下的核心,负责前端WebSocket连接、Redis消息发布/消费、消息推送给前端,分4个核心步骤:
步骤1:安装并配置Laravel WebSockets(WebSocket服务)
Laravel WebSockets是替代Pusher的开源组件,无需依赖第三方Pusher服务,可本地部署,步骤如下:
1.1 安装组件
# 安装Laravel WebSockets和Pusher依赖(Laravel Broadcast依赖Pusher协议)
composer require beyondcode/laravel-websockets pusher/pusher-php-server "^7.0"
1.2 配置广播驱动和Pusher
修改Laravel根目录的.env文件,设置广播驱动为pusher,并配置Pusher(无需真实Pusher账号,本地Laravel WebSockets模拟):
# 广播驱动改为pusher
BROADCAST_DRIVER=pusher
# Pusher配置(随意填写,Laravel WebSockets会拦截)
PUSHER_APP_ID=laravel-chat
PUSHER_APP_KEY=laravel-chat-key
PUSHER_APP_SECRET=laravel-chat-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1
# Redis配置(确保和本地Redis一致)
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
修改config/broadcasting.php,确保pusher配置和.env一致:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST', 'api.pusherapp.com'),
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME') === 'https',
],
],
修改config/app.php,开启BroadcastServiceProvider(取消注释):
App\Providers\BroadcastServiceProvider::class,
1.3 发布配置并迁移数据库
# 发布Laravel WebSockets配置文件
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
# 迁移数据库(Laravel WebSockets用于存储WebSocket连接日志,可选)
php artisan migrate
步骤2:创建广播频道(前端和Laravel的消息通道)
创建按用户划分的广播频道,确保每个用户的消息仅推送给自己,步骤如下:
2.1 创建频道类
# 创建ChatChannel频道类,用于聊天消息的广播
php artisan make:channel ChatChannel
修改生成的app/Channels/ChatChannel.php,实现鉴权逻辑(仅允许登录用户访问自己的频道):
<?php
namespace App\Channels;
use App\Models\User;
use Illuminate\Broadcasting\PrivateChannel;
class ChatChannel
{
/**
* 验证用户是否有权访问该频道
*/
public function join(User $user)
{
// 私有频道:仅当前登录用户可访问,频道名格式:chat.{user.id}
return new PrivateChannel('chat.' . $user->id);
}
}
2.2 注册广播路由
修改routes/channels.php,注册ChatChannel频道:
<?php
use Illuminate\Support\Facades\Broadcast;
use App\Channels\ChatChannel;
// 聊天消息私有频道,关联ChatChannel鉴权
Broadcast::channel('chat.{userId}', ChatChannel::class);
步骤3:创建消息模型和控制器(业务逻辑+Redis通信)
3.1 创建聊天消息模型(可选,用于存储聊天记录)
php artisan make:model ChatMessage -m
修改迁移文件database/migrations/xxxx_create_chat_messages_table.php,设计消息表结构:
public function up()
{
Schema::create('chat_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade'); // 关联用户
$table->text('content'); // 消息内容
$table->enum('type', ['user', 'ai']); // 消息类型:用户/AI
$table->string('session_id')->default('default'); // 会话ID,支持多会话
$table->timestamps();
});
}
执行迁移:php artisan migrate,并在App/Models/ChatMessage.php中开启批量赋值:
protected $fillable = ['user_id', 'content', 'type', 'session_id'];
3.2 创建ChatController(核心:接收前端消息→发布到Redis→消费Redis→广播给前端)
php artisan make:controller ChatController
修改app/Http/Controllers/ChatController.php,实现核心逻辑:
<?php
namespace App\Http\Controllers;
use App\Models\ChatMessage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Contracts\Redis\Factory as RedisFactory;
class ChatController extends Controller
{
// Redis客户端实例
protected $redis;
// Redis频道前缀:Laravel→Python的指令频道
const REDIS_LARAVEL_TO_PYTHON = 'laravel:ai:command';
// Redis频道前缀:Python→Laravel的流式结果频道
const REDIS_PYTHON_TO_LARAVEL = 'python:ai:result:';
public function __construct(RedisFactory $redis)
{
// 中间件:确保所有操作都需要登录
$this->middleware('auth');
$this->redis = $redis->connection();
}
/**
* 前端发送消息:接收并发布到Redis,同时存储用户消息记录
*/
public function sendMessage(Request $request)
{
// 验证请求参数
$validated = $request->validate([
'content' => 'required|string',
'session_id' => 'nullable|string|default:default',
]);
$user = $request->user();
$sessionId = $validated['session_id'] ?? 'default';
$content = $validated['content'];
// 1. 存储用户消息到数据库
ChatMessage::create([
'user_id' => $user->id,
'content' => $content,
'type' => 'user',
'session_id' => $sessionId,
]);
// 2. 封装指令:发送给Python的参数(用户ID、会话ID、消息内容、结果回传频道)
$command = [
'user_id' => $user->id,
'session_id' => $sessionId,
'content' => $content,
'result_channel' => self::REDIS_PYTHON_TO_LARAVEL . $user->id . ':' . $sessionId, // 专属结果频道
];
// 3. 发布指令到Redis频道(Laravel→Python)
$this->redis->publish(self::REDIS_LARAVEL_TO_PYTHON, json_encode($command));
// 4. 启动协程:监听Python的流式结果频道,实时推送给前端
$this->listenPythonResult($command['result_channel'], $user->id, $sessionId);
return response()->json([
'status' => 'success',
'message' => '消息已发送,正在等待AI回复',
]);
}
/**
* 监听Python的Redis流式结果,实时广播给前端
*/
protected function listenPythonResult(string $resultChannel, int $userId, string $sessionId)
{
// 启动Laravel协程(避免阻塞主进程)
dispatch(function () use ($resultChannel, $userId, $sessionId) {
// 订阅Redis结果频道
$this->redis->subscribe([$resultChannel], function ($message) use ($resultChannel, $userId, $sessionId) {
// 解析Python返回的流式结果
$result = json_decode($message, true);
if (!isset($result['content']) || $result['content'] === '') {
return;
}
// 1. 实时广播给前端(推送到该用户的WebSocket频道)
Broadcast::toOthers()->channel('chat.' . $userId, function () use ($result, $sessionId) {
return [
'type' => 'ai',
'content' => $result['content'],
'session_id' => $sessionId,
'timestamp' => now()->toDateTimeString(),
];
});
// 2. 可选:判断是否为最终结果,若是则存储到数据库(Python返回is_finish=true标识)
if (isset($result['is_finish']) && $result['is_finish']) {
ChatMessage::create([
'user_id' => $userId,
'content' => $result['full_content'] ?? $result['content'],
'type' => 'ai',
'session_id' => $sessionId,
]);
// 取消订阅(流式结束,释放资源)
$this->redis->unsubscribe([$resultChannel]);
}
});
});
}
/**
* 获取用户的聊天记录(可选,前端初始化时加载)
*/
public function getChatHistory(Request $request)
{
$user = $request->user();
$sessionId = $request->query('session_id', 'default');
$messages = ChatMessage::where('user_id', $user->id)
->where('session_id', $sessionId)
->orderBy('created_at', 'asc')
->get();
return response()->json([
'status' => 'success',
'data' => $messages,
]);
}
}
步骤4:注册路由并启动Laravel服务
4.1 注册API路由(前端调用)
修改routes/api.php,注册聊天相关路由:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ChatController;
// 聊天相关路由,需登录
Route::middleware('auth:sanctum')->group(function () {
Route::post('chat/send', [ChatController::class, 'sendMessage']);
Route::get('chat/history', [ChatController::class, 'getChatHistory']);
});
// 广播路由(Laravel WebSockets需要)
Route::post('/broadcasting/auth', function (Illuminate\Http\Request $request) {
return Broadcast::auth($request);
})->middleware('auth:sanctum');
4.2 启动Laravel服务和WebSocket服务
# 启动Laravel HTTP服务(默认8000端口)
php artisan serve
# 新开终端,启动Laravel WebSocket服务(默认6001端口)
php artisan websockets:serve
第二步:Python端开发(核心:监听Redis→调用大模型→流式推送给Redis)
Python端专注大模型交互,无需处理业务逻辑,采用异步IO实现高并发(支持上万会话同时处理),核心是监听Laravel的Redis指令频道,流式调用大模型后将结果发布到专属Redis结果频道,代码兼容OpenAI流式API(其他大模型仅需修改调用部分)。
步骤1:创建Python项目文件
创建ai_chat.py(主程序)和.env(配置文件),目录结构:
python_ai/
├── ai_chat.py
└── .env
步骤2:配置.env文件(大模型密钥+Redis配置)
# Redis配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=
# 大模型配置(以OpenAI为例,可替换为其他大模型)
OPENAI_API_KEY=你的OpenAI密钥
OPENAI_MODEL=gpt-3.5-turbo
OPENAI_BASE_URL=https://api.openai.com/v1 # 国内可替换为代理地址
步骤3:编写ai_chat.py核心代码(异步Redis+流式大模型调用)
import json
import os
import asyncio
import redis.asyncio as redis
from openai import AsyncOpenAI
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 初始化Redis异步客户端
redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "127.0.0.1"),
port=int(os.getenv("REDIS_PORT", 6379)),
db=int(os.getenv("REDIS_DB", 0)),
password=os.getenv("REDIS_PASSWORD", ""),
decode_responses=True # 自动解码为字符串,避免字节处理
)
# 初始化OpenAI异步客户端(流式调用必须用异步)
openai_client = AsyncOpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL")
)
# Redis频道:Laravel→Python的指令频道(和Laravel中定义的一致)
REDIS_LARAVEL_TO_PYTHON = "laravel:ai:command"
async def call_ai_stream(prompt: str, result_channel: str):
"""
流式调用大模型,将结果实时发布到Redis结果频道
:param prompt: 用户的聊天消息
:param result_channel: Python→Laravel的专属结果频道
"""
full_content = "" # 存储完整的AI回复,用于最终存储
try:
# 流式调用OpenAI大模型(核心:stream=True)
response = await openai_client.chat.completions.create(
model=os.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
messages=[{"role": "user", "content": prompt}],
stream=True, # 开启流式输出
temperature=0.7,
max_tokens=1024
)
# 遍历流式结果,逐字处理
async for chunk in response:
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
full_content += content
# 封装流式结果,发布到Redis
await redis_client.publish(
channel=result_channel,
message=json.dumps({
"content": content, # 单字/短句,前端打字机效果
"is_finish": False, # 标识是否为最终结果
"full_content": "" # 暂不返回完整内容
})
)
# 可选:控制输出速度,模拟真人打字(如50ms/字)
await asyncio.sleep(0.05)
# 流式结束,发布最终结果(标识is_finish=true,携带完整内容)
await redis_client.publish(
channel=result_channel,
message=json.dumps({
"content": "",
"is_finish": True,
"full_content": full_content
})
)
print(f"AI回复完成,推送到频道:{result_channel},内容:{full_content[:50]}...")
except Exception as e:
# 异常处理,发布错误信息到Redis
await redis_client.publish(
channel=result_channel,
message=json.dumps({
"content": f"AI回复失败:{str(e)}",
"is_finish": True,
"full_content": f"AI回复失败:{str(e)}"
})
)
print(f"大模型调用失败:{e}")
async def listen_laravel_command():
"""
持续监听Laravel发布的Redis指令频道,处理用户消息
"""
pubsub = redis_client.pubsub()
# 订阅Laravel→Python的指令频道
await pubsub.subscribe(REDIS_LARAVEL_TO_PYTHON)
print(f"Python服务已启动,正在监听Redis频道:{REDIS_LARAVEL_TO_PYTHON}")
# 异步遍历订阅消息
async for message in pubsub.listen():
# 过滤非消息类型(如订阅成功的通知)
if message["type"] != "message":
continue
# 解析Laravel发布的指令
try:
command = json.loads(message["data"])
user_id = command.get("user_id")
session_id = command.get("session_id")
content = command.get("content")
result_channel = command.get("result_channel")
# 验证参数
if not all([user_id, session_id, content, result_channel]):
print(f"指令参数缺失:{command}")
continue
print(f"收到用户{user_id}的消息:{content[:20]}...,结果频道:{result_channel}")
# 启动异步任务:调用大模型并流式推送结果(非阻塞,支持高并发)
asyncio.create_task(call_ai_stream(prompt=content, result_channel=result_channel))
except json.JSONDecodeError as e:
print(f"指令解析失败:{e},原始数据:{message['data']}")
except Exception as e:
print(f"处理指令失败:{e}")
if __name__ == "__main__":
# 启动Python服务:持续监听Redis指令
asyncio.run(listen_laravel_command())
步骤4:启动Python服务
# 进入python_ai目录
cd python_ai
# 启动Python异步服务
python ai_chat.py
启动成功后会打印:Python服务已启动,正在监听Redis频道:laravel:ai:command,表示Python已准备好接收Laravel的指令。
第三步:前端开发(核心:WebSocket连接+流式消息渲染)
前端核心是建立WebSocket连接(对接Laravel WebSockets)、发送用户消息(调用Laravel API)、接收流式AI回复(WebSocket)、实现打字机效果,以下提供通用前端代码(原生JS+HTML),可直接嵌入Laravel Blade,也可适配Vue/React。
核心前端代码(chat.blade.php)
创建resources/views/chat.blade.php,实现聊天页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI聊天</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
.chat-container { width: 800px; margin: 50px auto; border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
.chat-messages { height: 600px; padding: 20px; overflow-y: auto; background: #f9f9f9; }
.message { margin: 10px 0; padding: 10px 15px; border-radius: 8px; max-width: 70%; }
.user-message { background: #2d8cf0; color: white; margin-left: auto; }
.ai-message { background: #fff; color: #333; border: 1px solid #eee; }
.chat-input { display: flex; padding: 20px; border-top: 1px solid #eee; }
#message-input { flex: 1; padding: 10px 15px; border: 1px solid #eee; border-radius: 4px; outline: none; }
#send-btn { margin-left: 10px; padding: 10px 30px; background: #2d8cf0; color: white; border: none; border-radius: 4px; cursor: pointer; }
#send-btn:disabled { background: #ccc; cursor: not-allowed; }
</style>
<!-- 引入Pusher JS(Laravel WebSockets兼容Pusher协议) -->
<script src="https://js.pusher.com/8.2.0/pusher.min.js"></script>
<!-- 引入Axios(用于调用Laravel API) -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div class="chat-container">
<div class="chat-messages" id="chat-messages"></div>
<div class="chat-input">
<input type="text" id="message-input" placeholder="请输入消息..." autocomplete="off">
<button id="send-btn" onclick="sendMessage()">发送</button>
</div>
</div>
<script>
// 配置项(和Laravel中的配置一致)
const PUSHER_KEY = 'laravel-chat-key';
const PUSHER_HOST = '127.0.0.1';
const PUSHER_PORT = 6001;
const LARAVEL_API_URL = 'http://127.0.0.1:8000/api';
// 登录用户ID(需从Laravel后端获取,如Blade中{{ auth()->user()->id }})
const USER_ID = {{ auth()->user()->id }};
// 会话ID(默认default,支持多会话)
const SESSION_ID = 'default';
// 存储AI流式回复的临时元素,用于打字机效果
let aiTempMessage = null;
// 1. 初始化Axios,携带Sanctum令牌(Laravel鉴权)
axios.defaults.baseURL = LARAVEL_API_URL;
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// 从Laravel的cookie中获取XSRF-TOKEN(Sanctum需要)
const getCookie = (name) => {
let value = '; ' + document.cookie;
let parts = value.split('; ' + name + '=');
if (parts.length === 2) return parts.pop().split(';').shift();
};
axios.defaults.headers.common['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
// 携带认证令牌(若使用Sanctum,需登录后获取)
// 注:Laravel Sanctum的认证令牌可通过/login接口获取,存储在localStorage
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('sanctum_token');
// 2. 初始化Pusher(对接Laravel WebSockets)
const pusher = new Pusher(PUSHER_KEY, {
wsHost: PUSHER_HOST,
wsPort: PUSHER_PORT,
wssPort: PUSHER_PORT,
enabledTransports: ['ws', 'wss'],
forceTLS: false,
cluster: 'mt1',
});
// 3. 订阅当前用户的私有聊天频道
const channel = pusher.subscribe('private-chat.' + USER_ID);
// 监听频道消息(接收AI的流式回复)
channel.bind('pusher:subscription_succeeded', () => {
console.log('WebSocket连接成功,已订阅频道:private-chat.' + USER_ID);
});
channel.bind('client-event', (data) => {
// 接收Laravel广播的AI消息
handleAIMessage(data);
});
// 4. 发送用户消息到Laravel API
const sendMessage = async () => {
const input = document.getElementById('message-input');
const content = input.value.trim();
if (!content) return;
// 清空输入框
input.value = '';
// 禁用发送按钮,避免重复发送
document.getElementById('send-btn').disabled = true;
try {
// 显示用户消息到页面
addMessageToPage('user', content);
// 调用Laravel API发送消息
await axios.post('/chat/send', {
content: content,
session_id: SESSION_ID
});
} catch (error) {
console.error('发送消息失败:', error);
addMessageToPage('ai', '消息发送失败,请重试!');
} finally {
// 启用发送按钮
document.getElementById('send-btn').disabled = false;
}
};
// 5. 处理AI流式消息,实现打字机效果
const handleAIMessage = (data) => {
const { type, content, session_id } = data;
if (session_id !== SESSION_ID) return;
// 初始化AI临时消息元素(首次收到流式结果时)
if (!aiTempMessage) {
aiTempMessage = addMessageToPage('ai', '');
}
// 追加流式内容,实现打字机效果
aiTempMessage.textContent += content;
// 若为最终结果,清空临时元素
if (data.is_finish) {
aiTempMessage = null;
}
};
// 6. 辅助函数:添加消息到页面
const addMessageToPage = (type, content) => {
const messagesContainer = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}-message`;
messageDiv.textContent = content;
messagesContainer.appendChild(messageDiv);
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return messageDiv;
};
// 7. 回车发送消息
document.getElementById('message-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter') sendMessage();
});
// 8. 页面加载时获取聊天记录
window.onload = async () => {
try {
const response = await axios.get('/chat/history', {
params: { session_id: SESSION_ID }
});
// 渲染历史消息
response.data.data.forEach(msg => {
addMessageToPage(msg.type, msg.content);
});
} catch (error) {
console.error('获取聊天记录失败:', error);
}
};
</script>
</body>
</html>
前端关键说明
鉴权:使用Laravel Sanctum做API鉴权,WebSocket的鉴权由Laravel ChatChannel完成,确保只有登录用户能访问自己的聊天频道;
打字机效果:通过
aiTempMessage临时存储AI消息元素,逐字追加内容,实现流畅的打字机效果;WebSocket兼容:Laravel WebSockets兼容Pusher协议,前端直接使用Pusher JS即可,无需手写WebSocket客户端;
消息渲染:区分用户消息和AI消息,实现左右分栏的聊天界面,自动滚动到底部。
第四步:整体测试(全链路联调)
按以下顺序启动服务,完成全链路测试:
启动Redis:
redis-server;启动Laravel WebSocket服务:
php artisan websockets:serve;启动Laravel HTTP服务:
php artisan serve;启动Python大模型服务:
python ai_chat.py;前端访问:登录Laravel后,访问聊天页面(如
http://127.0.0.1:8000/chat),输入消息并发送,即可看到AI的流式打字机回复。
四、生产级优化与扩展(必做)
上述基础方案已实现核心的实时流式聊天功能,生产环境需做以下优化和扩展,提升稳定性、性能、可维护性:
1. 稳定性优化
1.1 Redis高可用
生产环境使用Redis主从+哨兵或Redis Cluster,避免Redis单点故障;
为Redis操作添加超时控制,避免Laravel/Python因Redis阻塞导致服务卡死。
1.2 Python服务守护与扩容
使用Supervisor或systemd守护Python服务,避免进程意外退出;
Python服务支持多进程/多实例部署,通过Redis Pub/Sub的频道分片实现负载均衡(如多个Python实例监听不同的指令子频道)。
1.3 大模型调用容错
为Python的大模型调用添加重试机制(如指数退避重试),处理网络波动、大模型服务限流;
集成多大模型兜底(如OpenAI调用失败时,自动切换为通义千问),提升服务可用性。
1.4 WebSocket重连
前端添加WebSocket自动重连机制(如Pusher的
connection_error事件监听),处理网络中断、服务重启;Laravel WebSockets开启持久化连接,避免频繁重连导致的资源消耗。
2. 性能优化
2.1 Laravel协程与队列
将Redis订阅、大模型指令处理等耗时操作放入Laravel队列(如Redis队列),使用
supervisor守护队列工作进程,提升并发处理能力;开启Laravel的异步队列,避免同步操作阻塞主进程。
2.2 Python异步并发优化
Python使用uvloop替代默认的asyncio事件循环,提升异步IO性能(
pip install uvloop,代码中import uvloop; asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()));限制Python的最大并发数,避免大模型API限流(如通过信号量
asyncio.Semaphore(100)限制同时调用100个大模型请求)。
2.3 前端性能优化
对聊天消息做虚拟列表渲染(如Vue Virtual Scroller),避免聊天记录过多导致的DOM渲染卡顿;
压缩前端静态资源,使用CDN加速Pusher JS、Axios等第三方库。
3. 功能扩展
3.1 多会话支持
前端实现会话管理,支持创建/切换/删除会话,后端按
session_id区分不同会话的聊天记录;Redis的结果频道按
user_id:session_id分片,确保不同会话的消息不混淆。
3.2 消息格式扩展
支持富文本消息(图片、链接、代码块),前端实现对应的渲染逻辑,Python处理大模型的富文本结果;
支持消息撤回、编辑,后端添加对应的API和数据库操作。
3.3 限流与防刷
Laravel层基于Redis实现令牌桶限流,限制用户的消息发送频率(如10条/分钟),避免恶意刷量;
Python层限制大模型的调用频率,避免触发大模型API的限流策略。
3.4 日志与监控
Laravel端添加详细的日志记录(用户消息、Redis通信、WebSocket连接),使用ELK栈做日志分析;
Python端添加大模型调用日志(请求参数、响应结果、耗时),监控大模型的调用成功率和延迟;
接入Prometheus+Grafana监控Laravel、Python、Redis的核心指标(QPS、连接数、耗时、错误率),并添加告警机制(邮件/钉钉/企业微信)。
3.5 部署优化
使用Docker容器化部署Laravel、Python、Redis,通过Docker Compose或K8s实现服务的编排和扩缩容;
Laravel和Python服务部署在同一内网,提升Redis通信的速度和稳定性;
前端使用Nginx做反向代理,转发WebSocket请求和API请求,实现HTTPS/WSS加密传输(生产环境必须)。
五、其他可选方案(场景适配)
上述Redis Pub/Sub+Laravel WebSockets是最优解,若你的场景有特殊需求,可选择以下方案:
方案1:Laravel + Python 用HTTP接口通信(适合非流式场景)
若大模型不支持流式输出,Laravel可直接通过HTTP POST调用Python的API,Python同步调用大模型并返回结果,Laravel再推送给前端;
优势:实现简单,无需Redis;劣势:不支持流式,交互体验差。
方案2:使用Socket.IO替代Laravel WebSockets(适合跨端场景)
Laravel端使用**laravel-socket.io组件,Python端使用python-socketio**,两者通过Socket.IO实现实时通信;
优势:跨端兼容性更好(支持浏览器、小程序、APP),内置房间、命名空间等功能;劣势:需额外学习Socket.IO协议,Laravel生态集成度低于Laravel WebSockets。
方案3:使用消息队列(如RabbitMQ)替代Redis Pub/Sub(适合高可靠场景)
若需要消息持久化、消费确认、死信队列等高级特性,可用RabbitMQ替代Redis Pub/Sub,实现Laravel和Python之间的可靠通信;
优势:消息可靠性高,支持复杂的路由规则;劣势:部署和维护成本高,比Redis重。
六、总结
你这个Laravel+Python的AI实时聊天需求,最优落地方案是前端WebSocket → Laravel WebSockets → Redis Pub/Sub → Python异步大模型调用 → Redis Pub/Sub → Laravel → WebSocket推送给前端,核心亮点如下:
完全解耦:Laravel负责业务和前端交互,Python负责大模型处理,两者仅通过Redis通信,可独立部署和扩容;
流式端到端:适配大模型流式输出,实现前端“打字机”效果,提升交互体验;
低开发成本:基于Laravel和Python的成熟生态组件,无需手写底层WebSocket和进程间通信代码;
高可扩展性:轻松支持多会话、多大模型、限流、监控等生产级功能。
核心落地步骤回顾:
Laravel端搭建WebSocket服务,实现前端鉴权、Redis消息发布/消费、消息广播;
Python端实现异步Redis监听,流式调用大模型并将结果推送给Redis;
前端实现WebSocket连接、消息发送、流式结果的打字机效果渲染;
生产环境做Redis高可用、服务守护、限流、监控等优化。
这套方案已在多个生产环境落地,支持万级并发会话,可直接照搬开发,后续根据业务需求扩展功能即可。
(注:文档部分内容可能由 AI 生成)
