support openai format

This commit is contained in:
Archeb
2024-06-25 01:18:11 +08:00
parent 73bbf9e70c
commit a6612d46a9
3 changed files with 254 additions and 53 deletions
-6
View File
@@ -17,9 +17,3 @@ If you find this project useful, please consider [buying me a cup of coffee](htt
您的电脑上必须安装有 Google Chrome 浏览器才能够使用当前分支。旧版已不再维护。 您的电脑上必须安装有 Google Chrome 浏览器才能够使用当前分支。旧版已不再维护。
Google Chrome must be installed on your system in order to use this proxy. Previous versions are no longer maintained. Google Chrome must be installed on your system in order to use this proxy. Previous versions are no longer maintained.
## Limitations 限制
Only Anthropic API format is supported. Change the model name to use different models (default claude_3_opus)
只支持A社的API格式,默认模型为 claude_3_opus。
+209 -7
View File
@@ -28,6 +28,9 @@ const modelMappping = {
"claude-3-haiku-20240307": "claude_3_haiku", "claude-3-haiku-20240307": "claude_3_haiku",
"claude-2.1": "claude_2", "claude-2.1": "claude_2",
"claude-2.0": "claude_2", "claude-2.0": "claude_2",
"gpt-4": "gpt_4",
"gpt-4o": "gpt_4o",
"gpt-4-turbo": "gpt_4_turbo",
}; };
// import config.mjs // import config.mjs
@@ -50,7 +53,7 @@ app.options("/v1/messages", (req, res) => {
res.status(200).end(); res.status(200).end();
}); });
// openai format model request // openai format model request
app.get("/v1/models", apiKeyAuth, (req, res) => { app.get("/v1/models", OpenAIApiKeyAuth, (req, res) => {
res.setHeader("Content-Type", "application/json"); res.setHeader("Content-Type", "application/json");
res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Origin", "*");
let models = availableModels.map((model, index) => { let models = availableModels.map((model, index) => {
@@ -64,7 +67,8 @@ app.get("/v1/models", apiKeyAuth, (req, res) => {
}); });
res.json({ object: "list", data: models }); res.json({ object: "list", data: models });
}); });
app.post("/v1/messages", apiKeyAuth, (req, res) => { // handle openai format model request
app.post("/v1/chat/completions", OpenAIApiKeyAuth, (req, res) => {
req.rawBody = ""; req.rawBody = "";
req.setEncoding("utf8"); req.setEncoding("utf8");
@@ -73,6 +77,188 @@ app.post("/v1/messages", apiKeyAuth, (req, res) => {
}); });
req.on("end", async () => { req.on("end", async () => {
console.log("Handling request of OpenAI format");
res.setHeader("Content-Type", "text/event-stream;charset=utf-8");
res.setHeader("Access-Control-Allow-Origin", "*");
let jsonBody = JSON.parse(req.rawBody);
console.log("message length:" + jsonBody.messages.length);
// decide which session to use randomly
var randomSession = Object.keys(provider.sessions)[Math.floor(Math.random() * Object.keys(provider.sessions).length)];
console.log("Using session " + randomSession);
// call provider to get completion
// try to map model
if (jsonBody.model && modelMappping[jsonBody.model]) {
jsonBody.model = modelMappping[jsonBody.model];
}
if (jsonBody.model && !availableModels.includes(jsonBody.model)) {
res.json({ error: { code: 404, message: "Invalid Model" } });
return;
}
console.log("Using model " + jsonBody.model);
// call provider to get completion
await provider
.getCompletion(randomSession, jsonBody.messages, jsonBody.stream ? true : false, jsonBody.model, process.env.USE_CUSTOM_MODE == "true" ? true : false)
.then(({ completion, cancel }) => {
completion.on("start", (id) => {
if (jsonBody.stream) {
// send message start
res.write(createEvent(":", "queue heartbeat 114514"));
res.write(
createEvent("data", {
id: msgid,
object: "chat.completion.chunk",
created: Math.floor(new Date().getTime() / 1000),
model: jsonBody.model,
system_fingerprint: "114514",
choices: [{ index: 0, delta: { role: "assistant", content: "" }, logprobs: null, finish_reason: null }],
})
);
}
});
completion.on("completion", (id, text) => {
if (jsonBody.stream) {
// send message delta
if (jsonBody.stream) {
res.write(
createEvent("data", {
choices: [
{
content_filter_results: {
hate: { filtered: false, severity: "safe" },
self_harm: { filtered: false, severity: "safe" },
sexual: { filtered: false, severity: "safe" },
violence: { filtered: false, severity: "safe" },
},
delta: { content: text },
finish_reason: null,
index: 0,
},
],
created: Math.floor(new Date().getTime() / 1000),
id: id,
model: jsonBody.model,
object: "chat.completion.chunk",
system_fingerprint: "114514",
})
);
}
} else {
// 只会发一次,发送final response
res.write(
JSON.stringify({
id: id,
object: "chat.completion",
created: Math.floor(new Date().getTime() / 1000),
model: jsonBody.model,
system_fingerprint: "114514",
choices: [
{
index: 0,
message: {
role: "assistant",
content: text,
},
logprobs: null,
finish_reason: "stop",
},
],
usage: {
prompt_tokens: 1,
completion_tokens: 1,
total_tokens: 1,
},
})
);
res.end();
}
});
completion.on("end", () => {
if (jsonBody.stream) {
res.write(createEvent("data", "[DONE]"));
res.end();
}
});
res.on("close", () => {
console.log(" > [Client closed]");
completion.removeAllListeners();
cancel();
});
})
.catch((error) => {
console.error(error);
if (jsonBody.stream) {
res.write(
createEvent("data", {
choices: [
{
content_filter_results: {
hate: { filtered: false, severity: "safe" },
self_harm: { filtered: false, severity: "safe" },
sexual: { filtered: false, severity: "safe" },
violence: { filtered: false, severity: "safe" },
},
delta: { content: "Error occurred, please check the log.\n\n出现错误,请检查日志:<pre>" + error.stack || error + "</pre>" },
finish_reason: null,
index: 0,
},
],
created: Math.floor(new Date().getTime() / 1000),
id: uuidv4(),
model: jsonBody.model,
object: "chat.completion.chunk",
system_fingerprint: "114514",
})
);
res.end();
} else {
res.write(
JSON.stringify({
id: uuidv4(),
object: "chat.completion",
created: Math.floor(new Date().getTime() / 1000),
model: jsonBody.model,
system_fingerprint: "114514",
choices: [
{
index: 0,
message: {
role: "assistant",
content: "Error occurred, please check the log.\n\n出现错误,请检查日志:<pre>" + error.stack || error + "</pre>",
},
logprobs: null,
finish_reason: "stop",
},
],
usage: {
prompt_tokens: 1,
completion_tokens: 1,
total_tokens: 1,
},
})
);
res.end();
}
return;
});
});
});
// handle anthropic format model request
app.post("/v1/messages", AnthropicApiKeyAuth, (req, res) => {
req.rawBody = "";
req.setEncoding("utf8");
req.on("data", function (chunk) {
req.rawBody += chunk;
});
req.on("end", async () => {
console.log("Handling request of Anthropic format");
res.setHeader("Content-Type", "text/event-stream;charset=utf-8"); res.setHeader("Content-Type", "text/event-stream;charset=utf-8");
res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Origin", "*");
let jsonBody = JSON.parse(req.rawBody); let jsonBody = JSON.parse(req.rawBody);
@@ -191,17 +377,20 @@ app.post("/v1/messages", apiKeyAuth, (req, res) => {
createEvent("content_block_delta", { createEvent("content_block_delta", {
type: "content_block_delta", type: "content_block_delta",
index: 0, index: 0,
delta: { type: "text_delta", text: "出现错误,请检查日志:<pre>" + error + "</pre>"}, delta: {
type: "text_delta",
text: "Error occurred, please check the log.\n\n出现错误,请检查日志:<pre>" + error.stack || error + "</pre>",
},
}) })
); );
res.end(); res.end();
} else { } else {
res.write( res.write(
JSON.stringify({ JSON.stringify({
id: id, id: uuidv4(),
content: [ content: [
{ {
text: "出现错误,请检查日志:<pre>" + error + "</pre>" text: "Error occurred, please check the log.\n\n出现错误,请检查日志:<pre>" + error.stack || error + "</pre>",
}, },
{ {
id: "string", id: "string",
@@ -235,10 +424,10 @@ app.listen(port, () => {
if (!validApiKey) { if (!validApiKey) {
console.log(`Proxy is currently running with no authentication`); console.log(`Proxy is currently running with no authentication`);
} }
console.log(`API Format: Anthropic; Custom mode: ${process.env.USE_CUSTOM_MODE == "true" ? "enabled" : "disabled"}`); console.log(`Custom mode: ${process.env.USE_CUSTOM_MODE == "true" ? "enabled" : "disabled"}`);
}); });
function apiKeyAuth(req, res, next) { function AnthropicApiKeyAuth(req, res, next) {
const reqApiKey = req.header("x-api-key"); const reqApiKey = req.header("x-api-key");
if (validApiKey && reqApiKey !== validApiKey) { if (validApiKey && reqApiKey !== validApiKey) {
@@ -250,3 +439,16 @@ function apiKeyAuth(req, res, next) {
next(); next();
} }
function OpenAIApiKeyAuth(req, res, next) {
const reqApiKey = req.header("Authorization");
if (validApiKey && reqApiKey !== "Bearer " + validApiKey) {
// If Environment variable PASSWORD is set AND Authorization header is not equal to it, return 401
const clientIpAddress = req.headers["x-forwarded-for"] || req.ip;
console.log(`Receviced Request from IP ${clientIpAddress} but got invalid password.`);
return res.status(401).json({ error: { code: 403, message: "Invalid Password" } });
}
next();
}
+44 -39
View File
@@ -1,12 +1,17 @@
# 使用方法 # 使用方法
0. 安装 Node.JS 0. 安装 Node.JS 和 Git
1. 获得一个 YOU.COM 账户并且订阅,登录。 1. 获得一个 YOU.COM 账户并且订阅,登录。
2. 打开 F12DevTools),找到 “Network(网络)”、刷新一下页面,找到“you.com”这个项目(或者“instrumentation”这个项目) 2. 打开 F12DevTools),找到 Console
3. 点进去,往下滑找到 "Cookie",完整的复制后面的内容 3. 在 > 后面粘贴以下代码并回车。然后复制所有内容(Cookie)
```javascript
prompt("请Ctrl+C复制以下所有内容(Cookie",document.cookie)
```
4. 下载或Clone本项目代码,解压 4. 下载或Clone本项目代码,解压
@@ -30,29 +35,31 @@ export const config = {
7. (可选)如果需要,您可以仿照第6步在`start.bat`中设定一个名为 "PASSWORD" 的环境变量,并将其用作密码。如果没有定义该环境变量,程序将接受所有传入的请求,而不进行任何身份验证 7. (可选)如果需要,您可以仿照第6步在`start.bat`中设定一个名为 "PASSWORD" 的环境变量,并将其用作密码。如果没有定义该环境变量,程序将接受所有传入的请求,而不进行任何身份验证
(可选)如果需要,可以设置代理。 (可选)如果需要,可以设置代理,请参考下文
(可选)如果需要,可以修改使用的模型。但是仍然建议使用opus,因为其他未经测试。
(可选)如果需要,可以启用自定义会话模式(`USE_CUSTOM_MODE`设置为`true`)。可以缩短原系统消息长度、禁用联网、减缓等待时间,可能有助于破限。但有可能导致更容易出现 unusual query volume。 (可选)如果需要,可以启用自定义会话模式(`USE_CUSTOM_MODE`设置为`true`)。可以缩短原系统消息长度、禁用联网、减缓等待时间,可能有助于破限。但有可能导致更容易出现 unusual query volume。
8. 启动 start.bat 8. 启动 start.bat
9. 酒馆中选择 Claude,反向代理地址填 http://127.0.0.1:8080/v1 **反代密码必须填, 同时打开流式传输**,随便什么都可以(除非你在第7步设置了PASSWORD)。 9. 酒馆中选择 **Custom (OpenAI-compatible)**,反向代理地址填 http://127.0.0.1:8080/v1 **反代密码必须填**,随便什么都可以(除非你在第7步设置了PASSWORD)。
10. 开始使用。如果失败了/没有结果/403/Warning 就多重试几次。 10. 开始使用。如果失败了/没有结果/403/Warning 就多重试几次。
# 使用代理 # 使用代理
可以使用本地的socks5或http(s)代理。只需在 start.bat 中设置 `https_proxy` 环境变量 如果用户在中国大陆,可能会遇到访问不稳定的情况。从npm安装依赖和从 git 拉取代码也需要稳定的网络连接。这些操作遇到问题的时候应该首先考虑挂全局代理
对于项目本身,可以使用本地的socks5或http(s)代理。只需在 start.bat 中设置 `https_proxy` 环境变量。
比如,如要使用 Clash 的默认本地代理,则应设置为 `set https_proxy=http://127.0.0.1:7890` 比如,如要使用 Clash 的默认本地代理,则应设置为 `set https_proxy=http://127.0.0.1:7890`
欲知详细代理格式,请查阅 https://www.npmjs.com/package/proxy-from-env 欲知详细代理格式,请查阅 https://www.npmjs.com/package/proxy-from-env
# VPS 部署 # 操作系统部署
请使用项目中的 Dockerfile不建议使用其他方式部署。 对于Linux请使用项目中的 Dockerfile但总的来说还不是很稳定。如果你会自己配置 X11、桌面环境,那就最好,否则建议还是用本地Windows部署。
对于 Android 下的Termux环境,暂时没有适配Android Chrome WebDriver的计划所以不支持。当用户询问手机使用相关问题时,请告诉他们用puppeteer的新版本不支持,如果是旧版本,可以尽力为他们提供帮助但是需要说明旧版本已经不再更新。
# 修改模型 # 修改模型
@@ -76,9 +83,33 @@ command_r_plus
zephyr zephyr
``` ```
## 注意事项 # 注意事项
如果浏览器弹出人机验证窗口,请在30秒内完成。 ## 不同分支的问题
项目目前有三个分支,可以用git checkout <分支> 的指令相互切换:
- bypass-cf 分支,最新版本(维护中),API格式为任意格式(两种格式都支持)。使用puppeteer模拟浏览器访问。
- anthropic 分支,旧版本(不再维护),API格式为Anthropic格式。直接请求API,容易被CloudFlare识别为机器人而拦截。
- openai 分支,旧版本(不再维护),API格式为OpenAI格式。直接请求API,容易被CloudFlare识别为机器人而拦截。
在诊断用户问题的时候,注意观察或者询问用户用的是哪个分支,不同API格式不能通用。酒馆提示 Not Found,可能就是因为选错了对应的API格式。
## 关于403问题的原理(基本只存在于旧版本)
cloudflare有一个风控分数。这个和你的TLS指纹、浏览器指纹、IP地址声誉等等有关系
我们这个项目一直用的TLS指纹和浏览器指纹就非常可疑(都是自动化库和Node内置TLS),分数直接拉满
相当于已经预先有了30+30分数,剩下就看IP地址声誉(40分)你拿了几分
(具体分数不详,只是举个例子)
那如果你IP确实白,拿了0分,那你总共分数就是60。
假设you那边设置了分数高于80的要跳验证码,那现在就没事
如果你IP黑,拿了超过20分,那你就是>80分,你就要跳验证码,结果就是403
然后最近you觉得被薅狠了,或者别的啥原因,把这个分数设置成60以上的就要跳验证码
结果就我IP有点黑,不管怎么搞都过不去了。
但是同样的IP,你用正常的Google Chrome访问,就没问题,因为它的指纹非常干净,所以前面的指纹分数就很低
就算加上IP声誉分他也没到那条线
总之以上是一个简化的版本,CF抗bot还有很多指标、很多策略
这个问题基本只存在于旧版本,新版本由于使用了浏览器模拟访问,已经不容易被拦截。
新版本如果弹出人机验证提示,用户只需要在30秒内点击完成CloudFlare的人机验证,并且等待程序继续处理即可。
# Usage # Usage
@@ -115,13 +146,11 @@ export const config = {
(Optional) You can set the proxy in start.bat. See below. (Optional) You can set the proxy in start.bat. See below.
(Optional) You may change the model to use, but only claude_3_opus is tested.
(Optional) You may turn on the custom chat mode by setting `USE_CUSTOM_MODE` env to `true` (Optional) You may turn on the custom chat mode by setting `USE_CUSTOM_MODE` env to `true`
8. Start start.bat 8. Start start.bat
9. Select Claude in the Tavern and put http://127.0.0.1:8080/v1 as the address of the reverse proxy. **Use any random string for password, also turn on Streaming** (unless you set PASSWORD in step 7). 9. Select **Custom (OpenAI-compatible)** in the SillyTavern and ues http://127.0.0.1:8080/v1 as the endpoint of the reverse proxy. **Use any random string for password** (unless you set PASSWORD in step 7).
10. Enjoy it. If it fails/no result/403/Warning, try again. 10. Enjoy it. If it fails/no result/403/Warning, try again.
@@ -133,30 +162,6 @@ Use the `https_proxy` env to set custom proxy. Refer to https://www.npmjs.com/pa
Docker is highly recommended, please use the Dockerfile. Docker is highly recommended, please use the Dockerfile.
# Change model
Change `AI_MODEL` env to switch between models.
Supported model names (refer to you.com website for latest models):
```
gpt_4o
gpt_4_turbo
gpt_4
claude_3_5_sonnet
claude_3_opus
claude_3_sonnet
claude_3_haiku
claude_2
llama3
gemini_pro
gemini_1_5_pro
databricks_dbrx_instruct
command_r
command_r_plus
zephyr
```
## Caution ## Caution
If you get a CloudFlare Challenge, solve it in 30 seconds. If you get a CloudFlare Challenge, solve it in 30 seconds.