首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >ThinkPHP开发的AI问答系统,调用AI流式接口并实时将这些数据推送给前端,实现打字机效果

ThinkPHP开发的AI问答系统,调用AI流式接口并实时将这些数据推送给前端,实现打字机效果

作者头像
程序猿的栖息地
发布2025-08-01 14:15:05
发布2025-08-01 14:15:05
5170
举报

在ThinkPHP中调用AI流式接口并实时返回给前端,可以通过以下步骤实现。这里以使用SSE(Server-Sent Events)技术为例:

如需完整代码请在留言区留下您的邮箱地址

后端实现(ThinkPHP)

  1. 创建控制器方法
代码语言:javascript
复制
namespace app\controller;

use think\Response;
use think\facade\Log;

class AiController
{
    public function streamChat()
    {
        // 设置SSE响应头
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        header('X-Accel-Buffering: no'); // 禁用Nginx缓冲

        // 获取用户输入
        $input = input('question');

        try {
            // 调用AI流式接口
            $this->callAiStream($input, function($chunk) {
                // 处理AI返回的数据块
                $data = $this->parseAiResponse($chunk);

                if (!empty($data['content'])) {
                    // 发送SSE格式数据
                    echo "event: update\n";  // 自定义事件名
                    echo "data: " . json_encode(['content' => $data['content']], JSON_UNESCAPED_UNICODE) . "\n\n";
                    ob_flush();
                    flush();
                }
            });
        } catch (\Exception $e) {
            Log::error('AI请求失败: ' . $e->getMessage());
            echo "event: error\n";
            echo "data: " . json_encode(['msg' => '服务异常']) . "\n\n";
        }

        // 发送结束信号
        echo "event: end\n";
        echo "data: \n\n";
        exit;
    }

    /**
     * 调用AI流式接口
     */
    private function callAiStream(string $prompt, callable $callback)
    {
        $apiKey = 'your-api-key'; // 替换为真实API密钥
        $url = 'https://api.openai.com/v1/chat/completions'; // 替换为实际API地址

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $apiKey
            ],
            CURLOPT_POSTFIELDS => json_encode([
                'model' => 'gpt-3.5-turbo',
                'messages' => [['role' => 'user', 'content' => $prompt]],
                'stream' => true,
                'temperature' => 0.7
            ]),
            CURLOPT_WRITEFUNCTION => function($ch, $data) use ($callback) {
                // 实时处理数据块
                $callback($data);
                return strlen($data);
            }
        ]);

        curl_exec($ch);
        if (curl_errno($ch)) {
            throw new \Exception(curl_error($ch));
        }
        curl_close($ch);
    }

    /**
     * 解析AI响应数据
     */
    private function parseAiResponse(string $chunk): array
    {
        $result = ['content' => ''];

        // 处理流式数据格式(示例为OpenAI格式)
        $lines = explode("\n", trim($chunk));
        foreach ($lines as $line) {
            if (strpos($line, 'data: ') === 0) {
                $json = substr($line, 6);
                if ($json === '[DONE]') break;

                $data = json_decode($json, true);
                if (isset($data['choices'][0]['delta']['content'])) {
                    $result['content'] .= $data['choices'][0]['delta']['content'];
                }
            }
        }

        return $result;
    }
}

前端实现(Vue示例)

代码语言:javascript
复制
<template>
  <div>
    <input v-model="question" placeholder="输入问题...">
    <button @click="startStream">开始对话</button>

    <div id="output" v-html="output"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      output: '',
      eventSource: null
    }
  },
  methods: {
    startStream() {
      // 清空前一次输出
      this.output = '';

      // 关闭现有连接
      if (this.eventSource) this.eventSource.close();

      // 创建SSE连接
      this.eventSource = new EventSource(`/ai/streamChat?question=${encodeURIComponent(this.question)}`);

      // 监听消息事件
      this.eventSource.addEventListener('update', event => {
        const data = JSON.parse(event.data);
        this.output += data.content; // 增量更新内容
      });

      // 监听结束事件
      this.eventSource.addEventListener('end', () => {
        this.eventSource.close();
        console.log('流式传输结束');
      });

      // 错误处理
      this.eventSource.addEventListener('error', err => {
        console.error('SSE错误:', err);
        this.eventSource.close();
      });
    }
  },
  beforeUnmount() {
    // 组件卸载时关闭连接
    if (this.eventSource) this.eventSource.close();
  }
}
</script>

关键点说明

  1. SSE工作原理
    • 服务端发送text/event-stream响应
    • 每条消息格式:event: xxx\ndata: {...}\n\n
    • 浏览器通过EventSource API接收
  2. 流式处理核心
    • 后端使用curlCURLOPT_WRITEFUNCTION逐块处理
    • 每次收到数据立即刷新输出缓冲区(ob_flush() + flush()
    • 前端通过事件监听增量更新DOM
  3. 性能优化
    • 禁用Nginx代理缓冲:X-Accel-Buffering: no
    • 设置PHP无缓冲输出:while (@ob_end_flush());
    • 前端使用增量更新而非全量替换
  4. 错误处理
    • 捕获cURL异常并发送error事件
    • 前端监听error事件进行重连/提示
    • 添加超时控制(示例未展示,可增加CURLOPT_TIMEOUT

部署注意事项

  1. 配置调整 nginx # Nginx配置proxy_bufferingoff;proxy_cacheoff;
  2. 超时设置 php // 在调用前设置set_time_limit(0);// 取消PHP超时限制ini_set('output_buffering',0);
  3. 安全增强
    • 添加CSRF令牌验证
    • 实施请求频率限制
    • 敏感数据过滤(防止Prompt注入)

替代方案

  1. WebSocket
    • 更复杂的双向通信
    • 需要Workerman/Swoole等支持
    • 适合高频交互场景
  2. 长轮询
    • 兼容性更好
    • 实现简单但效率较低
    • 适合低频更新场景

完整实现时建议:

  • 添加心跳机制(每30秒发送: heartbeat
  • 实现客户端重连逻辑
  • 添加对话状态管理
  • 设计限流和熔断机制
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿的栖息地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 后端实现(ThinkPHP)
  • 前端实现(Vue示例)
  • 关键点说明
  • 部署注意事项
  • 替代方案
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档