2025年,AI大模型已经成为应用开发的标配。ThinkPHP 8.0凭借其简洁的架构和强大的扩展能力,非常适合快速集成AI接口。本文将通过一个完整的「智能内容助手」案例,带你掌握ThinkPHP 8.0与AI API的集成技巧,包括流式输出、对话管理、缓存优化等。
1. 环境准备与项目初始化
确保你的PHP版本 ≥ 8.1,并已安装Composer。ThinkPHP 8.0要求PHP 8.0+,推荐8.1以上获得更好的性能。
# 通过Composer创建ThinkPHP 8.0项目 composer create-project topthink/think tp-ai-assistant cd tp-ai-assistant # 安装 GuzzleHttp 用于调用大模型API composer require guzzlehttp/guzzle # 安装 thinkphp 的缓存扩展(可选) composer require topthink/think-cache
接下来配置数据库(本案例使用SQLite简化,也可用MySQL)。修改 config/database.php:
// 使用SQLite数据库,无需额外配置
'default' => 'sqlite',
'connections' => [
'sqlite' => [
'type' => 'sqlite',
'database' => app()->getRootPath() . 'database/ai_assistant.db',
'prefix' => '',
],
],
2. 数据模型:对话记录
我们需要保存用户对话历史,以便大模型理解上下文。创建模型 app/model/Conversation.php:
<?php
namespace appmodel;
use thinkModel;
class Conversation extends Model
{
// 自动时间戳
protected $autoWriteTimestamp = true;
// 关联消息
public function messages()
{
return $this->hasMany(Message::class, 'conversation_id');
}
}
消息模型 app/model/Message.php:
<?php
namespace appmodel;
use thinkModel;
class Message extends Model
{
protected $autoWriteTimestamp = true;
public function conversation()
{
return $this->belongsTo(Conversation::class);
}
}
执行迁移(ThinkPHP 8.0内置迁移工具):
# 创建迁移文件 php think make:migration CreateConversationsTable php think make:migration CreateMessagesTable
编辑迁移文件,添加字段后执行 php think migrate:run。字段设计如下:
- conversations: id, title, created_at, updated_at
- messages: id, conversation_id, role (user/assistant), content, created_at
3. 核心服务:调用大模型API
以阿里云通义千问API为例(兼容OpenAI格式)。创建服务类 app/service/AiService.php:
<?php
namespace appservice;
use GuzzleHttpClient;
use thinkfacadeCache;
use thinkfacadeLog;
class AiService
{
protected $apiKey;
protected $apiUrl = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';
protected $client;
public function __construct()
{
$this->apiKey = env('AI_API_KEY', 'your-api-key'); // 从.env读取
$this->client = new Client([
'timeout' => 30,
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
]
]);
}
/**
* 发送对话请求(非流式)
*/
public function chat(array $messages, string $model = 'qwen-turbo'): string
{
$params = [
'model' => $model,
'input' => [
'messages' => $messages
],
'parameters' => [
'result_format' => 'message',
'temperature' => 0.8,
'max_tokens' => 2048,
]
];
try {
$response = $this->client->post($this->apiUrl, [
'json' => $params
]);
$body = json_decode($response->getBody(), true);
// 解析返回格式
return $body['output']['choices'][0]['message']['content'] ?? '';
} catch (Exception $e) {
Log::error('AI API调用失败: ' . $e->getMessage());
return '抱歉,AI服务暂时不可用。';
}
}
/**
* 流式输出(用于打字机效果)
*/
public function chatStream(array $messages, callable $callback, string $model = 'qwen-turbo')
{
$params = [
'model' => $model,
'input' => [
'messages' => $messages
],
'parameters' => [
'result_format' => 'message',
'incremental_output' => true,
'temperature' => 0.8,
]
];
$response = $this->client->post($this->apiUrl, [
'json' => $params,
'stream' => true,
]);
$body = $response->getBody();
while (!$body->eof()) {
$line = $body->readLine();
if (str_starts_with($line, 'data:')) {
$data = json_decode(substr($line, 5), true);
if (isset($data['output']['choices'][0]['delta']['content'])) {
$content = $data['output']['choices'][0]['delta']['content'];
call_user_func($callback, $content);
}
}
}
}
}
4. 控制器:处理对话请求
创建 app/controller/AiController.php:
<?php
namespace appcontroller;
use appmodelConversation;
use appmodelMessage;
use appserviceAiService;
use thinkfacadeView;
use thinkRequest;
class AiController
{
protected $aiService;
public function __construct(AiService $aiService)
{
$this->aiService = $aiService;
}
// 显示对话页面
public function index()
{
$conversations = Conversation::order('id', 'desc')->select();
return View::fetch('index', [
'conversations' => $conversations
]);
}
// 获取某个对话的消息
public function conversation($id)
{
$conversation = Conversation::find($id);
if (!$conversation) {
return json(['error' => '对话不存在'], 404);
}
$messages = $conversation->messages()->order('id', 'asc')->select();
return json(['messages' => $messages]);
}
// 发送消息(非流式)
public function send(Request $request)
{
$data = $request->post();
$content = $data['content'] ?? '';
$conversationId = $data['conversation_id'] ?? null;
if (empty($content)) {
return json(['error' => '请输入消息'], 400);
}
// 创建或获取对话
if ($conversationId) {
$conversation = Conversation::find($conversationId);
} else {
$conversation = Conversation::create([
'title' => mb_substr($content, 0, 30)
]);
}
// 保存用户消息
$userMessage = Message::create([
'conversation_id' => $conversation->id,
'role' => 'user',
'content' => $content
]);
// 构建消息历史
$historyMessages = $conversation->messages()->order('id', 'asc')->select();
$aiMessages = [];
foreach ($historyMessages as $msg) {
$aiMessages[] = [
'role' => $msg->role,
'content' => $msg->content
];
}
// 调用AI
$reply = $this->aiService->chat($aiMessages);
// 保存AI回复
$assistantMessage = Message::create([
'conversation_id' => $conversation->id,
'role' => 'assistant',
'content' => $reply
]);
return json([
'conversation_id' => $conversation->id,
'reply' => $reply,
'message_id' => $assistantMessage->id
]);
}
// 流式接口(SSE)
public function stream(Request $request)
{
$content = $request->post('content', '');
$conversationId = $request->post('conversation_id', 0);
// 创建或获取对话(与send类似,省略重复代码)
// 实际项目中可提取公共方法
// 此处直接返回流式响应
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$conversation = $conversationId ? Conversation::find($conversationId) : Conversation::create(['title' => mb_substr($content, 0, 30)]);
// 保存用户消息
Message::create([
'conversation_id' => $conversation->id,
'role' => 'user',
'content' => $content
]);
// 构建历史
$history = $conversation->messages()->order('id', 'asc')->select();
$aiMessages = [];
foreach ($history as $msg) {
$aiMessages[] = ['role' => $msg->role, 'content' => $msg->content];
}
$fullReply = '';
// 流式回调
$this->aiService->chatStream($aiMessages, function ($chunk) use (&$fullReply) {
$fullReply .= $chunk;
echo "data: " . json_encode(['chunk' => $chunk]) . "nn";
ob_flush();
flush();
});
// 保存完整回复
Message::create([
'conversation_id' => $conversation->id,
'role' => 'assistant',
'content' => $fullReply
]);
echo "data: [DONE]nn";
ob_flush();
flush();
}
}
5. 路由配置
编辑 route/app.php:
use thinkfacadeRoute;
Route::get('/', 'AiController/index');
Route::get('/conversation/:id', 'AiController/conversation');
Route::post('/send', 'AiController/send');
Route::post('/stream', 'AiController/stream');
6. 视图层:对话界面
创建 app/view/ai/index.html(使用原生PHP模板):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>智能内容助手 - ThinkPHP 8.0</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>🤖 AI 智能助手</h1>
<div>
<h3>历史对话</h3>
<ul>
<li><a href="javascript:void(0)" rel="external nofollow" onclick="loadConversation(id; ?>)">title); ?></a></li>
</ul>
</div>
<div id="chat-box" style="border:1px solid #ccc; height:400px; overflow-y:auto; padding:10px; margin:20px 0;">
<!-- 消息显示区域 -->
</div>
<div>
<textarea id="user-input" rows="3" style="width:80%;" placeholder="输入你的问题..."></textarea>
<button onclick="sendMessage()">发送(非流式)</button>
<button onclick="sendStream()">发送(流式)</button>
</div>
<script>
let currentConversationId = null;
function loadConversation(id) {
currentConversationId = id;
fetch('/conversation/' + id)
.then(res => res.json())
.then(data => {
const box = document.getElementById('chat-box');
box.innerHTML = '';
data.messages.forEach(msg => {
const div = document.createElement('div');
div.textContent = (msg.role === 'user' ? '🧑 ' : '🤖 ') + msg.content;
box.appendChild(div);
});
});
}
function sendMessage() {
const input = document.getElementById('user-input');
const content = input.value.trim();
if (!content) return;
fetch('/send', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
content: content,
conversation_id: currentConversationId
})
})
.then(res => res.json())
.then(data => {
if (data.conversation_id) {
currentConversationId = data.conversation_id;
// 重新加载对话
loadConversation(currentConversationId);
input.value = '';
}
});
}
function sendStream() {
const input = document.getElementById('user-input');
const content = input.value.trim();
if (!content) return;
const box = document.getElementById('chat-box');
// 显示用户消息
const userDiv = document.createElement('div');
userDiv.textContent = '🧑 ' + content;
box.appendChild(userDiv);
// 准备AI回复容器
const aiDiv = document.createElement('div');
aiDiv.textContent = '🤖 ';
box.appendChild(aiDiv);
fetch('/stream', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
content: content,
conversation_id: currentConversationId
})
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function read() {
reader.read().then(({done, value}) => {
if (done) return;
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('n');
buffer = lines.pop(); // 保留未完成的行
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.chunk) {
aiDiv.textContent += parsed.chunk;
}
} catch (e) {}
}
}
read();
});
}
read();
});
input.value = '';
}
</script>
</body>
</html>
7. 运行与测试
在项目根目录执行 php think run,访问 http://localhost:8000。你将看到对话界面:
- 左侧显示历史对话列表
- 右侧聊天框支持流式和非流式两种模式
- 所有对话记录持久化到SQLite数据库
进阶优化:
- 使用Redis缓存常见问题的回复,减少API调用
- 添加Token计数和限流中间件
- 支持Markdown渲染(可使用marked.js)
- 集成文心一言或Gemini API(仅需修改AiService)
8. 总结
通过本文,你学会了在ThinkPHP 8.0中:
- 使用GuzzleHttp调用大模型API
- 实现流式输出(SSE)提升用户体验
- 设计对话数据库模型并管理上下文
- 构建完整的AI对话Web应用
ThinkPHP 8.0的简洁设计让AI集成变得异常流畅。你可以在此基础上快速开发AI写作助手、智能客服、代码生成器等应用。拥抱AI时代,从ThinkPHP开始。
本文为原创技术教程,基于ThinkPHP 8.0.0和通义千问API。代码已通过测试,可直接运行。

