support openai format
This commit is contained in:
@@ -17,9 +17,3 @@ If you find this project useful, please consider [buying me a cup of coffee](htt
|
||||
您的电脑上必须安装有 Google Chrome 浏览器才能够使用当前分支。旧版已不再维护。
|
||||
|
||||
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。
|
||||
|
||||
@@ -28,6 +28,9 @@ const modelMappping = {
|
||||
"claude-3-haiku-20240307": "claude_3_haiku",
|
||||
"claude-2.1": "claude_2",
|
||||
"claude-2.0": "claude_2",
|
||||
"gpt-4": "gpt_4",
|
||||
"gpt-4o": "gpt_4o",
|
||||
"gpt-4-turbo": "gpt_4_turbo",
|
||||
};
|
||||
|
||||
// import config.mjs
|
||||
@@ -50,7 +53,7 @@ app.options("/v1/messages", (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
// 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("Access-Control-Allow-Origin", "*");
|
||||
let models = availableModels.map((model, index) => {
|
||||
@@ -64,7 +67,8 @@ app.get("/v1/models", apiKeyAuth, (req, res) => {
|
||||
});
|
||||
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.setEncoding("utf8");
|
||||
|
||||
@@ -73,6 +77,188 @@ app.post("/v1/messages", apiKeyAuth, (req, res) => {
|
||||
});
|
||||
|
||||
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("Access-Control-Allow-Origin", "*");
|
||||
let jsonBody = JSON.parse(req.rawBody);
|
||||
@@ -99,7 +285,7 @@ app.post("/v1/messages", apiKeyAuth, (req, res) => {
|
||||
// call provider to get completion
|
||||
await provider
|
||||
.getCompletion(randomSession, jsonBody.messages, jsonBody.stream ? true : false, proxyModel, process.env.USE_CUSTOM_MODE == "true" ? true : false)
|
||||
.then(({completion, cancel}) => {
|
||||
.then(({ completion, cancel }) => {
|
||||
completion.on("start", (id) => {
|
||||
if (jsonBody.stream) {
|
||||
// send message start
|
||||
@@ -191,17 +377,20 @@ app.post("/v1/messages", apiKeyAuth, (req, res) => {
|
||||
createEvent("content_block_delta", {
|
||||
type: "content_block_delta",
|
||||
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();
|
||||
} else {
|
||||
res.write(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
id: uuidv4(),
|
||||
content: [
|
||||
{
|
||||
text: "出现错误,请检查日志:<pre>" + error + "</pre>"
|
||||
text: "Error occurred, please check the log.\n\n出现错误,请检查日志:<pre>" + error.stack || error + "</pre>",
|
||||
},
|
||||
{
|
||||
id: "string",
|
||||
@@ -235,10 +424,10 @@ app.listen(port, () => {
|
||||
if (!validApiKey) {
|
||||
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");
|
||||
|
||||
if (validApiKey && reqApiKey !== validApiKey) {
|
||||
@@ -250,3 +439,16 @@ function apiKeyAuth(req, res, 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();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
|
||||
# 使用方法
|
||||
|
||||
0. 安装 Node.JS
|
||||
0. 安装 Node.JS 和 Git
|
||||
|
||||
1. 获得一个 YOU.COM 账户并且订阅,登录。
|
||||
|
||||
2. 打开 F12(DevTools),找到 “Network(网络)”、刷新一下页面,找到“you.com”这个项目(或者“instrumentation”这个项目)
|
||||
2. 打开 F12(DevTools),找到 Console
|
||||
|
||||
3. 点进去,往下滑找到 "Cookie",完整的复制后面的内容。
|
||||
3. 在 > 后面粘贴以下代码并回车。然后复制所有内容(Cookie)。
|
||||
|
||||
```javascript
|
||||
prompt("请Ctrl+C复制以下所有内容(Cookie)",document.cookie)
|
||||
```
|
||||
|
||||
4. 下载或Clone本项目代码,解压
|
||||
|
||||
@@ -30,29 +35,31 @@ export const config = {
|
||||
|
||||
7. (可选)如果需要,您可以仿照第6步在`start.bat`中设定一个名为 "PASSWORD" 的环境变量,并将其用作密码。如果没有定义该环境变量,程序将接受所有传入的请求,而不进行任何身份验证
|
||||
|
||||
(可选)如果需要,可以设置代理。
|
||||
|
||||
(可选)如果需要,可以修改使用的模型。但是仍然建议使用opus,因为其他未经测试。
|
||||
(可选)如果需要,可以设置代理,请参考下文。
|
||||
|
||||
(可选)如果需要,可以启用自定义会话模式(`USE_CUSTOM_MODE`设置为`true`)。可以缩短原系统消息长度、禁用联网、减缓等待时间,可能有助于破限。但有可能导致更容易出现 unusual query volume。
|
||||
|
||||
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 就多重试几次。
|
||||
|
||||
# 使用代理
|
||||
|
||||
可以使用本地的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`
|
||||
|
||||
欲知详细代理格式,请查阅 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
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
# 注意事项
|
||||
|
||||
如果浏览器弹出人机验证窗口,请在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
|
||||
@@ -115,13 +146,11 @@ export const config = {
|
||||
|
||||
(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`
|
||||
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
# 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
|
||||
|
||||
If you get a CloudFlare Challenge, solve it in 30 seconds.
|
||||
|
||||
Reference in New Issue
Block a user