From e4bacc45d62318735ecad7262dae7533c26b3b25 Mon Sep 17 00:00:00 2001 From: Jungley Date: Sun, 13 Aug 2023 22:55:43 +0800 Subject: [PATCH 01/22] ci: add stage caching to Dockerfile (#408) --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22055553..ffb8c21b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ FROM node:16 as builder WORKDIR /build +COPY web/package.json . +RUN npm install COPY ./web . COPY ./VERSION . -RUN npm install RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build FROM golang AS builder2 @@ -13,9 +14,10 @@ ENV GO111MODULE=on \ GOOS=linux WORKDIR /build +ADD go.mod go.sum ./ +RUN go mod download COPY . . COPY --from=builder /build/build ./web/build -RUN go mod download RUN go build -ldflags "-s -w -X 'one-api/common.Version=$(cat VERSION)' -extldflags '-static'" -o one-api FROM alpine From 90b4cac7f352fcbb9771ba5cd3ece6b5e105bace Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Wed, 16 Aug 2023 20:33:31 +0900 Subject: [PATCH 02/22] docs: add Japanese README (#425) --- README.en.md | 2 +- README.ja.md | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 3 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 README.ja.md diff --git a/README.en.md b/README.en.md index 8a041da8..783c140c 100644 --- a/README.en.md +++ b/README.en.md @@ -1,5 +1,5 @@

- 中文 | English + 中文 | English | 日本語

diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 00000000..8a852935 --- /dev/null +++ b/README.ja.md @@ -0,0 +1,298 @@ +

+ 中文 | English | 日本語 +

+ +

+ one-api logo +

+ +
+ +# One API + +_✨ 標準的な OpenAI API フォーマットを通じてすべての LLM にアクセスでき、導入と利用が容易です ✨_ + +
+ +

+ + license + + + release + + + docker pull + + + release + + + GoReportCard + +

+ +

+ デプロイチュートリアル + · + 使用方法 + · + フィードバック + · + スクリーンショット + · + ライブデモ + · + FAQ + · + 関連プロジェクト + · + 寄付 +

+ +> **警告**: この README は ChatGPT によって翻訳されています。翻訳ミスを発見した場合は遠慮なく PR を投稿してください。 + +> **警告**: 英語版の Docker イメージは `justsong/one-api-ja` です。 + +> **注**: Docker からプルされた最新のイメージは、`alpha` リリースかもしれません。安定性が必要な場合は、手動でバージョンを指定してください。 + +## 特徴 +1. 複数の大型モデルをサポート: + + [x] [OpenAI ChatGPT シリーズモデル](https://platform.openai.com/docs/guides/gpt/chat-completions-api) ([Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) をサポート) + + [x] [Anthropic Claude シリーズモデル](https://anthropic.com) + + [x] [Google PaLM2 シリーズモデル](https://developers.generativeai.google) + + [x] [Baidu Wenxin Yiyuan シリーズモデル](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) + + [x] [Alibaba Tongyi Qianwen シリーズモデル](https://help.aliyun.com/document_detail/2400395.html) + + [x] [Zhipu ChatGLM シリーズモデル](https://bigmodel.cn) +2. **ロードバランシング**による複数チャンネルへのアクセスをサポート。 +3. ストリーム伝送によるタイプライター的効果を可能にする**ストリームモード**に対応。 +4. **マルチマシンデプロイ**に対応。[詳細はこちら](#multi-machine-deployment)を参照。 +5. トークンの有効期限や使用回数を設定できる**トークン管理**に対応しています。 +6. **バウチャー管理**に対応しており、バウチャーの一括生成やエクスポートが可能です。バウチャーは口座残高の補充に利用できます。 +7. **チャンネル管理**に対応し、チャンネルの一括作成が可能。 +8. グループごとに異なるレートを設定するための**ユーザーグループ**と**チャンネルグループ**をサポートしています。 +9. チャンネル**モデルリスト設定**に対応。 +10. **クォータ詳細チェック**をサポート。 +11. **ユーザー招待報酬**をサポートします。 +12. 米ドルでの残高表示が可能。 +13. 新規ユーザー向けのお知らせ公開、リチャージリンク設定、初期残高設定に対応。 +14. 豊富な**カスタマイズ**オプションを提供します: + 1. システム名、ロゴ、フッターのカスタマイズが可能。 + 2. HTML と Markdown コードを使用したホームページとアバウトページのカスタマイズ、または iframe を介したスタンドアロンウェブページの埋め込みをサポートしています。 +15. システム・アクセストークンによる管理 API アクセスをサポートする。 +16. Cloudflare Turnstile によるユーザー認証に対応。 +17. ユーザー管理と複数のユーザーログイン/登録方法をサポート: + + 電子メールによるログイン/登録とパスワードリセット。 + + [GitHub OAuth](https://github.com/settings/applications/new)。 + + WeChat 公式アカウントの認証([WeChat Server](https://github.com/songquanpeng/wechat-server)の追加導入が必要)。 +18. 他の主要なモデル API が利用可能になった場合、即座にサポートし、カプセル化する。 + +## デプロイメント +### Docker デプロイメント +デプロイコマンド: `docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api-ja`。 + +コマンドを更新する: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrr/watchtower -cR`。 + +`-p 3000:3000` の最初の `3000` はホストのポートで、必要に応じて変更できます。 + +データはホストの `/home/ubuntu/data/one-api` ディレクトリに保存される。このディレクトリが存在し、書き込み権限があることを確認する、もしくは適切なディレクトリに変更してください。 + +Nginxリファレンス設定: +``` +server{ + server_name openai.justsong.cn; # ドメイン名は適宜変更 + + location / { + client_max_body_size 64m; + proxy_http_version 1.1; + proxy_pass http://localhost:3000; # それに応じてポートを変更 + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_cache_bypass $http_upgrade; + proxy_set_header Accept-Encoding gzip; + proxy_read_timeout 300s; # GPT-4 はより長いタイムアウトが必要 + } +} +``` + +次に、Let's Encrypt certbot を使って HTTPS を設定します: +```bash +# Ubuntu に certbot をインストール: +sudo snap install --classic certbot +sudo ln -s /snap/bin/certbot /usr/bin/certbot +# 証明書の生成と Nginx 設定の変更 +sudo certbot --nginx +# プロンプトに従う +# Nginx を再起動 +sudo service nginx restart +``` + +初期アカウントのユーザー名は `root` で、パスワードは `123456` です。 + +### マニュアルデプロイ +1. [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) から実行ファイルをダウンロードする、もしくはソースからコンパイルする: + ```shell + git clone https://github.com/songquanpeng/one-api.git + + # フロントエンドのビルド + cd one-api/web + npm install + npm run build + + # バックエンドのビルド + cd .. + go mod download + go build -ldflags "-s -w" -o one-api + ``` +2. 実行: + ```shell + chmod u+x one-api + ./one-api --port 3000 --log-dir ./logs + ``` +3. [http://localhost:3000/](http://localhost:3000/) にアクセスし、ログインする。初期アカウントのユーザー名は `root`、パスワードは `123456` である。 + +より詳細なデプロイのチュートリアルについては、[このページ](https://iamazing.cn/page/how-to-deploy-a-website) を参照してください。 + +### マルチマシンデプロイ +1. すべてのサーバに同じ `SESSION_SECRET` を設定する。 +2. `SQL_DSN` を設定し、SQLite の代わりに MySQL を使用する。すべてのサーバは同じデータベースに接続する。 +3. マスターノード以外のノードの `NODE_TYPE` を `slave` に設定する。 +4. データベースから定期的に設定を同期するサーバーには `SYNC_FREQUENCY` を設定する。 +5. マスター以外のノードでは、オプションで `FRONTEND_BASE_URL` を設定して、ページ要求をマスターサーバーにリダイレクトすることができます。 +6. マスター以外のノードには Redis を個別にインストールし、`REDIS_CONN_STRING` を設定して、キャッシュの有効期限が切れていないときにデータベースにゼロレイテンシーでアクセスできるようにする。 +7. メインサーバーでもデータベースへのアクセスが高レイテンシになる場合は、Redis を有効にし、`SYNC_FREQUENCY` を設定してデータベースから定期的に設定を同期する必要がある。 + +Please refer to the [environment variables](#environment-variables) section for details on using environment variables. + +### コントロールパネル(例: Baota)への展開 +詳しい手順は [#175](https://github.com/songquanpeng/one-api/issues/175) を参照してください。 + +配置後に空白のページが表示される場合は、[#97](https://github.com/songquanpeng/one-api/issues/97) を参照してください。 + +### サードパーティプラットフォームへのデプロイ +
+Sealos へのデプロイ +
+ +> Sealos は、高い同時実行性、ダイナミックなスケーリング、数百万人のユーザーに対する安定した運用をサポートしています。 + +> 下のボタンをクリックすると、ワンクリックで展開できます。👇 + +[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api) + + +
+
+ +
+Zeabur へのデプロイ +
+ +> Zeabur のサーバーは海外にあるため、ネットワークの問題は自動的に解決されます。 + +1. まず、コードをフォークする。 +2. [Zeabur](https://zeabur.com?referralCode=songquanpeng) にアクセスしてログインし、コンソールに入る。 +3. 新しいプロジェクトを作成します。Service -> Add ServiceでMarketplace を選択し、MySQL を選択する。接続パラメータ(ユーザー名、パスワード、アドレス、ポート)をメモします。 +4. 接続パラメータをコピーし、```create database `one-api` ``` を実行してデータベースを作成する。 +5. その後、Service -> Add Service で Git を選択し(最初の使用には認証が必要です)、フォークしたリポジトリを選択します。 +6. 自動デプロイが開始されますが、一旦キャンセルしてください。Variable タブで `PORT` に `3000` を追加し、`SQL_DSN` に `:@tcp(:)/one-api` を追加します。変更を保存する。SQL_DSN` が設定されていないと、データが永続化されず、再デプロイ後にデータが失われるので注意すること。 +7. 再デプロイを選択します。 +8. Domains タブで、"my-one-api" のような適切なドメイン名の接頭辞を選択する。最終的なドメイン名は "my-one-api.zeabur.app" となります。独自のドメイン名を CNAME することもできます。 +9. デプロイが完了するのを待ち、生成されたドメイン名をクリックして One API にアクセスします。 + +
+
+ +## コンフィグ +システムは箱から出してすぐに使えます。 + +環境変数やコマンドラインパラメータを設定することで、システムを構成することができます。 + +システム起動後、`root` ユーザーとしてログインし、さらにシステムを設定します。 + +## 使用方法 +`Channels` ページで API Key を追加し、`Tokens` ページでアクセストークンを追加する。 + +アクセストークンを使って One API にアクセスすることができる。使い方は [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) と同じです。 + +OpenAI API が使用されている場所では、API Base に One API のデプロイアドレスを設定することを忘れないでください(例: `https://openai.justsong.cn`)。API Key は One API で生成されたトークンでなければなりません。 + +具体的な API Base のフォーマットは、使用しているクライアントに依存することに注意してください。 + +```mermaid +graph LR + A(ユーザ) + A --->|リクエスト| B(One API) + B -->|中継リクエスト| C(OpenAI) + B -->|中継リクエスト| D(Azure) + B -->|中継リクエスト| E(その他のダウンストリームチャンネル) +``` + +現在のリクエストにどのチャネルを使うかを指定するには、トークンの後に チャネル ID を追加します: 例えば、`Authorization: Bearer ONE_API_KEY-CHANNEL_ID` のようにします。 +チャンネル ID を指定するためには、トークンは管理者によって作成される必要があることに注意してください。 + +もしチャネル ID が指定されない場合、ロードバランシングによってリクエストが複数のチャネルに振り分けられます。 + +### 環境変数 +1. `REDIS_CONN_STRING`: 設定すると、リクエストレート制限のためのストレージとして、メモリの代わりに Redis が使われる。 + + 例: `REDIS_CONN_STRING=redis://default:redispw@localhost:49153` +2. `SESSION_SECRET`: 設定すると、固定セッションキーが使用され、システムの再起動後もログインユーザーのクッキーが有効であることが保証されます。 + + 例: `SESSION_SECRET=random_string` +3. `SQL_DSN`: 設定すると、SQLite の代わりに指定したデータベースが使用されます。MySQL バージョン 8.0 を使用してください。 + + 例: `SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` +4. `FRONTEND_BASE_URL`: 設定されると、バックエンドアドレスではなく、指定されたフロントエンドアドレスが使われる。 + + 例: `FRONTEND_BASE_URL=https://openai.justsong.cn` +5. `SYNC_FREQUENCY`: 設定された場合、システムは定期的にデータベースからコンフィグを秒単位で同期する。設定されていない場合、同期は行われません。 + + 例: `SYNC_FREQUENCY=60` +6. `NODE_TYPE`: 設定すると、ノードのタイプを指定する。有効な値は `master` と `slave` である。設定されていない場合、デフォルトは `master`。 + + 例: `NODE_TYPE=slave` +7. `CHANNEL_UPDATE_FREQUENCY`: 設定すると、チャンネル残高を分単位で定期的に更新する。設定されていない場合、更新は行われません。 + + 例: `CHANNEL_UPDATE_FREQUENCY=1440` +8. `CHANNEL_TEST_FREQUENCY`: 設定すると、チャンネルを定期的にテストする。設定されていない場合、テストは行われません。 + + 例: `CHANNEL_TEST_FREQUENCY=1440` +9. `POLLING_INTERVAL`: チャネル残高の更新とチャネルの可用性をテストするときのリクエスト間の時間間隔 (秒)。デフォルトは間隔なし。 + + 例: `POLLING_INTERVAL=5` + +### コマンドラインパラメータ +1. `--port `: サーバがリッスンするポート番号を指定。デフォルトは `3000` です。 + + 例: `--port 3000` +2. `--log-dir `: ログディレクトリを指定。設定しない場合、ログは保存されません。 + + 例: `--log-dir ./logs` +3. `--version`: システムのバージョン番号を表示して終了する。 +4. `--help`: コマンドの使用法ヘルプとパラメータの説明を表示。 + +## スクリーンショット +![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png) +![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png) + +## FAQ +1. ノルマとは何か?どのように計算されますか?One API にはノルマ計算の問題はありますか? + + ノルマ = グループ倍率 * モデル倍率 * (プロンプトトークンの数 + 完了トークンの数 * 完了倍率) + + 完了倍率は、公式の定義と一致するように、GPT3.5 では 1.33、GPT4 では 2 に固定されています。 + + ストリームモードでない場合、公式 API は消費したトークンの総数を返す。ただし、プロンプトとコンプリートの消費倍率は異なるので注意してください。 +2. アカウント残高は十分なのに、"insufficient quota" と表示されるのはなぜですか? + + トークンのクォータが十分かどうかご確認ください。トークンクォータはアカウント残高とは別のものです。 + + トークンクォータは最大使用量を設定するためのもので、ユーザーが自由に設定できます。 +3. チャンネルを使おうとすると "No available channels" と表示されます。どうすればいいですか? + + ユーザーとチャンネルグループの設定を確認してください。 + + チャンネルモデルの設定も確認してください。 +4. チャンネルテストがエラーを報告する: "invalid character '<' looking for beginning of value" + + このエラーは、返された値が有効な JSON ではなく、HTML ページである場合に発生する。 + + ほとんどの場合、デプロイサイトのIPかプロキシのノードが CloudFlare によってブロックされています。 +5. ChatGPT Next Web でエラーが発生しました: "Failed to fetch" + + デプロイ時に `BASE_URL` を設定しないでください。 + + インターフェイスアドレスと API Key が正しいか再確認してください。 + +## 関連プロジェクト +[FastGPT](https://github.com/labring/FastGPT): LLM に基づく知識質問応答システム + +## 注 +本プロジェクトはオープンソースプロジェクトです。OpenAI の[利用規約](https://openai.com/policies/terms-of-use)および**適用される法令**を遵守してご利用ください。違法な目的での利用はご遠慮ください。 + +このプロジェクトは MIT ライセンスで公開されています。これに基づき、ページの最下部に帰属表示と本プロジェクトへのリンクを含める必要があります。 + +このプロジェクトを基にした派生プロジェクトについても同様です。 + +帰属表示を含めたくない場合は、事前に許可を得なければなりません。 + +MIT ライセンスによると、このプロジェクトを利用するリスクと責任は利用者が負うべきであり、このオープンソースプロジェクトの開発者は責任を負いません。 diff --git a/README.md b/README.md index e2979961..94483a20 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- 中文 | English + 中文 | English | 日本語

From 86c2627c24e814dbea9086fbd8e9a904137a9cda Mon Sep 17 00:00:00 2001 From: JustSong Date: Wed, 16 Aug 2023 23:40:24 +0800 Subject: [PATCH 03/22] fix: update cache immediately after cache get Co-authored-by: chikasaki <1347283135@qq.com> Co-authored-by: Cruel <157922018@qq.com> --- common/redis.go | 5 +++++ controller/relay-text.go | 6 +++++- model/cache.go | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/common/redis.go b/common/redis.go index 1a05721c..12c477b8 100644 --- a/common/redis.go +++ b/common/redis.go @@ -61,3 +61,8 @@ func RedisDel(key string) error { ctx := context.Background() return RDB.Del(ctx, key).Err() } + +func RedisDecrease(key string, value int64) error { + ctx := context.Background() + return RDB.DecrBy(ctx, key, value).Err() +} diff --git a/controller/relay-text.go b/controller/relay-text.go index e8dab514..761ca86f 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -194,7 +194,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if err != nil { return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError) } - if userQuota > 10*preConsumedQuota { + err = model.CacheDecreaseUserQuota(userId, preConsumedQuota) + if err != nil { + return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError) + } + if userQuota > 100*preConsumedQuota { // in this case, we do not pre-consume quota // because the user has enough quota preConsumedQuota = 0 diff --git a/model/cache.go b/model/cache.go index 64666c86..55fbba9b 100644 --- a/model/cache.go +++ b/model/cache.go @@ -95,6 +95,14 @@ func CacheUpdateUserQuota(id int) error { return err } +func CacheDecreaseUserQuota(id int, quota int) error { + if !common.RedisEnabled { + return nil + } + err := common.RedisDecrease(fmt.Sprintf("user_quota:%d", id), int64(quota)) + return err +} + func CacheIsUserEnabled(userId int) bool { if !common.RedisEnabled { return IsUserEnabled(userId) From 8fb082ba3b6bbce21ed58c8d2bf6c4044325db0c Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 14:54:40 +0800 Subject: [PATCH 04/22] docs: update README --- README.ja.md | 4 ++-- README.md | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.ja.md b/README.ja.md index 8a852935..fa3339c2 100644 --- a/README.ja.md +++ b/README.ja.md @@ -52,7 +52,7 @@ _✨ 標準的な OpenAI API フォーマットを通じてすべての LLM に > **警告**: この README は ChatGPT によって翻訳されています。翻訳ミスを発見した場合は遠慮なく PR を投稿してください。 -> **警告**: 英語版の Docker イメージは `justsong/one-api-ja` です。 +> **警告**: 英語版の Docker イメージは `justsong/one-api-en` です。 > **注**: Docker からプルされた最新のイメージは、`alpha` リリースかもしれません。安定性が必要な場合は、手動でバージョンを指定してください。 @@ -89,7 +89,7 @@ _✨ 標準的な OpenAI API フォーマットを通じてすべての LLM に ## デプロイメント ### Docker デプロイメント -デプロイコマンド: `docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api-ja`。 +デプロイコマンド: `docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api-en`。 コマンドを更新する: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrr/watchtower -cR`。 diff --git a/README.md b/README.md index 94483a20..00573493 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,17 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 赞赏支持

-> **Note**:本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 +> **Note** +> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -> **Note**:使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 +> **Note** +> 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 -> **Warning**:从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 +> **Note** +> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 + +> **Warning** +> 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 ## 功能 1. 支持多种大模型: From 80a49e01a38e628704a9bab998224d20c4d3fa7e Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 14:55:17 +0800 Subject: [PATCH 05/22] docs: update README --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 00573493..6e2bd432 100644 --- a/README.md +++ b/README.md @@ -51,17 +51,13 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 赞赏支持

-> **Note** -> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 +> **Note** 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -> **Note** -> 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 +> **Note** 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 -> **Note** -> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 +> **Note** 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 -> **Warning** -> 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 +> **Warning** 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 ## 功能 1. 支持多种大模型: From 5136b12612a9ba8ecb15a3b8cd4cab567e3e09ff Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 14:55:38 +0800 Subject: [PATCH 06/22] docs: update README --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6e2bd432..00573493 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,17 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 赞赏支持

-> **Note** 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 +> **Note** +> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -> **Note** 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 +> **Note** +> 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 -> **Note** 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 +> **Note** +> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 -> **Warning** 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 +> **Warning** +> 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 ## 功能 1. 支持多种大模型: From 8ea7b9aae2d26a9f4a51c533966edd51a7ec8555 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 14:56:21 +0800 Subject: [PATCH 07/22] docs: update README --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 00573493..95234bfa 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,11 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 > **Note** > 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 - -> **Note** +> > 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 -> **Note** -> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 - > **Warning** -> 从 `v0.3` 版本升级到 `v0.4` 版本需要手动迁移数据库,请手动执行[数据库迁移脚本](./bin/migration_v0.3-v0.4.sql)。 +> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 ## 功能 1. 支持多种大模型: From 2a7b82650c94158dcdef759113af8e981d887854 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 14:56:48 +0800 Subject: [PATCH 08/22] docs: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95234bfa..070032e3 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 > **Note** > 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 > -> 根据《生成式人工智能服务管理暂行办法》 (http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 +> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 > **Warning** > 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 From 49d1a634028bb858ca0c774e85b0ae2f0e75ca5a Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 19 Aug 2023 16:35:38 +0800 Subject: [PATCH 09/22] docs: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 070032e3..36841c79 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 > **Note** > 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 > -> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区(含港澳台)公众提供一切未经备案的生成式人工智能服务。 +> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。 > **Warning** > 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 From 23b1c63538bdba2cf6b8532dcf3721cd97816209 Mon Sep 17 00:00:00 2001 From: Benny Date: Sat, 19 Aug 2023 16:58:34 +0800 Subject: [PATCH 10/22] fix: claude model ratio (#449) * fix: Claude model ratio * chore: update implementation --------- Co-authored-by: JustSong --- common/model-ratio.go | 25 ++++++++++++++++++++++--- controller/relay-text.go | 9 +-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index 5865b4dc..e658cdc1 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -1,6 +1,9 @@ package common -import "encoding/json" +import ( + "encoding/json" + "strings" +) // ModelRatio // https://platform.openai.com/docs/models/model-endpoint-compatibility @@ -38,8 +41,8 @@ var ModelRatio = map[string]float64{ "text-moderation-stable": 0.1, "text-moderation-latest": 0.1, "dall-e": 8, - "claude-instant-1": 0.75, - "claude-2": 30, + "claude-instant-1": 0.815, // $1.63 / 1M tokens + "claude-2": 5.51, // $11.02 / 1M tokens "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens @@ -73,3 +76,19 @@ func GetModelRatio(name string) float64 { } return ratio } + +func GetCompletionRatio(name string) float64 { + if strings.HasPrefix(name, "gpt-3.5") { + return 1.333333 + } + if strings.HasPrefix(name, "gpt-4") { + return 2 + } + if strings.HasPrefix(name, "claude-instant-1") { + return 3.38 + } + if strings.HasPrefix(name, "claude-2") { + return 2.965517 + } + return 1 +} diff --git a/controller/relay-text.go b/controller/relay-text.go index 761ca86f..e061d387 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -326,14 +326,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { go func() { if consumeQuota { quota := 0 - completionRatio := 1.0 - if strings.HasPrefix(textRequest.Model, "gpt-3.5") { - completionRatio = 1.333333 - } - if strings.HasPrefix(textRequest.Model, "gpt-4") { - completionRatio = 2 - } - + completionRatio := common.GetCompletionRatio(textRequest.Model) promptTokens = textResponse.Usage.PromptTokens completionTokens = textResponse.Usage.CompletionTokens From 1b56becfaae3473861b870c8750205df21334355 Mon Sep 17 00:00:00 2001 From: Benny Date: Sat, 19 Aug 2023 17:08:50 +0800 Subject: [PATCH 11/22] feat: show total quota consumption only when user click (#448) * feat: add toggleable visibility for total quota using an eye icon * chore: update implementation --------- Co-authored-by: JustSong --- i18n/en.json | 3 ++- web/src/components/LogsTable.js | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index a9402419..ae395dae 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -520,5 +520,6 @@ "代理": "Proxy", "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com", "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?", - "按照如下格式输入:": "Enter in the following format:" + "按照如下格式输入:": "Enter in the following format:", + "点击查看": "click to view" } diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index e311a520..bacb7689 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -43,6 +43,7 @@ function renderType(type) { const LogsTable = () => { const [logs, setLogs] = useState([]); + const [showStat, setShowStat] = useState(false); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(''); @@ -92,6 +93,17 @@ const LogsTable = () => { } }; + const handleEyeClick = async () => { + if (!showStat) { + if (isAdminUser) { + await getLogStat(); + } else { + await getLogSelfStat(); + } + } + setShowStat(!showStat); + }; + const loadLogs = async (startIdx) => { let url = ''; let localStartTimestamp = Date.parse(start_timestamp) / 1000; @@ -129,13 +141,8 @@ const LogsTable = () => { const refresh = async () => { setLoading(true); - setActivePage(1) + setActivePage(1); await loadLogs(0); - if (isAdminUser) { - getLogStat().then(); - } else { - getLogSelfStat().then(); - } }; useEffect(() => { @@ -169,7 +176,7 @@ const LogsTable = () => { if (logs.length === 0) return; setLoading(true); let sortedLogs = [...logs]; - if (typeof sortedLogs[0][key] === 'string'){ + if (typeof sortedLogs[0][key] === 'string') { sortedLogs.sort((a, b) => { return ('' + a[key]).localeCompare(b[key]); }); @@ -190,7 +197,12 @@ const LogsTable = () => { return ( <> -
使用明细(总消耗额度:{renderQuota(stat.quota)})
+
+ 使用明细(总消耗额度: + {showStat && renderQuota(stat.quota)} + {!showStat && 点击查看} + ) +
{ From dfaa0183b71274a19eb87fe9f837d70a414737f4 Mon Sep 17 00:00:00 2001 From: glzjin Date: Sat, 19 Aug 2023 17:14:39 +0800 Subject: [PATCH 12/22] fix: fix baidu & ali's quota calculation (#444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复阿里计费问题 * 修复百度计费问题 --- controller/relay-ali.go | 8 +++++--- controller/relay-baidu.go | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/controller/relay-ali.go b/controller/relay-ali.go index 014f6b84..9dca9a89 100644 --- a/controller/relay-ali.go +++ b/controller/relay-ali.go @@ -177,9 +177,11 @@ func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStat common.SysError("error unmarshalling stream response: " + err.Error()) return true } - usage.PromptTokens += aliResponse.Usage.InputTokens - usage.CompletionTokens += aliResponse.Usage.OutputTokens - usage.TotalTokens += aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens + if aliResponse.Usage.OutputTokens != 0 { + usage.PromptTokens = aliResponse.Usage.InputTokens + usage.CompletionTokens = aliResponse.Usage.OutputTokens + usage.TotalTokens = aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens + } response := streamResponseAli2OpenAI(&aliResponse) response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText) lastResponseText = aliResponse.Output.Text diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index ad20d6d6..39f31a9a 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -215,9 +215,11 @@ func baiduStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithSt common.SysError("error unmarshalling stream response: " + err.Error()) return true } - usage.PromptTokens += baiduResponse.Usage.PromptTokens - usage.CompletionTokens += baiduResponse.Usage.CompletionTokens - usage.TotalTokens += baiduResponse.Usage.TotalTokens + if baiduResponse.Usage.TotalTokens != 0 { + usage.TotalTokens = baiduResponse.Usage.TotalTokens + usage.PromptTokens = baiduResponse.Usage.PromptTokens + usage.CompletionTokens = baiduResponse.Usage.TotalTokens - baiduResponse.Usage.PromptTokens + } response := streamResponseBaidu2OpenAI(&baiduResponse) jsonResponse, err := json.Marshal(response) if err != nil { From 7e058bfb9b0cf56a937fb725b65e30f35bb81a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BB=94=E5=93=A5?= Date: Sat, 19 Aug 2023 17:50:34 +0800 Subject: [PATCH 13/22] feat: support xunfei's v2 api (#442, close #440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 兼容讯飞v2接口 * Revert "兼容讯飞v2接口" This reverts commit 21f05d1294b8693d0a21664a23ec04f028b9b117. * fix: fix implementation --------- Co-authored-by: JustSong Co-authored-by: JustSong <39998050+songquanpeng@users.noreply.github.com> --- controller/relay-xunfei.go | 21 +++++++++++++++++---- i18n/en.json | 2 ++ middleware/distributor.go | 2 +- web/src/pages/Channel/EditChannel.js | 17 +++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/controller/relay-xunfei.go b/controller/relay-xunfei.go index 87037e34..3b6fe5a0 100644 --- a/controller/relay-xunfei.go +++ b/controller/relay-xunfei.go @@ -75,7 +75,7 @@ type XunfeiChatResponse struct { } `json:"payload"` } -func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string) *XunfeiChatRequest { +func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string, domain string) *XunfeiChatRequest { messages := make([]XunfeiMessage, 0, len(request.Messages)) for _, message := range request.Messages { if message.Role == "system" { @@ -96,7 +96,7 @@ func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string) *Xun } xunfeiRequest := XunfeiChatRequest{} xunfeiRequest.Header.AppId = xunfeiAppId - xunfeiRequest.Parameter.Chat.Domain = "general" + xunfeiRequest.Parameter.Chat.Domain = domain xunfeiRequest.Parameter.Chat.Temperature = request.Temperature xunfeiRequest.Parameter.Chat.TopK = request.N xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens @@ -178,15 +178,28 @@ func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string { func xunfeiStreamHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) { var usage Usage + query := c.Request.URL.Query() + apiVersion := query.Get("api-version") + if apiVersion == "" { + apiVersion = c.GetString("api_version") + } + if apiVersion == "" { + apiVersion = "v1.1" + common.SysLog("api_version not found, use default: " + apiVersion) + } + domain := "general" + if apiVersion == "v2.1" { + domain = "generalv2" + } + hostUrl := fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion) d := websocket.Dialer{ HandshakeTimeout: 5 * time.Second, } - hostUrl := "wss://aichat.xf-yun.com/v1/chat" conn, resp, err := d.Dial(buildXunfeiAuthUrl(hostUrl, apiKey, apiSecret), nil) if err != nil || resp.StatusCode != 101 { return errorWrapper(err, "dial_failed", http.StatusInternalServerError), nil } - data := requestOpenAI2Xunfei(textRequest, appId) + data := requestOpenAI2Xunfei(textRequest, appId, domain) err = conn.WriteJSON(data) if err != nil { return errorWrapper(err, "write_json_failed", http.StatusInternalServerError), nil diff --git a/i18n/en.json b/i18n/en.json index ae395dae..aed65979 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -521,5 +521,7 @@ "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com", "取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?", "按照如下格式输入:": "Enter in the following format:", + "模型版本": "Model version", + "请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1": "Please enter the version of the Starfire model, note that it is the version number in the interface address, for example: v2.1", "点击查看": "click to view" } diff --git a/middleware/distributor.go b/middleware/distributor.go index 91c00e1a..ebbde535 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -107,7 +107,7 @@ func Distribute() func(c *gin.Context) { c.Set("model_mapping", channel.ModelMapping) c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key)) c.Set("base_url", channel.BaseURL) - if channel.Type == common.ChannelTypeAzure { + if channel.Type == common.ChannelTypeAzure || channel.Type == common.ChannelTypeXunfei { c.Set("api_version", channel.Other) } c.Next() diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index b5fb524e..fcbdb980 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -163,6 +163,9 @@ const EditChannel = () => { if (localInputs.type === 3 && localInputs.other === '') { localInputs.other = '2023-06-01-preview'; } + if (localInputs.type === 18 && localInputs.other === '') { + localInputs.other = 'v2.1'; + } if (localInputs.model_mapping === '') { localInputs.model_mapping = '{}'; } @@ -275,6 +278,20 @@ const EditChannel = () => { options={groupOptions} /> + { + inputs.type === 18 && ( + + + + ) + } Date: Sat, 19 Aug 2023 17:58:45 +0800 Subject: [PATCH 14/22] fix: empty completion issue caused by bad status code from upstream channel (#422) --- controller/relay-text.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/controller/relay-text.go b/controller/relay-text.go index e061d387..6ebe75e9 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -317,6 +317,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") } + if resp.StatusCode != http.StatusOK { + return errorWrapper( + fmt.Errorf("bad status code: %d", resp.StatusCode), "bad_status_code", resp.StatusCode) + } + var textResponse TextResponse tokenName := c.GetString("token_name") channelId := c.GetInt("channel_id") From efeb9a16ceb8b9e88560fc68ba4a528e1982f76d Mon Sep 17 00:00:00 2001 From: glzjin Date: Sun, 20 Aug 2023 22:07:50 +0800 Subject: [PATCH 15/22] fix: fix xunfei crash (#451) --- controller/relay-text.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index 6ebe75e9..0bad948f 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -315,11 +315,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) } isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") - } - if resp.StatusCode != http.StatusOK { - return errorWrapper( - fmt.Errorf("bad status code: %d", resp.StatusCode), "bad_status_code", resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return errorWrapper( + fmt.Errorf("bad status code: %d", resp.StatusCode), "bad_status_code", resp.StatusCode) + } } var textResponse TextResponse From ac7c0f3a76c50632f9fcaaac642cfa3465e5074d Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 26 Aug 2023 12:05:18 +0800 Subject: [PATCH 16/22] fix: disable channel when 401 received (close #467) --- controller/channel-test.go | 2 +- controller/relay-utils.go | 6 +++++- controller/relay.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/controller/channel-test.go b/controller/channel-test.go index 8465d51d..4acb2e3b 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -174,7 +174,7 @@ func testAllChannels(notify bool) error { err = errors.New(fmt.Sprintf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)) disableChannel(channel.Id, channel.Name, err.Error()) } - if shouldDisableChannel(openaiErr) { + if shouldDisableChannel(openaiErr, -1) { disableChannel(channel.Id, channel.Name, err.Error()) } channel.UpdateResponseTime(milliseconds) diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 5b3e0274..aaf579ab 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gin-gonic/gin" "github.com/pkoukk/tiktoken-go" + "net/http" "one-api/common" ) @@ -95,13 +96,16 @@ func errorWrapper(err error, code string, statusCode int) *OpenAIErrorWithStatus } } -func shouldDisableChannel(err *OpenAIError) bool { +func shouldDisableChannel(err *OpenAIError, statusCode int) bool { if !common.AutomaticDisableChannelEnabled { return false } if err == nil { return false } + if statusCode == http.StatusUnauthorized { + return true + } if err.Type == "insufficient_quota" || err.Code == "invalid_api_key" || err.Code == "account_deactivated" { return true } diff --git a/controller/relay.go b/controller/relay.go index 86f16c45..1eaa2c26 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -185,7 +185,7 @@ func Relay(c *gin.Context) { channelId := c.GetInt("channel_id") common.SysError(fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message)) // https://platform.openai.com/docs/guides/error-codes/api-errors - if shouldDisableChannel(&err.OpenAIError) { + if shouldDisableChannel(&err.OpenAIError, err.StatusCode) { channelId := c.GetInt("channel_id") channelName := c.GetString("channel_name") disableChannel(channelId, channelName, err.Message) From a3e267df7eb1fc88a5b8332c8338965ba1782042 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 26 Aug 2023 12:37:45 +0800 Subject: [PATCH 17/22] fix: fix error response (close #468) --- controller/relay-text.go | 3 +-- controller/relay-utils.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index 0bad948f..5298d292 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -317,8 +317,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { - return errorWrapper( - fmt.Errorf("bad status code: %d", resp.StatusCode), "bad_status_code", resp.StatusCode) + return relayErrorHandler(resp) } } diff --git a/controller/relay-utils.go b/controller/relay-utils.go index aaf579ab..1a9ee0d1 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -1,11 +1,14 @@ package controller import ( + "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/pkoukk/tiktoken-go" + "io" "net/http" "one-api/common" + "strconv" ) var stopFinishReason = "stop" @@ -119,3 +122,30 @@ func setEventStreamHeaders(c *gin.Context) { c.Writer.Header().Set("Transfer-Encoding", "chunked") c.Writer.Header().Set("X-Accel-Buffering", "no") } + +func relayErrorHandler(resp *http.Response) (openAIErrorWithStatusCode *OpenAIErrorWithStatusCode) { + openAIErrorWithStatusCode = &OpenAIErrorWithStatusCode{ + StatusCode: resp.StatusCode, + OpenAIError: OpenAIError{ + Message: fmt.Sprintf("bad response status code %d", resp.StatusCode), + Type: "one_api_error", + Code: "bad_response_status_code", + Param: strconv.Itoa(resp.StatusCode), + }, + } + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return + } + err = resp.Body.Close() + if err != nil { + return + } + var textResponse TextResponse + err = json.Unmarshal(responseBody, &textResponse) + if err != nil { + return + } + openAIErrorWithStatusCode.OpenAIError = textResponse.Error + return +} From fdb2cccf65f3d27f56a05b91c48aed393c7ed9c9 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 26 Aug 2023 13:02:02 +0800 Subject: [PATCH 18/22] perf: initialize all token encoder when starting (close #459, close $460) --- controller/relay-utils.go | 18 ++++++++++++++++++ main.go | 1 + 2 files changed, 19 insertions(+) diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 1a9ee0d1..9010d275 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -15,6 +15,24 @@ var stopFinishReason = "stop" var tokenEncoderMap = map[string]*tiktoken.Tiktoken{} +func InitTokenEncoders() { + common.SysLog("initializing token encoders") + fallbackTokenEncoder, err := tiktoken.EncodingForModel("gpt-3.5-turbo") + if err != nil { + common.FatalLog(fmt.Sprintf("failed to get fallback token encoder: %s", err.Error())) + } + for model, _ := range common.ModelRatio { + tokenEncoder, err := tiktoken.EncodingForModel(model) + if err != nil { + common.SysError(fmt.Sprintf("using fallback encoder for model %s", model)) + tokenEncoderMap[model] = fallbackTokenEncoder + continue + } + tokenEncoderMap[model] = tokenEncoder + } + common.SysLog("token encoders initialized") +} + func getTokenEncoder(model string) *tiktoken.Tiktoken { if tokenEncoder, ok := tokenEncoderMap[model]; ok { return tokenEncoder diff --git a/main.go b/main.go index f4d20373..9fb0a73e 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,7 @@ func main() { } go controller.AutomaticallyTestChannels(frequency) } + controller.InitTokenEncoders() // Initialize HTTP server server := gin.Default() From 4f2f911e4d2cd12b1de037d8bef803b1cfd89f61 Mon Sep 17 00:00:00 2001 From: shao0222 <22172112+shao0222@users.noreply.github.com> Date: Sat, 26 Aug 2023 13:10:18 +0800 Subject: [PATCH 19/22] fix: fix the issue of function_call not working when using model mapping (#462) --- controller/relay.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controller/relay.go b/controller/relay.go index 1eaa2c26..6a2d58eb 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -40,6 +40,7 @@ type GeneralOpenAIRequest struct { Input any `json:"input,omitempty"` Instruction string `json:"instruction,omitempty"` Size string `json:"size,omitempty"` + Functions any `json:"functions,omitempty"` } type ChatRequest struct { From 5ee24e8acfbb6f9c57bb6ecedaad995ac45201ef Mon Sep 17 00:00:00 2001 From: JustSong Date: Sat, 26 Aug 2023 13:30:21 +0800 Subject: [PATCH 20/22] feat: support 360's models (close #331, close #461) feat: support 360's models (close #331, close #461) --- README.md | 1 + common/constants.go | 2 + common/model-ratio.go | 85 ++++++++++++++------------ controller/channel-test.go | 4 ++ controller/model.go | 45 ++++++++++++++ web/src/constants/channel.constants.js | 1 + web/src/pages/Channel/EditChannel.js | 3 + 7 files changed, 101 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 36841c79..45c8b603 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html) + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html) + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) + + [x] [360 智脑](https://ai.360.cn) 2. 支持配置镜像以及众多第三方代理服务: + [x] [OpenAI-SB](https://openai-sb.com) + [x] [API2D](https://api2d.com/r/197971) diff --git a/common/constants.go b/common/constants.go index 4b9df311..0ae8ae83 100644 --- a/common/constants.go +++ b/common/constants.go @@ -173,6 +173,7 @@ const ( ChannelTypeZhipu = 16 ChannelTypeAli = 17 ChannelTypeXunfei = 18 + ChannelType360 = 19 ) var ChannelBaseURLs = []string{ @@ -195,4 +196,5 @@ var ChannelBaseURLs = []string{ "https://open.bigmodel.cn", // 16 "https://dashscope.aliyuncs.com", // 17 "", // 18 + "https://ai.360.cn", // 19 } diff --git a/common/model-ratio.go b/common/model-ratio.go index e658cdc1..3f4f64b7 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -13,46 +13,51 @@ import ( // 1 === $0.002 / 1K tokens // 1 === ¥0.014 / 1k tokens var ModelRatio = map[string]float64{ - "gpt-4": 15, - "gpt-4-0314": 15, - "gpt-4-0613": 15, - "gpt-4-32k": 30, - "gpt-4-32k-0314": 30, - "gpt-4-32k-0613": 30, - "gpt-3.5-turbo": 0.75, // $0.0015 / 1K tokens - "gpt-3.5-turbo-0301": 0.75, - "gpt-3.5-turbo-0613": 0.75, - "gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens - "gpt-3.5-turbo-16k-0613": 1.5, - "text-ada-001": 0.2, - "text-babbage-001": 0.25, - "text-curie-001": 1, - "text-davinci-002": 10, - "text-davinci-003": 10, - "text-davinci-edit-001": 10, - "code-davinci-edit-001": 10, - "whisper-1": 10, - "davinci": 10, - "curie": 10, - "babbage": 10, - "ada": 10, - "text-embedding-ada-002": 0.05, - "text-search-ada-doc-001": 10, - "text-moderation-stable": 0.1, - "text-moderation-latest": 0.1, - "dall-e": 8, - "claude-instant-1": 0.815, // $1.63 / 1M tokens - "claude-2": 5.51, // $11.02 / 1M tokens - "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens - "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens - "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens - "PaLM-2": 1, - "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens - "chatglm_std": 0.3572, // ¥0.005 / 1k tokens - "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens - "qwen-v1": 0.8572, // TBD: https://help.aliyun.com/document_detail/2399482.html?spm=a2c4g.2399482.0.0.1ad347feilAgag - "qwen-plus-v1": 0.5715, // Same as above - "SparkDesk": 0.8572, // TBD + "gpt-4": 15, + "gpt-4-0314": 15, + "gpt-4-0613": 15, + "gpt-4-32k": 30, + "gpt-4-32k-0314": 30, + "gpt-4-32k-0613": 30, + "gpt-3.5-turbo": 0.75, // $0.0015 / 1K tokens + "gpt-3.5-turbo-0301": 0.75, + "gpt-3.5-turbo-0613": 0.75, + "gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens + "gpt-3.5-turbo-16k-0613": 1.5, + "text-ada-001": 0.2, + "text-babbage-001": 0.25, + "text-curie-001": 1, + "text-davinci-002": 10, + "text-davinci-003": 10, + "text-davinci-edit-001": 10, + "code-davinci-edit-001": 10, + "whisper-1": 10, + "davinci": 10, + "curie": 10, + "babbage": 10, + "ada": 10, + "text-embedding-ada-002": 0.05, + "text-search-ada-doc-001": 10, + "text-moderation-stable": 0.1, + "text-moderation-latest": 0.1, + "dall-e": 8, + "claude-instant-1": 0.815, // $1.63 / 1M tokens + "claude-2": 5.51, // $11.02 / 1M tokens + "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens + "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens + "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens + "PaLM-2": 1, + "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens + "chatglm_std": 0.3572, // ¥0.005 / 1k tokens + "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens + "qwen-v1": 0.8572, // TBD: https://help.aliyun.com/document_detail/2399482.html?spm=a2c4g.2399482.0.0.1ad347feilAgag + "qwen-plus-v1": 0.5715, // Same as above + "SparkDesk": 0.8572, // TBD + "360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens + "embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens + "embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens + "semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens + "360GPT_S2_V9.4": 0.8572, // ¥0.012 / 1k tokens } func ModelRatio2JSONString() string { diff --git a/controller/channel-test.go b/controller/channel-test.go index 4acb2e3b..686521ef 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -24,6 +24,10 @@ func testChannel(channel *model.Channel, request ChatRequest) (error, *OpenAIErr fallthrough case common.ChannelTypeZhipu: fallthrough + case common.ChannelTypeAli: + fallthrough + case common.ChannelType360: + fallthrough case common.ChannelTypeXunfei: return errors.New("该渠道类型当前版本不支持测试,请手动测试"), nil case common.ChannelTypeAzure: diff --git a/controller/model.go b/controller/model.go index c68aa50c..a8ac6a65 100644 --- a/controller/model.go +++ b/controller/model.go @@ -360,6 +360,51 @@ func init() { Root: "SparkDesk", Parent: nil, }, + { + Id: "360GPT_S2_V9", + Object: "model", + Created: 1677649963, + OwnedBy: "360", + Permission: permission, + Root: "360GPT_S2_V9", + Parent: nil, + }, + { + Id: "embedding-bert-512-v1", + Object: "model", + Created: 1677649963, + OwnedBy: "360", + Permission: permission, + Root: "embedding-bert-512-v1", + Parent: nil, + }, + { + Id: "embedding_s1_v1", + Object: "model", + Created: 1677649963, + OwnedBy: "360", + Permission: permission, + Root: "embedding_s1_v1", + Parent: nil, + }, + { + Id: "semantic_similarity_s1_v1", + Object: "model", + Created: 1677649963, + OwnedBy: "360", + Permission: permission, + Root: "semantic_similarity_s1_v1", + Parent: nil, + }, + { + Id: "360GPT_S2_V9.4", + Object: "model", + Created: 1677649963, + OwnedBy: "360", + Permission: permission, + Root: "360GPT_S2_V9.4", + Parent: nil, + }, } openAIModelsMap = make(map[string]OpenAIModels) for _, model := range openAIModels { diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index a17ef374..a14c4e0f 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -7,6 +7,7 @@ export const CHANNEL_OPTIONS = [ { key: 17, text: '阿里通义千问', value: 17, color: 'orange' }, { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' }, { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' }, + { key: 19, text: '360 智脑', value: 19, color: 'blue' }, { key: 8, text: '自定义渠道', value: 8, color: 'pink' }, { key: 2, text: '代理:API2D', value: 2, color: 'blue' }, { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' }, diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index fcbdb980..5d8951a1 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -61,6 +61,9 @@ const EditChannel = () => { case 18: localModels = ['SparkDesk']; break; + case 19: + localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1', '360GPT_S2_V9.4'] + break; } setInputs((inputs) => ({ ...inputs, models: localModels })); } From d09d317459e8749b28622f7c3bd710dc9821819f Mon Sep 17 00:00:00 2001 From: JustSong Date: Sun, 27 Aug 2023 15:28:23 +0800 Subject: [PATCH 21/22] feat: supper whisper now (close #197) --- common/model-ratio.go | 2 +- controller/model.go | 9 +++ controller/relay-audio.go | 147 ++++++++++++++++++++++++++++++++++++++ controller/relay.go | 9 +++ middleware/distributor.go | 10 ++- router/relay-router.go | 4 +- 6 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 controller/relay-audio.go diff --git a/common/model-ratio.go b/common/model-ratio.go index 3f4f64b7..70758805 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -31,7 +31,7 @@ var ModelRatio = map[string]float64{ "text-davinci-003": 10, "text-davinci-edit-001": 10, "code-davinci-edit-001": 10, - "whisper-1": 10, + "whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens "davinci": 10, "curie": 10, "babbage": 10, diff --git a/controller/model.go b/controller/model.go index a8ac6a65..88f95f7b 100644 --- a/controller/model.go +++ b/controller/model.go @@ -63,6 +63,15 @@ func init() { Root: "dall-e", Parent: nil, }, + { + Id: "whisper-1", + Object: "model", + Created: 1677649963, + OwnedBy: "openai", + Permission: permission, + Root: "whisper-1", + Parent: nil, + }, { Id: "gpt-3.5-turbo", Object: "model", diff --git a/controller/relay-audio.go b/controller/relay-audio.go new file mode 100644 index 00000000..277ab404 --- /dev/null +++ b/controller/relay-audio.go @@ -0,0 +1,147 @@ +package controller + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "one-api/common" + "one-api/model" + + "github.com/gin-gonic/gin" +) + +func relayAudioHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { + audioModel := "whisper-1" + + tokenId := c.GetInt("token_id") + channelType := c.GetInt("channel") + userId := c.GetInt("id") + group := c.GetString("group") + + preConsumedTokens := common.PreConsumedQuota + modelRatio := common.GetModelRatio(audioModel) + groupRatio := common.GetGroupRatio(group) + ratio := modelRatio * groupRatio + preConsumedQuota := int(float64(preConsumedTokens) * ratio) + userQuota, err := model.CacheGetUserQuota(userId) + if err != nil { + return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError) + } + err = model.CacheDecreaseUserQuota(userId, preConsumedQuota) + if err != nil { + return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError) + } + if userQuota > 100*preConsumedQuota { + // in this case, we do not pre-consume quota + // because the user has enough quota + preConsumedQuota = 0 + } + if preConsumedQuota > 0 { + err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota) + if err != nil { + return errorWrapper(err, "pre_consume_token_quota_failed", http.StatusForbidden) + } + } + + // map model name + modelMapping := c.GetString("model_mapping") + if modelMapping != "" { + modelMap := make(map[string]string) + err := json.Unmarshal([]byte(modelMapping), &modelMap) + if err != nil { + return errorWrapper(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError) + } + if modelMap[audioModel] != "" { + audioModel = modelMap[audioModel] + } + } + + baseURL := common.ChannelBaseURLs[channelType] + requestURL := c.Request.URL.String() + + if c.GetString("base_url") != "" { + baseURL = c.GetString("base_url") + } + + fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) + requestBody := c.Request.Body + + req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) + if err != nil { + return errorWrapper(err, "new_request_failed", http.StatusInternalServerError) + } + req.Header.Set("Authorization", c.Request.Header.Get("Authorization")) + req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) + req.Header.Set("Accept", c.Request.Header.Get("Accept")) + + resp, err := httpClient.Do(req) + if err != nil { + return errorWrapper(err, "do_request_failed", http.StatusInternalServerError) + } + + err = req.Body.Close() + if err != nil { + return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) + } + err = c.Request.Body.Close() + if err != nil { + return errorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) + } + var audioResponse AudioResponse + + defer func() { + go func() { + quota := countTokenText(audioResponse.Text, audioModel) + quotaDelta := quota - preConsumedQuota + err := model.PostConsumeTokenQuota(tokenId, quotaDelta) + if err != nil { + common.SysError("error consuming token remain quota: " + err.Error()) + } + err = model.CacheUpdateUserQuota(userId) + if err != nil { + common.SysError("error update user quota cache: " + err.Error()) + } + if quota != 0 { + tokenName := c.GetString("token_name") + logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) + model.RecordConsumeLog(userId, 0, 0, audioModel, tokenName, quota, logContent) + model.UpdateUserUsedQuotaAndRequestCount(userId, quota) + channelId := c.GetInt("channel_id") + model.UpdateChannelUsedQuota(channelId, quota) + } + }() + }() + + responseBody, err := io.ReadAll(resp.Body) + + if err != nil { + return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError) + } + err = resp.Body.Close() + if err != nil { + return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError) + } + err = json.Unmarshal(responseBody, &audioResponse) + if err != nil { + return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError) + } + + resp.Body = io.NopCloser(bytes.NewBuffer(responseBody)) + + for k, v := range resp.Header { + c.Writer.Header().Set(k, v[0]) + } + c.Writer.WriteHeader(resp.StatusCode) + + _, err = io.Copy(c.Writer, resp.Body) + if err != nil { + return errorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError) + } + err = resp.Body.Close() + if err != nil { + return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError) + } + return nil +} diff --git a/controller/relay.go b/controller/relay.go index 6a2d58eb..056d42d3 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -24,6 +24,7 @@ const ( RelayModeModerations RelayModeImagesGenerations RelayModeEdits + RelayModeAudio ) // https://platform.openai.com/docs/api-reference/chat @@ -63,6 +64,10 @@ type ImageRequest struct { Size string `json:"size"` } +type AudioResponse struct { + Text string `json:"text,omitempty"` +} + type Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` @@ -159,11 +164,15 @@ func Relay(c *gin.Context) { relayMode = RelayModeImagesGenerations } else if strings.HasPrefix(c.Request.URL.Path, "/v1/edits") { relayMode = RelayModeEdits + } else if strings.HasPrefix(c.Request.URL.Path, "/v1/audio") { + relayMode = RelayModeAudio } var err *OpenAIErrorWithStatusCode switch relayMode { case RelayModeImagesGenerations: err = relayImageHelper(c, relayMode) + case RelayModeAudio: + err = relayAudioHelper(c, relayMode) default: err = relayTextHelper(c, relayMode) } diff --git a/middleware/distributor.go b/middleware/distributor.go index ebbde535..93827c95 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -58,7 +58,10 @@ func Distribute() func(c *gin.Context) { } else { // Select a channel for the user var modelRequest ModelRequest - err := common.UnmarshalBodyReusable(c, &modelRequest) + var err error + if !strings.HasPrefix(c.Request.URL.Path, "/v1/audio") { + err = common.UnmarshalBodyReusable(c, &modelRequest) + } if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": gin.H{ @@ -84,6 +87,11 @@ func Distribute() func(c *gin.Context) { modelRequest.Model = "dall-e" } } + if strings.HasPrefix(c.Request.URL.Path, "/v1/audio") { + if modelRequest.Model == "" { + modelRequest.Model = "whisper-1" + } + } channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model) if err != nil { message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model) diff --git a/router/relay-router.go b/router/relay-router.go index c3c84d8b..a76e42cf 100644 --- a/router/relay-router.go +++ b/router/relay-router.go @@ -26,8 +26,8 @@ func SetRelayRouter(router *gin.Engine) { relayV1Router.POST("/images/variations", controller.RelayNotImplemented) relayV1Router.POST("/embeddings", controller.Relay) relayV1Router.POST("/engines/:model/embeddings", controller.Relay) - relayV1Router.POST("/audio/transcriptions", controller.RelayNotImplemented) - relayV1Router.POST("/audio/translations", controller.RelayNotImplemented) + relayV1Router.POST("/audio/transcriptions", controller.Relay) + relayV1Router.POST("/audio/translations", controller.Relay) relayV1Router.GET("/files", controller.RelayNotImplemented) relayV1Router.POST("/files", controller.RelayNotImplemented) relayV1Router.DELETE("/files/:id", controller.RelayNotImplemented) From 56b50073798e3930cbccceaf17b6e2a6a68524b8 Mon Sep 17 00:00:00 2001 From: JustSong Date: Sun, 27 Aug 2023 16:16:45 +0800 Subject: [PATCH 22/22] feat: supper OpenRouter now (close #333, close #340) --- common/constants.go | 42 ++++++++++++++------------ controller/relay-text.go | 4 +++ web/src/constants/channel.constants.js | 1 + web/src/pages/Channel/EditChannel.js | 6 ++-- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/common/constants.go b/common/constants.go index 0ae8ae83..e5211e3d 100644 --- a/common/constants.go +++ b/common/constants.go @@ -154,26 +154,27 @@ const ( ) const ( - ChannelTypeUnknown = 0 - ChannelTypeOpenAI = 1 - ChannelTypeAPI2D = 2 - ChannelTypeAzure = 3 - ChannelTypeCloseAI = 4 - ChannelTypeOpenAISB = 5 - ChannelTypeOpenAIMax = 6 - ChannelTypeOhMyGPT = 7 - ChannelTypeCustom = 8 - ChannelTypeAILS = 9 - ChannelTypeAIProxy = 10 - ChannelTypePaLM = 11 - ChannelTypeAPI2GPT = 12 - ChannelTypeAIGC2D = 13 - ChannelTypeAnthropic = 14 - ChannelTypeBaidu = 15 - ChannelTypeZhipu = 16 - ChannelTypeAli = 17 - ChannelTypeXunfei = 18 - ChannelType360 = 19 + ChannelTypeUnknown = 0 + ChannelTypeOpenAI = 1 + ChannelTypeAPI2D = 2 + ChannelTypeAzure = 3 + ChannelTypeCloseAI = 4 + ChannelTypeOpenAISB = 5 + ChannelTypeOpenAIMax = 6 + ChannelTypeOhMyGPT = 7 + ChannelTypeCustom = 8 + ChannelTypeAILS = 9 + ChannelTypeAIProxy = 10 + ChannelTypePaLM = 11 + ChannelTypeAPI2GPT = 12 + ChannelTypeAIGC2D = 13 + ChannelTypeAnthropic = 14 + ChannelTypeBaidu = 15 + ChannelTypeZhipu = 16 + ChannelTypeAli = 17 + ChannelTypeXunfei = 18 + ChannelType360 = 19 + ChannelTypeOpenRouter = 20 ) var ChannelBaseURLs = []string{ @@ -197,4 +198,5 @@ var ChannelBaseURLs = []string{ "https://dashscope.aliyuncs.com", // 17 "", // 18 "https://ai.360.cn", // 19 + "https://openrouter.ai/api", // 20 } diff --git a/controller/relay-text.go b/controller/relay-text.go index 5298d292..624b9d01 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -282,6 +282,10 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { req.Header.Set("api-key", apiKey) } else { req.Header.Set("Authorization", c.Request.Header.Get("Authorization")) + if channelType == common.ChannelTypeOpenRouter { + req.Header.Set("HTTP-Referer", "https://github.com/songquanpeng/one-api") + req.Header.Set("X-Title", "One API") + } } case APITypeClaude: req.Header.Set("x-api-key", apiKey) diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index a14c4e0f..b1631479 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -9,6 +9,7 @@ export const CHANNEL_OPTIONS = [ { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' }, { key: 19, text: '360 智脑', value: 19, color: 'blue' }, { key: 8, text: '自定义渠道', value: 8, color: 'pink' }, + { key: 20, text: '代理:OpenRouter', value: 20, color: 'black' }, { key: 2, text: '代理:API2D', value: 2, color: 'blue' }, { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' }, { key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' }, diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index 5d8951a1..da11b588 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { API, showError, showInfo, showSuccess, verifyJSON } from '../../helpers'; import { CHANNEL_OPTIONS } from '../../constants'; @@ -19,7 +19,7 @@ const EditChannel = () => { const handleCancel = () => { navigate('/channel'); }; - + const originInputs = { name: '', type: 1, @@ -62,7 +62,7 @@ const EditChannel = () => { localModels = ['SparkDesk']; break; case 19: - localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1', '360GPT_S2_V9.4'] + localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1', '360GPT_S2_V9.4']; break; } setInputs((inputs) => ({ ...inputs, models: localModels }));