百度智能云,流式请求示例

前端

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <label for="textInput">Prompt:</label>
  <input type="textarea" id="textInput" placeholder="您有什么问题">
  <button onclick="run_prompt()">执行prompt</button>
  <p><textarea id="answer" rows="10" cols="50" readonly></textarea></p>
<script>
  current_text = document.getElementById('answer');
  text = "";
  char_index = 0
  function run_prompt() {
    var inputValue = document.getElementById('textInput').value;
    document.getElementById('answer').value = "";
    // 调用服务端的流式接口, 修改为自己的服务器地址和端口号
    fetch('http://<server address>:8000/eb_stream', {
      method: 'post',
      headers: {'Content-Type': 'text/plain'},
      body: JSON.stringify({'prompt': inputValue})
    })
    .then(response => {
      return response.body;
    })
    .then(body => {
      const reader = body.getReader();
      const decoder = new TextDecoder();
      function read() {
        return reader.read().then(({ done, value }) => {
          if (done) { // 读取完成
            return;
          }
          data = decoder.decode(value, { stream: true });
          text += JSON.parse(data).result;
          type();  // 打字机效果输出
          return read();
        });
      }
      return read();
    })
    .catch(error => {
      console.error('发生错误:', error);
    });
  }

  function type() {
    let enableCursor = true;  // 启用光标效果
    if (char_index < text.length) {
      let txt = document.getElementById('answer').value;
      let cursor = enableCursor ? "|" : "";
      if (enableCursor && txt.endsWith("|")) {
        txt = txt.slice(0, -1);
      }
      document.getElementById('answer').value = txt + text.charAt(char_index) + cursor;
      char_index++;
      setTimeout(type, 1000/5);  // 打字机速度控制, 每秒5个字
    }
  }
</script>
</body>
</html>

后端

<?php
/*
启动方式: php server.php
*/
// 运行端口号
$server_port = "0.0.0.0:8000";
// 服务无连接时的超时时间(s), 0表示永不超时
$server_timeout = 0;
// 千帆应用 AK/SK
$ak = "24.9d0562891315801c0f14cef19b39f7b0.2592000.1719019992.282335-73805369";
$sk = "";
// 鉴权接口URL
$token_url = 'https://aip.baidubce.com/oauth/2.0/token';
// 大模型接口URL
$eb_url = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant';
$source = "&sourceVer=0.0.1&source=app_center&appName=streamDemo";

// 创建 socket 服务
$server = stream_socket_server("tcp://$server_port", $errno, $errstr);
if (!$server) {
    die("Failed to create server: $errstr");
}

// 持续等待客户端请求
$start_time = time();
while (true) {
    // 检查是否有客户端连接
    $read = array($server);
    $write = null;
    $except = null;
    if (stream_select($read, $write, $except, 0)) {
        $client = stream_socket_accept($server);
        // 获取客户端发来的数据
        $data = fread($client, 1024);
        $postData = '';
        if (preg_match("/\r\n\r\n(.*)/s", $data, $match)) {
            $postData = $match[1];
        }
        // 解析 POST 参数, 请求大模型服务
        $prompt = json_decode($postData);
        $responseBody = get_info_from_llm($prompt->prompt);

        // 返回响应头
        $responseHeaders = [
            'Content-Type' => 'text/plain',
            'Access-Control-Allow-Origin' => '*',
        ];
        fwrite($client, "HTTP/1.1 200 OK\r\n");
        foreach ($responseHeaders as $header => $value) {
            fwrite($client, "$header: $value\r\n");
        }
        fwrite($client, "\r\n");

        // 返回响应体
        foreach ($responseBody as $chunk) {
            fwrite($client, "$chunk");
            flush();

            // 防止偶发多条合并返回
            usleep(100000);
        }
        fclose($client);
    } else {
        // 0 表示永不超时
        if ($server_timeout == 0) {
            continue;
        }

        // 超时则退出
        if (time() - $start_time >= $server_timeout) {
            echo "Timeout reached. Stopping server.
";
            break;
        }

        // 否则继续等待
        continue;
    }
}
// 释放资源
fclose($server);

// 请求大模型服务, 并将大模型的返回数据逐行返回
function get_info_from_llm($prompt){
    // 全局变量
    global $ak, $sk, $token_url, $eb_url;

    // 获取 access_token
    $curl = curl_init();
    $postData = array(
        'grant_type' => 'client_credentials',
        'client_id' => $ak,
        'client_secret' => $sk
    );
    curl_setopt_array($curl, array(
        CURLOPT_URL => $token_url,
        CURLOPT_CUSTOMREQUEST => 'POST',
        CURLOPT_SSL_VERIFYPEER  => false,
        CURLOPT_SSL_VERIFYHOST  => false,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => http_build_query($postData)
    ));
    $token_rtn = curl_exec($curl);
    curl_close($curl);

    // 请求大模型服务, 并将大模型的返回数据逐行返回给客户端
    $body = ["messages" => [["role" => "user", "content" => $prompt]],"stream" => true];
    $url = $eb_url . '?access_token=' . json_decode($token_rtn)->access_token . $source;
    $options = [
        'http' => [
            'method' => 'POST',
            'header' => 'Content-Type: text/plain',
            'content' => json_encode($body),
            'ignore_errors' => true,
            'protocol_version' => 1.1,
            'timeout' => 60
        ]
    ];
    $context = stream_context_create($options);
    $stream = fopen($url, 'r', false, $context);
    stream_set_blocking($stream, 0);
    if ($stream) {
        $msg = "";
        // 判断数据流是否已读取完
        while (!feof($stream)) {
            // 逐行读取
            $lines = explode("\n", fgets($stream));
            foreach ($lines as $line) {
                // 去掉空行
                if ($line != "") {
                    echo "正在处理的行数据: $line\n";
                    $msgPart = explode(":", $line, 2);
                    if ($msgPart[0] == "data") {
                        if ($msg != "") {
                            yield $msg;
                        }
                        $msg = $msgPart[1];
                    } else {
                        $msg = $msg . $line;
                    }
                }
            }
        }
        // 最后一次msg不需要yield,原因是最后一次请求大模型接口返回的result是空,只是使用is_end=true标记请求结束,忽略即可
    }
}
?>

 

posted @ 2024-05-23 10:39  张志健  阅读(75)  评论(0编辑  收藏  举报