前端
<!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標記請求結束,忽略即可 } } ?>