前端
<!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标记请求结束,忽略即可
}
}
?>