前端:
<!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>
// go run main.go
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
const (
// 运行端口号
SERVER_PORT = ":8000"
// 千帆应用 AK/SK
API_KEY = "JMLF5n6eWqxxxx9ECh"
SECRET_KEY = "s1ii7dGJBK5Jxxxx8iIrAET"
// 鉴权接口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"
)
// GetAccessToken 使用 AK,SK 生成鉴权签名(Access Token)
func GetAccessToken() (string, error) {
// 拼接请求参数
postData := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", API_KEY, SECRET_KEY)
client := &http.Client{}
req, err := http.NewRequest("POST", TOKEN_URL, strings.NewReader(postData))
if err != nil {
return "", fmt.Errorf("创建HTTP Request报错: %s", err.Error())
}
// 设置请求头
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
// 发起请求并获取响应
ret, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("执行HTTP请求报错: %s", err.Error())
}
defer ret.Body.Close()
// 解析响应内容
body, err := io.ReadAll(ret.Body)
if err != nil {
return "", fmt.Errorf("获取Token报错: %s", err.Error())
}
// 解析并返回 access_token
accessTokenObj := map[string]string{}
_ = json.Unmarshal([]byte(body), &accessTokenObj)
return accessTokenObj["access_token"], nil
}
// main 主入口
func main() {
// 初始化Gin实例
r := gin.Default()
// 跨域配置
r.Use(cors.Default())
// 注册路由
r.POST("/eb_stream", func(c *gin.Context) {
// 设置响应头
c.Header("Connection", "keep-alive")
c.Header("Cache-Control", "no-cache")
c.Header("Transfer-Encoding", "chunked")
c.Header("Content-Type", "text/event-stream")
// 获取入参
var prompt struct {
Prompt string `json:"prompt"`
}
if err := c.ShouldBindJSON(&prompt); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 获取 Access Token
token, err := GetAccessToken()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 组装请求参数并请求文心
source := "&sourceVer=0.0.1&source=app_center&appName=streamDemo"
url := EB_URL + "?access_token=" + token + source
payload := strings.NewReader(fmt.Sprintf(`{"messages":[{"role":"user","content":"%s"}],"stream":true}`, prompt.Prompt))
resp, err := http.Post(url, "application/json", payload)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
// 处理返回结果并写入响应流
c.Stream(func(w io.Writer) bool {
reader := bufio.NewReader(resp.Body)
for {
// 逐行读取
line, err := reader.ReadBytes('\n')
if err != nil {
log.Println("读取大模型响应数据失败: ", err.Error())
break
}
// 对读到的这行数据进行处理
lineStr := string(line)
if strings.HasPrefix(lineStr, "data:") {
lineStr = strings.TrimPrefix(lineStr, "data:")
lineStr = strings.TrimSuffix(lineStr, "\n")
log.Println("收到大模型的响应: ", lineStr)
// 写入响应流
_, err = w.Write([]byte(lineStr))
if err != nil {
log.Println("写入响应流失败: ", err.Error())
return false
}
// 判断是否为结束
if strings.Contains(lineStr, `"is_end":true`) {
log.Println("收到大模型反馈的结束标志")
break
}
}
log.Println("等待大模型下一次响应")
return true
}
log.Println("流式响应结束")
return false
})
})
// 启动服务
r.Run(SERVER_PORT)
}
ERNIE-Bot 4.0
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
const API_KEY = "JMLF5n6eWq2xxxECh"
const SECRET_KEY = "s1ii7dGJBxxxzT8iIrAET"
func main() {
url := "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=" + GetAccessToken()
payload := strings.NewReader(`{"disable_search":false,"enable_citation":false}`)
client := &http.Client {}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
/**
* 使用 AK,SK 生成鉴权签名(Access Token)
* @return string 鉴权签名信息(Access Token)
*/
func GetAccessToken() string {
url := "https://aip.baidubce.com/oauth/2.0/token"
postData := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s", API_KEY, SECRET_KEY)
resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(postData))
if err != nil {
fmt.Println(err)
return ""
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return ""
}
accessTokenObj := map[string]string{}
json.Unmarshal([]byte(body), &accessTokenObj)
return accessTokenObj["access_token"]
}