6 Commits

Author SHA1 Message Date
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
6 changed files with 152 additions and 65 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

@@ -66,6 +66,10 @@ https://udify.app/chat/Po0F6BMJ15q5vu2P
![响应流畅度一致](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

BIN
doc/example-7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

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

View File

@@ -14,6 +14,10 @@ import util from '@/lib/util.ts';
const MODEL_NAME = 'kimi';
// access_token有效期
const ACCESS_TOKEN_EXPIRES = 300;
// 最大重试次数
const MAX_RETRY_COUNT = 3;
// 重试延迟
const RETRY_DELAY = 5000;
// 伪装headers
const FAKE_HEADERS = {
'Accept': '*/*',
@@ -163,45 +167,59 @@ async function removeConversation(convId: string, refreshToken: string) {
* @param messages 参考gpt系列消息格式多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/
async function createCompletion(messages: any[], refreshToken: string, useSearch = true) {
logger.info(messages);
async function createCompletion(messages: any[], refreshToken: string, useSearch = true, retryCount = 0) {
return (async () => {
logger.info(messages);
// 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 创建会话
const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken);
// 创建会话
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
}, {
headers: {
Authorization: `Bearer ${token}`,
Referer: `https://kimi.moonshot.cn/chat/${convId}`,
...FAKE_HEADERS
},
// 120秒超时
timeout: 120000,
validateStatus: () => true,
responseType: 'stream'
});
// 请求流
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();
// 接收流为输出文本
const answer = await receiveStream(convId, result.data);
logger.success(`Stream has completed transfer ${util.timestamp() - streamStartTime}ms`);
const streamStartTime = util.timestamp();
// 接收流为输出文本
const answer = await receiveStream(convId, result.data);
logger.success(`Stream has completed transfer ${util.timestamp() - streamStartTime}ms`);
// 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(convId, refreshToken)
.catch(err => console.error(err));
// 异步移除会话,如果消息不合规,此操作可能会抛出数据库错误异常,请忽略
removeConversation(convId, refreshToken)
.catch(err => console.error(err));
return answer;
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;
});
}
/**
@@ -210,42 +228,56 @@ async function createCompletion(messages: any[], refreshToken: string, useSearch
* @param messages 参考gpt系列消息格式多轮对话请完整提供上下文
* @param refreshToken 用于刷新access_token的refresh_token
* @param useSearch 是否开启联网搜索
* @param retryCount 重试次数
*/
async function createCompletionStream(messages: any[], refreshToken: string, useSearch = true) {
logger.info(messages);
async function createCompletionStream(messages: any[], refreshToken: string, useSearch = true, retryCount = 0) {
return (async () => {
logger.info(messages);
// 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 提取引用文件URL并上传kimi获得引用的文件ID列表
const refFileUrls = extractRefFileUrls(messages);
const refs = refFileUrls.length ? await Promise.all(refFileUrls.map(fileUrl => uploadFile(fileUrl, refreshToken))) : [];
// 创建会话
const convId = await createConversation(`cmpl-${util.uuid(false)}`, refreshToken);
// 创建会话
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));
});
// 请求流
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;
});
}
/**

View File

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