私有 AI 和 OpenAI 兼容的 API
现在很多私有化部署的 GPT 正在逐渐变多,且 运算方法在不断的更新,优化也越来越好,使得在笔电上跑一个 GPT 都已经不是问题了。
所以还是有必要记录一下我搭建的私有GPT和对外开放API的坑的。
私有 GPT
方式方法软件都有很多:
OpenAI 的 API 文档:
https://platform.openai.com/docs/api-reference/chat/create
ChatGLM(chatglm.cpp)
这是一个 c++ 实现的 ChatGLM,效率非常惊人,运行中占用的资源极低。
但是如果想要运行 openai 兼容的 API,有点小坑
1. 安装依赖
issue: cd 到其他目录就不会有 init 冲突问题
# 不要在根目录下安装api的依赖,会有点问题
# No module named 'chatglm_cpp._C' 仓库的最外层目录中 init 文件中引入了这个包,同时 pip 拉取的依赖中的 init 文件 也有这个包,导致冲突。 cd 到其他目录就不会有 init 冲突问题。 至于 pydantic_settings 包报错,可能是拉取依赖的问题
cd chatglm.cpp/chatglm_cp
pip install 'chatglm-cpp[api]'
但是还是会安装失败,尝试启动一下 看缺少什么 手动安装即可
2. 启动:
命令行启动:
MODEL=/opt/chatglm.cpp/chatglm3-ggml.bin uvicorn chatglm_cpp.openai_api:app --host 0.0.0.0 --port 9000
systemd:
如果不使用root账户运行 则必须在执行命令前手动让用户环境载入一下,否则python的依赖找不到
[Unit]
Description=chatglm cpp
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
WorkingDirectory=/opt/chatglm.cpp/chatglm_cpp
User=wayne
Group=wayne
Environment="MODEL=/opt/chatglm.cpp/chatglm3-ggml.bin"
ExecStart=/bin/bash --login -c "source ~/.profile; uvicorn chatglm_cpp.openai_api:app --host 0.0.0.0 --port 9000"
PrivateTmp=true
[Install]
WantedBy=multi-user.target
接下来就可以正常使用了,这个API做的非常好,可以兼容各种客户端
Cloudflare AI
Workers AI allows you to run machine learning models, on the Cloudflare network, from your own code – whether that be from Workers, Pages, or anywhere via the Cloudflare API.
OpenAI 兼容 API 改造
调用 CF AI 的最好方式还是 worker, 那么只需要考虑怎么让输入输出在worker里处理就可以了
虽然我会讲的很简单,但是主要会面临两个问题。
- 判断是否流传输,根据可选项来调整输出方式(流传输是固定格式,有些不一样,且很多客户端只支持流传输)
- CF 目前还不支持流传输,所以这不仅是一个 API 代理
- 让 worker 流式传输
1. 简单的方式进行鉴权
CF AI 是有使用限制的,需要使用一个 token 来避免别人薅你
const authorization = request.headers.get("Authorization");
if (authorization !== `Bearer ${token}`) return Response.json({"message": "No permission invoke API"});
2. 接收请求和调用 CF AI
这部分还是比较简单,好在入参是一样的,交给CF算就成了,而且 CF 算的非常快。
// messages - chat style input
let chat = {
messages: content.messages
};
let response = await ai.run(`@cf/meta/${cloudflare_model}`, chat);
3. 普通结果返回(Json)
其实相当多的参数都是写死的,包括 usage 部分,对于大多数客户端都没用,而且 CF 的计算公式不一样,所以不太能用得到。
let task =
{
"id": "cloudflare-ai",
"object": "chat.completion",
"created": new Date().getTime() / 1000,
"model": cloudflare_model,
"system_fingerprint": requestId,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": content,
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 10
}
}
return Response.json(task);
4. 流响应
流响应是这个过程中比较难搞的部分,第一是因为大多客户端只有这种方式才能正常工作,例如 CodeGPT,第二是,worker 的 API 并没有详细说明这部分,所以这部分需要你了解有关 HTTP 流传输的相关知识。
可以留意下 下文的参考部分,非常有用
/**
* write data to writer stream
* @param {WritableStream} writable
* @param {Array} msgBodyStream
*/
async function writeToStream(writable, msgBodyStream) {
const writer = writable.getWriter();
for (let msg of msgBodyStream) {
setTimeout(async () => {
const encoder = new TextEncoder();
// 这是 OpenAI API 固定的格式,只能这么写,别的就不对
await writer.write(encoder.encode(`data: ${JSON.stringify(msg)}` + '\n\n'));
}, 200)
}
}
const streamRender = (content) => {
const {readable, writable} = new TransformStream()
// 因为 CF 响应的太快了,而且不知道怎么拆分句子,所以,stream 简单的粗暴的分为3部分
let stream = [
// 固定开头
{"object": "chat.completion.chunk", "choices": [{"delta": {"role": "assistant"}}]},
// CF AI 响应的内容
{"object": "chat.completion.chunk", "choices": [{"delta": {"content": content}}]},
// 固定停止 finish_reason:stop
{"object": "chat.completion.chunk", "choices": [{"delta": {}, "finish_reason": "stop"}]}
]
let headers = new Headers();
headers.append('Content-Type', 'text/event-stream');
headers.append('Cache-Control', 'no-cache');
const init = {"status": 200, "statusText": "ok", "headers": headers};
writeToStream(writable, stream);
return new Response(readable, init);
}