ThinkPHP 8.0 + 大模型API:构建智能内容助手实战

2026-04-24 0 278

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。代码已通过测试,可直接运行。

ThinkPHP 8.0 + 大模型API:构建智能内容助手实战
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 thinkphp ThinkPHP 8.0 + 大模型API:构建智能内容助手实战 https://www.taomawang.com/server/thinkphp/1743.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务