21 Commits

Author SHA1 Message Date
Vinlic
b8134a64a5 Release 0.0.15 2024-03-19 15:56:49 +08:00
Vinlic
c9b3574b0b 增加伪装请求,企图降低封号几率 2024-03-19 15:56:27 +08:00
Vinlic
eef674eac8 处理token统计为0导致部分客户端无法识别问题 2024-03-17 02:23:29 +08:00
Vinlic
e530317486 update README 2024-03-16 14:02:21 +08:00
Vinlic
a5beade70a Release 0.0.13 2024-03-16 04:47:15 +08:00
Vinlic
1395278a6e 增加流响应错误时重试机制 2024-03-16 04:46:39 +08:00
Vinlic
08a4b2e720 update README 2024-03-16 04:46:28 +08:00
Vinlic科技
2f26d29de1 Merge pull request #8 from Yanyutin753/master
作者大大,pr一个github action实现自动打包docker镜像
2024-03-15 19:09:22 +08:00
Yanyutin753
3bb24b26d3 feat docker-image.yml 2024-03-15 18:55:12 +08:00
Yanyutin753
dd8bb923a8 feat github action 2024-03-15 18:54:10 +08:00
Vinlic
b3b6daf1ef Release 0.0.12 2024-03-15 18:48:57 +08:00
Vinlic
25606dc1ea 请求载荷大小限制提高到100mb,增加新的异常信息 2024-03-15 18:34:55 +08:00
Vinlic
fc5c13b650 Release 0.0.11 2024-03-14 23:38:30 +08:00
Vinlic
4949b16091 支持多路refresh_token 2024-03-14 23:21:22 +08:00
Vinlic
17809274a5 update 2024-03-14 15:52:27 +08:00
Vinlic
228d18712b 补充Nginx反代优化 2024-03-14 15:09:46 +08:00
Vinlic
2da3e804f3 增加响应耗时比较 2024-03-14 14:43:30 +08:00
Vinlic
efdf5a229c 流传输耗时显示 2024-03-13 22:58:50 +08:00
Vinlic
4083abf4e0 Release 0.0.10 2024-03-13 22:47:50 +08:00
Vinlic
153ebdc1c8 添加流传输完毕提示 2024-03-13 22:44:14 +08:00
Vinlic
c54f89e9f8 修复新token的存储 2024-03-13 22:39:35 +08:00
10 changed files with 285 additions and 97 deletions

46
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Build and Push Docker Image
on:
release:
types: [created]
workflow_dispatch:
inputs:
tag:
description: 'Tag Name'
required: true
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set tag name
id: tag_name
run: |
if [ "${{ github.event_name }}" = "release" ]; then
echo "::set-output name=tag::${GITHUB_REF#refs/tags/}"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "::set-output name=tag::${{ github.event.inputs.tag }}"
fi
- name: Build and push Docker image with Release tag
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: |
vinlic/kimi-free-api:${{ steps.tag_name.outputs.tag }}
vinlic/kimi-free-api:latest
platforms: linux/amd64,linux/arm64
build-args: TARGETPLATFORM=${{ matrix.platform }}

View File

@@ -5,7 +5,7 @@
![](https://img.shields.io/github/forks/llm-red-team/kimi-free-api.svg) ![](https://img.shields.io/github/forks/llm-red-team/kimi-free-api.svg)
![](https://img.shields.io/docker/pulls/vinlic/kimi-free-api.svg) ![](https://img.shields.io/docker/pulls/vinlic/kimi-free-api.svg)
支持高速流式输出、支持多轮对话、支持联网搜索、支持长文档解读、支持图像解析,零配置部署,自动清理会话痕迹。 支持高速流式输出、支持多轮对话、支持联网搜索、支持长文档解读、支持图像解析,零配置部署,多路token支持自动清理会话痕迹。
与ChatGPT接口完全兼容。 与ChatGPT接口完全兼容。
@@ -15,6 +15,7 @@
* [在线体验](#在线体验) * [在线体验](#在线体验)
* [效果示例](#效果示例) * [效果示例](#效果示例)
* [接入准备](#接入准备) * [接入准备](#接入准备)
* [多账号接入](#多账号接入)
* [Docker部署](#Docker部署) * [Docker部署](#Docker部署)
* [Docker-compose部署](#Docker-compose部署) * [Docker-compose部署](#Docker-compose部署)
* [原生部署](#原生部署) * [原生部署](#原生部署)
@@ -22,14 +23,16 @@
* [对话补全](#对话补全) * [对话补全](#对话补全)
* [文档解读](#文档解读) * [文档解读](#文档解读)
* [图像解析](#图像解析) * [图像解析](#图像解析)
* [注意事项](#注意事项)
* [Nginx反代优化](#Nginx反代优化)
## 声明 ## 声明
仅限自用,禁止对外提供服务,避免对官方造成服务压力,否则风险自担! 仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!
仅限自用,禁止对外提供服务,避免对官方造成服务压力,否则风险自担! 仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!
仅限自用,禁止对外提供服务,避免对官方造成服务压力,否则风险自担! 仅限自用,禁止对外提供服务或商用,避免对官方造成服务压力,否则风险自担!
## 在线体验 ## 在线体验
@@ -59,14 +62,34 @@ https://udify.app/chat/Po0F6BMJ15q5vu2P
![图像解析](./doc/example-3.png) ![图像解析](./doc/example-3.png)
### 响应流畅度一致
![响应流畅度一致](https://github.com/LLM-Red-Team/kimi-free-api/assets/20235341/48c7ec00-2b03-46c4-95d0-452d3075219b)
### 100线程并发测试
![100线程并发测试](./doc/example-7.jpg)
## 接入准备 ## 接入准备
从 [kimi.moonshot.cn](https://kimi.moonshot.cn) 获取refresh_token 从 [kimi.moonshot.cn](https://kimi.moonshot.cn) 获取refresh_token
进入kimi随便发起一个对话然后F12打开开发者工具从Application > Local Storage中找到refresh_token的值这将作为Authorization的Bearer KEY值。 进入kimi随便发起一个对话然后F12打开开发者工具从Application > Local Storage中找到`refresh_token`的值这将作为Authorization的Bearer Token值`Authorization: Bearer TOKEN`
![example0](./doc/example-0.png) ![example0](./doc/example-0.png)
如果你看到的`refresh_token`是一个数组,请使用`.`拼接起来再使用。
![example8](./doc/example-8.jpg)
### 多账号接入
目前kimi限制普通账号每3小时内只能进行30轮长文本的问答你可以通过提供多个账号的refresh_token并使用`,`拼接提供:
`Authorization: Bearer TOKEN1,TOKEN2,TOKEN3`
每次请求服务会从中挑选一个。
## Docker部署 ## Docker部署
请准备一台具有公网IP的服务器并将8000端口开放。 请准备一台具有公网IP的服务器并将8000端口开放。
@@ -208,9 +231,9 @@ Authorization: Bearer [refresh_token]
} }
], ],
"usage": { "usage": {
"prompt_tokens": 0, "prompt_tokens": 1,
"completion_tokens": 0, "completion_tokens": 1,
"total_tokens": 0 "total_tokens": 2
}, },
"created": 1710152062 "created": 1710152062
} }
@@ -270,9 +293,9 @@ Authorization: Bearer [refresh_token]
} }
], ],
"usage": { "usage": {
"prompt_tokens": 0, "prompt_tokens": 1,
"completion_tokens": 0, "completion_tokens": 1,
"total_tokens": 0 "total_tokens": 2
}, },
"created": 100920 "created": 100920
} }
@@ -334,11 +357,33 @@ Authorization: Bearer [refresh_token]
} }
], ],
"usage": { "usage": {
"prompt_tokens": 0, "prompt_tokens": 1,
"completion_tokens": 0, "completion_tokens": 1,
"total_tokens": 0 "total_tokens": 2
}, },
"created": 1710123627 "created": 1710123627
} }
``` ```
## 注意事项
### Nginx反代优化
如果您正在使用Nginx反向代理kimi-free-api请添加以下配置项优化流的输出效果优化体验感。
```nginx
# 关闭代理缓冲。当设置为off时Nginx会立即将客户端请求发送到后端服务器并立即将从后端服务器接收到的响应发送回客户端。
proxy_buffering off;
# 启用分块传输编码。分块传输编码允许服务器为动态生成的内容分块发送数据,而不需要预先知道内容的大小。
chunked_transfer_encoding on;
# 开启TCP_NOPUSH这告诉Nginx在数据包发送到客户端之前尽可能地发送数据。这通常在sendfile使用时配合使用可以提高网络效率。
tcp_nopush on;
# 开启TCP_NODELAY这告诉Nginx不延迟发送数据立即发送小数据包。在某些情况下这可以减少网络的延迟。
tcp_nodelay on;
# 设置保持连接的超时时间这里设置为120秒。如果在这段时间内客户端和服务器之间没有进一步的通信连接将被关闭。
keepalive_timeout 120;
```
### Token统计
由于推理侧不再kimi-free-api因此token不可统计将以固定数字返回。

BIN
doc/example-7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
doc/example-8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "kimi-free-api", "name": "kimi-free-api",
"version": "0.0.9", "version": "0.0.15",
"description": "Kimi Free API Server", "description": "Kimi Free API Server",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -4,5 +4,6 @@ export default {
API_REQUEST_FAILED: [-2001, '请求失败'], API_REQUEST_FAILED: [-2001, '请求失败'],
API_TOKEN_EXPIRES: [-2002, 'Token已失效'], API_TOKEN_EXPIRES: [-2002, 'Token已失效'],
API_FILE_URL_INVALID: [-2003, '远程文件URL非法'], API_FILE_URL_INVALID: [-2003, '远程文件URL非法'],
API_FILE_EXECEEDS_SIZE: [-2004, '远程文件超出大小'] API_FILE_EXECEEDS_SIZE: [-2004, '远程文件超出大小'],
API_CHAT_STREAM_PUSHING: [-2005, '已有对话流正在输出']
} }

View File

@@ -14,13 +14,17 @@ import util from '@/lib/util.ts';
const MODEL_NAME = 'kimi'; const MODEL_NAME = 'kimi';
// access_token有效期 // access_token有效期
const ACCESS_TOKEN_EXPIRES = 300; const ACCESS_TOKEN_EXPIRES = 300;
// 最大重试次数
const MAX_RETRY_COUNT = 3;
// 重试延迟
const RETRY_DELAY = 5000;
// 伪装headers // 伪装headers
const FAKE_HEADERS = { const FAKE_HEADERS = {
'Accept': '*/*', 'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9',
'Origin': 'https://kimi.moonshot.cn', 'Origin': 'https://kimi.moonshot.cn',
'Cookie': util.generateCookie(), // 'Cookie': util.generateCookie(),
'R-Timezone': 'Asia/Shanghai', 'R-Timezone': 'Asia/Shanghai',
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"', 'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Mobile': '?0',
@@ -53,7 +57,7 @@ async function requestToken(refreshToken: string) {
const result = await axios.get('https://kimi.moonshot.cn/api/auth/token/refresh', { const result = await axios.get('https://kimi.moonshot.cn/api/auth/token/refresh', {
headers: { headers: {
Authorization: `Bearer ${refreshToken}`, Authorization: `Bearer ${refreshToken}`,
Referer: 'https://kimi.moonshot.cn', Referer: 'https://kimi.moonshot.cn/',
...FAKE_HEADERS ...FAKE_HEADERS
}, },
timeout: 15000, timeout: 15000,
@@ -70,7 +74,7 @@ async function requestToken(refreshToken: string) {
} }
})() })()
.then(result => { .then(result => {
if(accessTokenRequestQueueMap[refreshToken]) { if (accessTokenRequestQueueMap[refreshToken]) {
accessTokenRequestQueueMap[refreshToken].forEach(resolve => resolve(result)); accessTokenRequestQueueMap[refreshToken].forEach(resolve => resolve(result));
delete accessTokenRequestQueueMap[refreshToken]; delete accessTokenRequestQueueMap[refreshToken];
} }
@@ -78,13 +82,13 @@ async function requestToken(refreshToken: string) {
return result; return result;
}) })
.catch(err => { .catch(err => {
if(accessTokenRequestQueueMap[refreshToken]) { if (accessTokenRequestQueueMap[refreshToken]) {
accessTokenRequestQueueMap[refreshToken].forEach(resolve => resolve(err)); accessTokenRequestQueueMap[refreshToken].forEach(resolve => resolve(err));
delete accessTokenRequestQueueMap[refreshToken]; delete accessTokenRequestQueueMap[refreshToken];
} }
return err; return err;
}); });
if(_.isError(result)) if (_.isError(result))
throw result; throw result;
return result; return result;
} }
@@ -102,8 +106,10 @@ async function acquireToken(refreshToken: string): Promise<string> {
result = await requestToken(refreshToken); result = await requestToken(refreshToken);
accessTokenMap.set(refreshToken, result); accessTokenMap.set(refreshToken, result);
} }
if (util.unixTimestamp() > result.refreshTime) if (util.unixTimestamp() > result.refreshTime) {
result = await requestToken(refreshToken); result = await requestToken(refreshToken);
accessTokenMap.set(refreshToken, result);
}
return result.accessToken; return result.accessToken;
} }
@@ -122,7 +128,7 @@ async function createConversation(name: string, refreshToken: string) {
}, { }, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: 'https://kimi.moonshot.cn', Referer: 'https://kimi.moonshot.cn/',
...FAKE_HEADERS ...FAKE_HEADERS
}, },
timeout: 15000, timeout: 15000,
@@ -161,43 +167,63 @@ async function removeConversation(convId: string, refreshToken: string) {
* @param messages 参考gpt系列消息格式多轮对话请完整提供上下文 * @param messages 参考gpt系列消息格式多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token * @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索 * @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/ */
async function createCompletion(messages: any[], refreshToken: string, useSearch = true) { async function createCompletion(messages: any[], refreshToken: string, useSearch = true, retryCount = 0) {
logger.info(messages); return (async () => {
logger.info(messages);
// 提取引用文件URL并上传kimi获得引用的文件ID列表 // 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages); const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : []; const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 创建会话 // 伪装调用获取用户信息
const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken); fakeRequest(refreshToken)
.catch(err => logger.error(err));
// 请求流 // 创建会话
const token = await acquireToken(refreshToken); const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken);
const result = await axios.post(`https://kimi.moonshot.cn/api/chat/${convId}/completion/stream`, {
messages: messagesPrepare(messages),
refs,
use_search: useSearch
}, {
headers: {
Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn/chat/${convId}`,
...FAKE_HEADERS
},
// 120秒超时
timeout: 120000,
validateStatus: () => true,
responseType: 'stream'
});
// 接收流为输出文本 // 请求流
const answer = await receiveStream(convId, result.data); const token = await acquireToken(refreshToken);
const result = await axios.post(`https://kimi.moonshot.cn/api/chat/${convId}/completion/stream`, {
messages: messagesPrepare(messages),
refs,
use_search: useSearch
}, {
headers: {
Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn/chat/${convId}`,
...FAKE_HEADERS
},
// 120秒超时
timeout: 120000,
validateStatus: () => true,
responseType: 'stream'
});
// 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略 const streamStartTime = util.timestamp();
removeConversation(convId, refreshToken) // 接收流为输出文本
.catch(err => console.error(err)); const answer = await receiveStream(convId, result.data);
logger.success(`Stream has completed transfer ${util.timestamp() - streamStartTime}ms`);
return answer; // 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(convId, refreshToken)
.catch(err => console.error(err));
return answer;
})()
.catch(err => {
if (retryCount < MAX_RETRY_COUNT) {
logger.error(`Stream response error: ${err.message}`);
logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
return (async () => {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
return createCompletion(messages, refreshToken, useSearch, retryCount + 1);
})();
}
throw err;
});
} }
/** /**
@@ -206,41 +232,92 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
* @param messages 参考gpt系列消息格式多轮对话请完整提供上下文 * @param messages 参考gpt系列消息格式多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token * @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索 * @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/ */
async function createCompletionStream(messages: any[], refreshToken: string, useSearch = true) { async function createCompletionStream(messages: any[], refreshToken: string, useSearch = true, retryCount = 0) {
logger.info(messages); return (async () => {
logger.info(messages);
// 提取引用文件URL并上传kimi获得引用的文件ID列表 // 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages); const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : []; const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 创建会话 // 伪装调用获取用户信息
const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken); fakeRequest(refreshToken)
.catch(err => logger.error(err));
// 请求流 // 创建会话
const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken);
// 请求流
const token = await acquireToken(refreshToken);
const result = await axios.post(`https://kimi.moonshot.cn/api/chat/${convId}/completion/stream`, {
messages: messagesPrepare(messages),
refs,
use_search: useSearch
}, {
// 120秒超时
timeout: 120000,
headers: {
Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn/chat/${convId}`,
...FAKE_HEADERS
},
validateStatus: () => true,
responseType: 'stream'
});
const streamStartTime = util.timestamp();
// 创建转换流将消息格式转换为gpt兼容格式
return createTransStream(convId, result.data, () => {
logger.success(`Stream has completed transfer ${util.timestamp() - streamStartTime}ms`);
// 流传输结束后异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(convId, refreshToken)
.catch(err => console.error(err));
});
})()
.catch(err => {
if (retryCount < MAX_RETRY_COUNT) {
logger.error(`Stream response error: ${err.message}`);
logger.warn(`Try again after ${RETRY_DELAY / 1000}s...`);
return (async () => {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
return createCompletionStream(messages, refreshToken, useSearch, retryCount + 1);
})();
}
throw err;
});
}
/**
* 调用一些接口伪装访问
*
* 随机挑一个
*
* @param refreshToken 用于刷新access_token的refresh_token
*/
async function fakeRequest(refreshToken: string) {
const token = await acquireToken(refreshToken); const token = await acquireToken(refreshToken);
const result = await axios.post(`https://kimi.moonshot.cn/api/chat/${convId}/completion/stream`, { const options = {
messages: messagesPrepare(messages),
refs,
use_search: useSearch
}, {
// 120秒超时
timeout: 120000,
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn/chat/${convId}`, Referer: `https://kimi.moonshot.cn/`,
...FAKE_HEADERS ...FAKE_HEADERS
}, }
validateStatus: () => true, };
responseType: 'stream' await [
}); () => axios.get('https://kimi.moonshot.cn/api/user', options),
() => axios.get('https://kimi.moonshot.cn/api/chat_1m/user/status', options),
// 创建转换流将消息格式转换为gpt兼容格式 () => axios.post('https://kimi.moonshot.cn/api/chat/list', {
return createTransStream(convId, result.data, () => { offset: 0,
// 流传输结束后异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略 size: 50
removeConversation(convId, refreshToken) }, options),
.catch(err => console.error(err)); () => axios.post('https://kimi.moonshot.cn/api/show_case/list', {
}); offset: 0,
size: 4,
enable_cache: true,
order: "asc"
}, options)
][Math.floor(Math.random() * 4)]();
} }
/** /**
@@ -319,7 +396,7 @@ async function preSignUrl(filename: string, refreshToken: string) {
timeout: 15000, timeout: 15000,
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn`, Referer: `https://kimi.moonshot.cn/`,
...FAKE_HEADERS ...FAKE_HEADERS
}, },
validateStatus: () => true validateStatus: () => true
@@ -400,7 +477,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
headers: { headers: {
'Content-Type': mimeType, 'Content-Type': mimeType,
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn`, Referer: `https://kimi.moonshot.cn/`,
...FAKE_HEADERS ...FAKE_HEADERS
}, },
validateStatus: () => true validateStatus: () => true
@@ -416,7 +493,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
}, { }, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn`, Referer: `https://kimi.moonshot.cn/`,
...FAKE_HEADERS ...FAKE_HEADERS
} }
}); });
@@ -429,7 +506,7 @@ async function uploadFile(fileUrl: string, refreshToken: string) {
}, { }, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn`, Referer: `https://kimi.moonshot.cn/`,
...FAKE_HEADERS ...FAKE_HEADERS
} }
}); });
@@ -456,6 +533,8 @@ function checkResult(result: AxiosResponse, refreshToken: string) {
return result.data; return result.data;
if (error_type == 'auth.token.invalid') if (error_type == 'auth.token.invalid')
accessTokenMap.delete(refreshToken); accessTokenMap.delete(refreshToken);
if (error_type == 'chat.user_stream_pushing')
throw new APIException(EX.API_CHAT_STREAM_PUSHING);
throw new APIException(EX.API_REQUEST_FAILED, `[请求kimi失败]: ${message}`); throw new APIException(EX.API_REQUEST_FAILED, `[请求kimi失败]: ${message}`);
} }
@@ -467,7 +546,7 @@ function checkResult(result: AxiosResponse, refreshToken: string) {
*/ */
async function receiveStream(convId: string, stream: any) { async function receiveStream(convId: string, stream: any) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 第一条消息初始化 // 消息初始化
const data = { const data = {
id: convId, id: convId,
model: MODEL_NAME, model: MODEL_NAME,
@@ -475,7 +554,7 @@ async function receiveStream(convId: string, stream: any) {
choices: [ choices: [
{ index: 0, message: { role: 'assistant', content: '' }, finish_reason: 'stop' } { index: 0, message: { role: 'assistant', content: '' }, finish_reason: 'stop' }
], ],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
created: util.unixTimestamp() created: util.unixTimestamp()
}; };
let refContent = ''; let refContent = '';
@@ -573,7 +652,7 @@ function createTransStream(convId: string, stream: any, endCallback?: Function)
} : {}, finish_reason: 'stop' } : {}, finish_reason: 'stop'
} }
], ],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
created created
})}\n\n`; })}\n\n`;
!transStream.closed && transStream.write(data); !transStream.closed && transStream.write(data);
@@ -614,8 +693,18 @@ function createTransStream(convId: string, stream: any, endCallback?: Function)
return transStream; return transStream;
} }
/**
* Token切分
*
* @param authorization 认证字符串
*/
function tokenSplit(authorization: string) {
return authorization.replace('Bearer ', '').split(',');
}
export default { export default {
createConversation, createConversation,
createCompletion, createCompletion,
createCompletionStream createCompletionStream,
tokenSplit
}; };

View File

@@ -15,17 +15,19 @@ export default {
request request
.validate('body.messages', _.isArray) .validate('body.messages', _.isArray)
.validate('headers.authorization', _.isString) .validate('headers.authorization', _.isString)
const token = request.headers.authorization; // refresh_token切分
const refreshToken = token.replace('Bearer ', ''); const tokens = chat.tokenSplit(request.headers.authorization);
// 随机挑选一个refresh_token
const token = _.sample(tokens);
const messages = request.body.messages; const messages = request.body.messages;
if (request.body.stream) { if (request.body.stream) {
const stream = await chat.createCompletionStream(request.body.messages, refreshToken, request.body.use_search); const stream = await chat.createCompletionStream(request.body.messages, token, request.body.use_search);
return new Response(stream, { return new Response(stream, {
type: "text/event-stream" type: "text/event-stream"
}); });
} }
else else
return await chat.createCompletion(messages, refreshToken, request.body.use_search); return await chat.createCompletion(messages, token, request.body.use_search);
} }
} }

View File

@@ -44,12 +44,12 @@ export class SystemConfig {
this.requestBody = Object.assign(requestBody || {}, { this.requestBody = Object.assign(requestBody || {}, {
enableTypes: ['json', 'form', 'text', 'xml'], enableTypes: ['json', 'form', 'text', 'xml'],
encoding: 'utf-8', encoding: 'utf-8',
formLimit: '10mb', formLimit: '100mb',
jsonLimit: '10mb', jsonLimit: '100mb',
textLimit: '10mb', textLimit: '100mb',
xmlLimit: '10mb', xmlLimit: '100mb',
formidable: { formidable: {
maxFileSize: '30mb' maxFileSize: '100mb'
}, },
multipart: true, multipart: true,
parsedMethods: ['POST', 'PUT', 'PATCH'] parsedMethods: ['POST', 'PUT', 'PATCH']

View File

@@ -29,6 +29,11 @@ export default class Exception extends Error {
this.errmsg = _errmsg || errmsg; this.errmsg = _errmsg || errmsg;
} }
compare(exception: (string | number)[]) {
const [errcode] = exception as [number, string];
return this.errcode == errcode;
}
setHTTPStatusCode(value: number) { setHTTPStatusCode(value: number) {
this.httpStatusCode = value; this.httpStatusCode = value;
return this; return this;