讯飞星火、阿里云通义千问 Web 端调用示例

正在开发一个问答小网页,需要接入大模型,查看了:

讯飞星火的调用文档: https://www.xfyun.cn/doc/spark/Web.html

通义千问的调用文档: https://help.aliyun.com/zh/dashscope/developer-reference/api-details

发现官方文档对于前端浏览器直接调用 API 的示例都写得不够清楚,所以在此重新封装了示例。

星火用的是 WebSocket 协议,千问用的是 Server-Sent Events,也就是 SSE。

注意:示例中直接把密钥写进了前端代码,生产环境不推荐这么做,建议通过 nginx 代理等方式将密钥注入请求。

截图

讯飞星火

开通服务: https://xinghuo.xfyun.cn/sparkapi

ai-xinghuo.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import CryptoJS from "crypto-js";
import type { ChatHistory } from "./ai";

const URL = "wss://spark-api.xf-yun.com/v3.1/chat";
const APPID = "";
const API_SECRET = "";
const API_KEY = "";

export function callXinghuo({
text,
history,
onMessage,
}: {
text: string;
history: ChatHistory;
onMessage: (msg: string) => void;
}) {
return new Promise((resolve, reject) => {
let fullMsg = "";
const webSocket = new WebSocket(getWebSocketUrl());
webSocket.onmessage = (e) => {
try {
const resultData = e.data;
const jsonData = JSON.parse(resultData);
if (jsonData.header.code === 0) {
const msg = getContent(jsonData);
console.log("Got message", msg);
fullMsg += msg;
onMessage(msg);
if (jsonData.header.status === 2) {
console.log("API response finished", fullMsg);
webSocket.close();
resolve(fullMsg);
}
} else {
const error = new Error(
`${jsonData.header.code}:${jsonData.header.message}`,
);
console.error("API error:", error);
reject(error);
}
} catch (e) {
console.error("Handle message exception:", e);
reject(e);
}
};
webSocket.onerror = (e) => {
console.error("WebSocket error:", e);
reject(e);
};
webSocket.onopen = () => {
console.log("WebSocket open");
webSocket.send(getParams(text, history));
};
webSocket.onclose = () => {
console.log("WebSocket close");
};
});
}

function getWebSocketUrl() {
var host = location.host;
var date = new Date().toUTCString();
var algorithm = "hmac-sha256";
var headers = "host date request-line";
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v3.1/chat HTTP/1.1`;
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, API_SECRET);
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
var authorizationOrigin = `api_key="${API_KEY}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
var authorization = btoa(authorizationOrigin);
return `${URL}?authorization=${authorization}&date=${date}&host=${host}`;
}

function getParams(message: string, history: ChatHistory) {
return JSON.stringify({
header: {
app_id: APPID,
uid: "fd3f47e40d",
},
parameter: {
chat: {
domain: "generalv3",
temperature: 0.5,
max_tokens: 1024,
},
},
payload: {
message: {
text: getPayloadText(message, history),
},
},
});
}

function getContent(jsonData: any) {
let content = "";
try {
if (jsonData.header.code === 0) {
for (const choice of jsonData.payload.choices.text) {
content += choice.content;
}
}
} catch (e) {
console.error("Get content error:", e);
}
return content;
}

function getPayloadText(text: string, history: ChatHistory) {
const array = [];
// 排除最后一条 history,因为是本次刚发的消息
for (let i = 0; i < history.length - 1; i++) {
const chat = history[i];
array.push(
{
role: "user",
content: chat.userMsg,
},
{
role: "assistant",
content: chat.assistantMsg,
},
);
}
array.push({
role: "user",
content: text,
});
return array;
}

阿里云通义千问

开通服务: https://dashscope.console.aliyun.com/overview

ai-qwen.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import type { ChatHistory } from "./ai";

// 直接调会跨域,需要配反代
const URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
const API_KEY = "";

export async function callQwen({
text,
history,
onMessage,
}: {
text: string;
history: ChatHistory;
onMessage: (msg: string) => void;
}) {
const response = await fetch(URL, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"X-DashScope-SSE": "enable",
},
body: getParams(text, history),
});
const reader = response.body?.getReader();

if (!reader) return;
while (true) {
const { value, done } = await reader.read();
const utf8Decoder = new TextDecoder("utf-8");
const resultText = value ? utf8Decoder.decode(value, { stream: true }) : "";
try {
const data = getResult(resultText);
console.log("qwen data", data);
if (data.output.text) {
onMessage(data.output.text);
}
} catch (e) {}
if (done) {
break;
}
}
}

function getParams(message: string, history: ChatHistory) {
return JSON.stringify({
model: "qwen-max",
input: {
prompt: message,
history: getHistory(history),
},
parameters: {
incremental_output: true,
},
});
}

function getHistory(history: ChatHistory) {
const array = [];
// 排除最后一条 history,因为是本次刚发的消息
for (let i = 0; i < history.length - 1; i++) {
const chat = history[i];
array.push({
user: chat.userMsg,
bot: chat.assistantMsg,
});
}
return array;
}

function getResult(resultText: string) {
const lines = resultText.split("\n");
for (const line of lines) {
if (line.startsWith("data:")) {
const data = JSON.parse(line.slice(5));
return data;
}
}
}

调用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
await callAi({
text: '你好吗',
history: [{
userMsg: '你好',
assistantMsg: '你好呀',
}],
onMessage: (msg) => {
console.log(msg);
},
});
} catch (e: any) {
console.error(e);
}

讯飞星火、阿里云通义千问 Web 端调用示例

https://www.imaegoo.com/2023/xinghuo-web/

作者

iMaeGoo

发布于

2023-11-14

更新于

2023-11-14

许可协议

CC BY 4.0

评论