Write the Code. Change the World.

4月 26

随着 AI 聊天的兴起,流式展现数据的需求变得更常见。前端 EventSource 的使用频率也会更高。接openai,文心一言,通义千问这些接口,并以流式的方式在页面展现出来。就得自己的接口服务端也以流式的方式返回给自己的前端,自己的服务器端接它们,让它们也要以流的方式返回。这个时候服务端不仅要做接口的对接和数据的转发,还得做数据的解析和存储。

这里前端以 vue3,后端使用 laravel 的方式,来简单介绍下怎么搞。

前端 API 选择

https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource

前端选择 fetch,并没有选择 EventSource。

因为 fetch 本身就可以支持 EventSource 的方式接受数据,使用方式也会像使用 axios 类似。而单纯 EventSource 的使用会收到请求方式,不能自定义Header头,连接数目等方式的限制。fetch 就像在调用接口,EventSource 就像是 websocket。

EventSource 示例:

const sse = new EventSource("/api/v1/sse");

sse.addEventListener("notice", (e) => {
  console.log(e.data);
});

sse.addEventListener("update", (e) => {
  console.log(e.data);
});

sse.addEventListener("message", (e) => {
  console.log(e.data);
});

fetch 示例:

const data = { username: "example" };

fetch("https://example.com/profile", {
    method: "POST", // or 'PUT'
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify(data),
})
    .then((response) => response.json())
    .then((data) => {
        console.log("Success:", data);
    })
    .catch((error) => {
        console.error("Error:", error);
    });

后端 API 选择

后端选择 GuzzleHttp\Client, 它支持流式的方式去请求接口。

https://phpguzzle.org/zh/

示列

use GuzzleHttp\Client;
use Psr\Http\Message\StreamInterface;

     $client = new Client();

        $response = $client->request(
            'post',
            'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
            [
                'headers' => [
                    'Content-Type' => 'application/json',
                    'Authorization' => 'xxx',
                ],
                'json' => [
                    'model' => 'xxx',
                ],
                'stream' => true
            ]
        );

        return response()->stream(function () use ($response) {
            ob_start();
            $stream = $response->getBody();
            if ($stream instanceof StreamInterface) {
                while (!$stream->eof()) { // 当没有到达流的末尾时  
                    $chunk = $stream->read(1024); // 每次读取 1024 字节的数据块
                }
                ob_end_flush();
                // 关闭流  
                $stream->close();
            } else {
                // 处理错误情况  
                echo 'Failed to get stream from response.';
            }
        }, 200, [
            'Content-Type' => 'text/event-stream',
            'Cache-Control' => 'no-cache',
            'Connection' => 'keep-alive',
            'X-Accel-Buffering' => 'no'
        ]);

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注