diff --git a/.env b/.env
index a7dcde5..d9b94c8 100644
--- a/.env
+++ b/.env
@@ -1,60 +1,58 @@
-# 示例与解释见 .env.example
+# Sample and explanation can be found in .env.example
-# `LOG_CHAT`: 是否记录日志
-#LOG_CHAT=true
+# `LOG_CHAT`: Whether to log the chat
+LOG_CHAT=true
-CACHE_CHAT_COMPLETION=true
+#CACHE_CHAT_COMPLETION=true
-# `CACHE_BACKEND`: MEMORY, LMDB, ROCKSDB, LevelDB
+# `CACHE_BACKEND`: Options (MEMORY, LMDB, ROCKSDB, LevelDB)
CACHE_BACKEND=MEMORY
#BENCHMARK_MODE=true
-# `OPENAI_BASE_URL`: 转发openai风格的任何服务地址,允许指定多个, 以逗号隔开。
-# 如果指定超过一个,则任何OPENAI_ROUTE_PREFIX/EXTRA_ROUTE_PREFIX都不能为根路由/
-#OPENAI_BASE_URL=https://api.openai-forward.com
+# `OPENAI_BASE_URL`: Forward any service address in the style of OpenAI; multiple can be specified, separated by commas.
+# If more than one is specified, neither OPENAI_ROUTE_PREFIX nor EXTRA_ROUTE_PREFIX can be the root route/
OPENAI_BASE_URL=https://api.openai.com
-# `OPENAI_ROUTE_PREFIX`: 可指定所有openai风格(为记录日志)服务的转发路由前缀
-OPENAI_ROUTE_PREFIX=
+# `OPENAI_ROUTE_PREFIX`: Specify the forwarding route prefix for all services in OpenAI style (for logging purposes)
+#OPENAI_ROUTE_PREFIX=
-OPENAI_API_KEY=
-FORWARD_KEY=
+#OPENAI_API_KEY=
+#FORWARD_KEY=
CHAT_COMPLETION_ROUTE=/v1/chat/completions
COMPLETION_ROUTE=/v1/completions
-# `EXTRA_BASE_URL`: 可指定任意服务转发
-EXTRA_BASE_URL=
-# `EXTRA_ROUTE_PREFIX`: 与 EXTRA_BASE_URL 匹配的路由前缀
-EXTRA_ROUTE_PREFIX=
+# `EXTRA_BASE_URL`: Specify any service for forwarding
+#EXTRA_BASE_URL=
+# `EXTRA_ROUTE_PREFIX`: Route prefix matching EXTRA_BASE_URL
+#EXTRA_ROUTE_PREFIX=
-# `REQ_RATE_LIMIT`: i.e. 对指定路由的请求速率限制, 区分用户
+# `REQ_RATE_LIMIT`: i.e., Request rate limit for specified routes, user specific
# format: {route: ratelimit-string}
# ratelimit-string format [count] [per|/] [n (optional)] [second|minute|hour|day|month|year] :ref:`ratelimit-string`: https://limits.readthedocs.io/en/stable/quickstart.html#rate-limit-string-notation
REQ_RATE_LIMIT={"/v1/chat/completions":"100/2minutes", "/v1/completions":"60/minute;600/hour"}
-# rate limit后端: [memory, redis, memcached, ...] :ref: https://limits.readthedocs.io/en/stable/storage.html#
+# Backend for rate limiting: [memory, redis, memcached, ...] :ref: https://limits.readthedocs.io/en/stable/storage.html#
#REQ_RATE_LIMIT_BACKEND=redis://localhost:6379
-# `GLOBAL_RATE_LIMIT`: 限制所有`REQ_RATE_LIMIT`没有指定的路由. 不填默认无限制
+# `GLOBAL_RATE_LIMIT`: Limits all routes not specified by `REQ_RATE_LIMIT`. If not set, there's no limit by default.
GLOBAL_RATE_LIMIT=100/minute
#`RATE_LIMIT_STRATEGY` Options: (fixed-window, fixed-window-elastic-expiry, moving-window) :ref: https://limits.readthedocs.io/en/latest/strategies.html
-# `fixed-window`: most memory efficient strategy; `moving-window`:most effective for preventing bursts but higher memory cost.
+# `fixed-window`: most memory efficient strategy; `moving-window`:most effective for preventing bursts but has higher memory cost.
RATE_LIMIT_STRATEGY=moving-window
-# 返回的token速率限制
+# Rate limit for returned tokens
TOKEN_RATE_LIMIT={"/v1/chat/completions":"60/second","/v1/completions":"60/second"}
-
-# TCP连接的超时时间(秒)
+# TCP connection timeout duration (in seconds)
TIMEOUT=6
ITER_CHUNK_TYPE=one-by-one
#ITER_CHUNK_TYPE=efficiency
-IP_BLACKLIST=
+#IP_BLACKLIST=
-# 设定时区
+# Set timezone
TZ=Asia/Shanghai
diff --git a/.env.example b/.env.example
index 7fb7155..a1616de 100644
--- a/.env.example
+++ b/.env.example
@@ -1,37 +1,46 @@
-# LOG_CHAT: 是否开启日志
+# `LOG_CHAT`: 是否开启日志
+# `LOG_CHAT`: Whether to log the chat
LOG_CHAT=true
-PRINT_CHAT=true
+#PRINT_CHAT=true
CACHE_CHAT_COMPLETION=true
-# `CACHE_BACKEND`: MEMORY, LMDB, ROCKSDB
+# `CACHE_BACKEND`: Options (MEMORY, LMDB, ROCKSDB, LevelDB)
CACHE_BACKEND=MEMORY
BENCHMARK_MODE=true
-# OPENAI_BASE_URL: 转发openai风格的任何服务地址,允许指定多个, 以逗号隔开。
+# `OPENAI_BASE_URL`: 转发openai风格的任何服务地址,允许指定多个, 以逗号隔开。
# 如果指定超过一个,则任何OPENAI_ROUTE_PREFIX/EXTRA_ROUTE_PREFIX都不能为根路由/
+# `OPENAI_BASE_URL`: Forward any service address in the style of OpenAI; multiple can be specified, separated by commas.
+# If more than one is specified, neither OPENAI_ROUTE_PREFIX nor EXTRA_ROUTE_PREFIX can be the root route/
OPENAI_BASE_URL='https://api.openai.com, http://localhost:8080'
-# OPENAI_ROUTE_PREFIX: 可指定所有openai风格(为记录日志)服务的转发路由前缀
+# `OPENAI_ROUTE_PREFIX`: 可指定所有openai风格(为记录日志)服务的转发路由前缀
+# `OPENAI_ROUTE_PREFIX`: Specify the forwarding route prefix for all services in OpenAI style (for logging purposes)
OPENAI_ROUTE_PREFIX='/openai, /localai'
CHAT_COMPLETION_ROUTE=/openai/v1/chat/completions
COMPLETION_ROUTE=/v1/completions
-# OPENAI_API_KEY:允许输入多个api key, 以逗号隔开, 形成轮询池
-OPENAI_API_KEY='sk-xxx1, sk-xxx2, sk-xxx3'
+# `OPENAI_API_KEY`:配置OpenAI 接口风格的API密钥,支持使用多个密钥,通过逗号分隔
+# `OPENAI_API_KEY`: Configure API key in OpenAI style, supports using multiple keys separated by commas
+OPENAI_API_KEY='sk-***, sk-***, sk-***'
-# FORWARD_KEY: 当前面的OPENAI_API_KEY被设置,就可以设置这里的FORWARD_KEY,客户端调用时可以使用FORWARD_KEY作为api key
-FORWARD_KEY=fk-xxx1
+# `FORWARD_KEY`: 当前面的OPENAI_API_KEY被设置,就可以设置这里的FORWARD_KEY,客户端调用时可以使用FORWARD_KEY作为api key
+# `FORWARD_KEY`: Set a custom key for proxying, multiple keys can be separated by commas.
+FORWARD_KEY='fk-***, ***, 123'
# EXTRA_BASE_URL: 可指定任意服务转发
+# `EXTRA_BASE_URL`: Specify any service for forwarding
EXTRA_BASE_URL='http://localhost:8882, http://localhost:8881'
# EXTRA_ROUTE_PREFIX: 与 EXTRA_BASE_URL 匹配的路由前缀
+# `EXTRA_ROUTE_PREFIX`: Route prefix matching EXTRA_BASE_URL
EXTRA_ROUTE_PREFIX='/tts, /translate'
# `REQ_RATE_LIMIT`: 指定路由的请求速率限制(区分用户)
+# `REQ_RATE_LIMIT`: i.e., Request rate limit for specified routes, user specific
# format: {route: ratelimit-string}
# ratelimit-string format [count] [per|/] [n (optional)] [second|minute|hour|day|month|year] :ref:`ratelimit-string`: https://limits.readthedocs.io/en/stable/quickstart.html#rate-limit-string-notation
REQ_RATE_LIMIT='{
@@ -40,6 +49,8 @@ REQ_RATE_LIMIT='{
}'
# rate limit后端: [memory, redis, Memcached, ...] :ref: https://limits.readthedocs.io/en/stable/storage.html#
+# Backend for rate limiting: [memory, redis, memcached, ...] :ref: https://limits.readthedocs.io/en/stable/storage.html#
+# Assuming your Redis service is at localhost:6379
REQ_RATE_LIMIT_BACKEND="redis://localhost:6379"
# `GLOBAL_RATE_LIMIT`: 限制所有`REQ_RATE_LIMIT`没有指定的路由. 不填默认无限制
@@ -53,12 +64,14 @@ RATE_LIMIT_STRATEGY=fixed-window
PROXY=http://localhost:7890
# `TOKEN_RATE_LIMIT` 对每一份流式返回的token速率限制 (注:这里的token并不严格等于gpt中定义的token,而是SSE的chunk)
+# Rate limit for returned tokens
TOKEN_RATE_LIMIT={"/v1/chat/completions":"20/second", "/benchmark/v1/chat/completions":"500/second"}
-
+# TCP connection timeout duration (in seconds)
TIMEOUT=10
ITER_CHUNK_TYPE=efficiency
+#ITER_CHUNK_TYPE=one-by-one
-# 设定时区
+# Set timezone
TZ=Asia/Shanghai
\ No newline at end of file
diff --git a/Examples/.gitignore b/Examples/.gitignore
index dcbdd61..ec224ff 100644
--- a/Examples/.gitignore
+++ b/Examples/.gitignore
@@ -1 +1,2 @@
config.yaml
+lite_example
diff --git a/README.md b/README.md
index 304b085..b124aad 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-**简体中文** | [**English**](./README_EN.md)
+**English** | [**简体中文**](./README_ZH.md)
@@ -34,80 +34,88 @@
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/KenyonY/openai-forward)
-[特点](#主要特性) |
-[部署指南](deploy.md) |
-[使用指南](#使用指南) |
-[配置](#配置) |
-[对话日志](#对话日志)
+
+[Features](#Key-Features) |
+[Deployment Guide](deploy_en.md) |
+[User Guide](#User-Guide) |
+[Configuration](#Configuration) |
+[Conversation Logs](#Conversation-Logs)
+
-**OpenAI-Forward** 是为大型语言模型设计的高效转发服务。其核心功能包括
-用户请求速率控制、Token速率限制、智能预测缓存、日志管理和API密钥管理等,旨在提供高效、便捷的模型转发服务。
-无论是代理本地语言模型还是云端语言模型,如 [LocalAI](https://github.com/go-skynet/LocalAI) 或 [OpenAI](https://api.openai.com),都可以借助 OpenAI Forward 轻松实现。
-凭借 fastapi、aiohttp 和 asyncio 等库的支持,OpenAI-Forward 实现了出色的异步性能。
+---
+**OpenAI-Forward** is an efficient forwarding service designed for large language models.
+Its core features include user request rate control, Token rate limits, intelligent prediction caching,
+log management, and API key management, aiming to provide a fast and convenient model forwarding
+service. Whether proxying local language models or cloud-based language models,
+such as [LocalAI](https://github.com/go-skynet/LocalAI) or [OpenAI](https://api.openai.com),
+OpenAI Forward facilitates easy implementation.
+With the support of libraries like [uvicorn](https://github.com/encode/uvicorn), [aiohttp](https://github.com/aio-libs/aiohttp), and [asyncio](https://docs.python.org/3/library/asyncio.html),
+OpenAI-Forward achieves impressive asynchronous performance.
-## 主要特性
-
-OpenAI-Forward 提供以下核心功能:
+## Key Features
-- **全能转发**:可转发几乎所有类型的请求
-- **性能优先**:拥有出色的异步性能
-- **缓存AI预测**:对AI预测进行缓存,加速服务访问并节省费用
-- **用户流量控制**:自定义请求与Token速率
-- **实时响应日志**:优化调用链的可观察性
-- **自定义秘钥**:替代原始API密钥
-- **多目标路由**:转发多个服务地址至同一服务下的不同路由
-- **自动重试**:确保服务的稳定性,请求失败时将自动重试
-- **快速部署**:支持通过pip和docker在本地或云端进行快速部署
+OpenAI-Forward offers the following capabilities:
+- **Universal Forwarding**: Supports forwarding of almost all types of requests.
+- **Performance First**: Boasts outstanding asynchronous performance.
+- **Cache AI Predictions**: Caches AI predictions, accelerating service access and saving costs.
+- **User Traffic Control**: Customize request and Token rates.
+- **Real-time Response Logs**: Enhances observability of the call chain.
+- **Custom Secret Keys**: Replaces the original API keys.
+- **Multi-target Routing**: Forwards to multiple service addresses under a single service to different routes.
+- **Automatic Retries**: Ensures service stability; will automatically retry on failed requests.
+- **Quick Deployment**: Supports fast deployment locally or on the cloud via pip and docker.
+**Proxy services set up by this project include**:
-**由本项目搭建的代理服务地址:**
-
-- 原始OpenAI 服务地址
+- Original OpenAI Service Address:
> https://api.openai-forward.com
> https://render.openai-forward.com
-- 开启缓存的服务地址(用户请求结果将被保存一段时间)
+- Cached Service Address (User request results will be saved for some time):
> https://smart.openai-forward.com
-注:此处部署的代理服务仅供个人学习和研究目的使用,勿用于任何商业用途。
+Note: The proxy services deployed here are for personal learning and research purposes only and should not be used for any commercial purposes.
-## 部署指南
-👉 [部署文档](deploy.md)
+---
+
+## Deployment Guide
+
+👉 [Deployment Documentation](deploy_en.md)
-
+
-## 使用指南
+## User Guide
-### 快速入门
+### Quick Start
-**安装**
+**Installation**
```bash
pip install openai-forward
```
-**启动服务**
+**Starting the Service**
```bash
aifd run
```
-如果读入了根路径的`.env`的配置, 将会看到以下启动信息
+If the configuration from the `.env` file at the root path is read, you will see the following startup information.
```bash
❯ aifd run
@@ -135,19 +143,21 @@ INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
```
-### 代理OpenAI模型:
+---
-`aifd run`的默认选项便是代理`https://api.openai.com`
+### Proxy OpenAI Model:
-下面以搭建好的服务地址`https://api/openai-forward.com` 为例
+The default option for `aifd run` is to proxy `https://api.openai.com`.
-
- 点击展开
+The following uses the set up service address `https://api.openai-forward.com` as an example.
-#### 在三方应用中使用
+
+ Click to expand
-基于开源项目[ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web)中接入:
-替换docker启动命令中的 `BASE_URL`为自己搭建的代理服务地址
+#### Use in Third-party Applications
+
+Integrate within the open-source project [ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web):
+Replace the `BASE_URL` in the Docker startup command with the address of your self-hosted proxy service.
```bash
docker run -d \
@@ -158,7 +168,9 @@ docker run -d \
yidadaa/chatgpt-next-web
```
-#### 在代码中接入
+#### Integrate within Code
+
+---
**Python**
@@ -206,86 +218,81 @@ curl --location 'https://api.openai-forward.com/v1/images/generations' \
-### 代理本地模型
+---
+### Proxy Local Model
-- **适用场景:** 与 [LocalAI](https://github.com/go-skynet/LocalAI),
- [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)等项目一起使用
+- **Applicable scenarios:** To be used in conjunction with projects such as [LocalAI](https://github.com/go-skynet/LocalAI) and [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm).
-- **如何操作:**
- 以LocalAI为例,如果已在 http://localhost:8080 部署了LocalAI服务,仅需在环境变量或 .env
- 文件中设置 `OPENAI_BASE_URL=http://localhost:8080`。
- 然后即可通过访问 http://localhost:8000 使用LocalAI。
+- **How to operate:**
+ Using LocalAI as an example, if the LocalAI service has been deployed at http://localhost:8080, you only need to set `OPENAI_BASE_URL=http://localhost:8080` in the environment variable or in the .env file.
+ After that, you can access LocalAI through http://localhost:8000.
-(更多)
+(More)
-### 代理其它云端模型
+### Proxy Other Cloud Models
-- **适用场景:**
- 例如可通过 [LiteLLM](https://github.com/BerriAI/litellm) 可以将 众多云模型的 API 格式转换为 openai
- 的api格式,
- 然后使用本服务进行代理。
+- **Applicable scenarios:**
+ For instance, through [LiteLLM](https://github.com/BerriAI/litellm), you can convert the API format of many cloud models to the OpenAI API format and then use this service as a proxy.
-(更多)
+(More)
-## 配置
+## Configuration
-### 命令行参数
+### Command Line Arguments
-执行 `aifd run --help` 获取参数详情
+Execute `aifd run --help` to get details on arguments.
Click for more details
-| 配置项 | 说明 | 默认值 |
-|-----------|-------|:----:|
-| --port | 服务端口号 | 8000 |
-| --workers | 工作进程数 | 1 |
+| Configuration | Description | Default Value |
+|---------------|-------------|:-------------:|
+| --port | Service port | 8000 |
+| --workers | Number of working processes | 1 |
-### 环境变量详情
-
-你可以在项目的运行目录下创建 .env 文件来定制各项配置。参考配置可见根目录下的
-[.env.example](.env.example)文件
-
-| 环境变量 | 说明 | 默认值 |
-|-----------------------|----------------------------------------------------------------------|:----------------------:|
-| OPENAI_BASE_URL | 设置OpenAI API风格的基础地址 | https://api.openai.com |
-| OPENAI_ROUTE_PREFIX | 为OPENAI_BASE_URL接口地址定义路由前缀 | / |
-| OPENAI_API_KEY | 配置OpenAI 接口风格的API密钥,支持使用多个密钥,通过逗号分隔 | 无 |
-| FORWARD_KEY | 设定用于代理的自定义密钥,多个密钥可用逗号分隔。如果未设置(不建议),将直接使用 `OPENAI_API_KEY` | 无 |
-| EXTRA_BASE_URL | 用于配置额外代理服务的基础URL | 无 |
-| EXTRA_ROUTE_PREFIX | 定义额外代理服务的路由前缀 | 无 |
-| REQ_RATE_LIMIT | 设置特定路由的用户请求速率限制 (区分用户) | 无 |
-| GLOBAL_RATE_LIMIT | 配置全局请求速率限制,适用于未在 `REQ_RATE_LIMIT` 中指定的路由 | 无 |
-| RATE_LIMIT_STRATEGY | 选择速率限制策略,选项包括:fixed-window、fixed-window-elastic-expiry、moving-window | 无 |
-| TOKEN_RATE_LIMIT | 限制流式响应中每个token(或SSE chunk)的输出速率 | 无 |
-| PROXY | 设置HTTP代理地址 | 无 |
-| LOG_CHAT | 开关聊天内容的日志记录,用于调试和监控 | `false` |
-| CACHE_BACKEND | cache后端,支持内存后端和数据库后端,默认为内存后端,可选lmdb, rocksdb和leveldb数据库后端 | `MEMORY` |
-| CACHE_CHAT_COMPLETION | 是否缓存/v1/chat/completions 结果 | `false` |
-
-详细配置说明可参见 [.env.example](.env.example) 文件。(待完善)
-
-> 注意:如果你设置了 OPENAI_API_KEY 但未设置 FORWARD_KEY,客户端在调用时将不需要提供密钥。由于这可能存在安全风险,除非有明确需求,否则不推荐将
-> FORWARD_KEY 置空。
+### Environment Variable Details
+
+You can create a .env file in the project's run directory to customize configurations. For a reference configuration, see the [.env.example](.env.example) file in the root directory.
+
+| Environment Variable | Description | Default Value |
+|-----------------------|-------------------------------------------------------------------------------------------------|:-----------------------------:|
+| OPENAI_BASE_URL | Set base address for OpenAI-style API | https://api.openai.com |
+| OPENAI_ROUTE_PREFIX | Define a route prefix for the OPENAI_BASE_URL interface address | / |
+| OPENAI_API_KEY | Configure API key in OpenAI style, supports using multiple keys separated by commas | None |
+| FORWARD_KEY | Set a custom key for proxying, multiple keys can be separated by commas. If not set (not recommended), it will directly use `OPENAI_API_KEY` | None |
+| EXTRA_BASE_URL | Configure the base URL for additional proxy services | None |
+| EXTRA_ROUTE_PREFIX | Define the route prefix for additional proxy services | None |
+| REQ_RATE_LIMIT | Set the user request rate limit for specific routes (user distinguished) | None |
+| GLOBAL_RATE_LIMIT | Configure a global request rate limit applicable to routes not specified in `REQ_RATE_LIMIT` | None |
+| RATE_LIMIT_STRATEGY | Choose a rate limit strategy, options include: fixed-window, fixed-window-elastic-expiry, moving-window | None |
+| TOKEN_RATE_LIMIT | Limit the output rate of each token (or SSE chunk) in a streaming response | None |
+| PROXY | Set HTTP proxy address | None |
+| LOG_CHAT | Toggle chat content logging for debugging and monitoring | `false` |
+| CACHE_BACKEND | Cache backend, supports memory backend and database backend. By default, it's memory backend, optional database backends are lmdb, rocksdb, and leveldb | `MEMORY` |
+| CACHE_CHAT_COMPLETION | Whether to cache /v1/chat/completions results | `false` |
+
+Detailed configuration descriptions can be seen in the [.env.example](.env.example) file. (To be completed)
+
+> Note: If you set OPENAI_API_KEY but did not set FORWARD_KEY, clients will not need to provide a key when calling. As this may pose a security risk, it's not recommended to leave FORWARD_KEY unset unless there's a specific need.
### Caching
-缓存默认使用内存后端,可选择数据库后端,需安装相应的环境:
+By default, caching uses a memory backend. You can choose a database backend but need to install the corresponding environment:
```bash
-pip install openai-forward[lmdb] # lmdb后端
-pip install openai-forward[leveldb] # leveldb后端
-pip install openai-forward[rocksdb] # rocksdb后端
+pip install openai-forward[lmdb] # lmdb backend
+pip install openai-forward[leveldb] # leveldb backend
+pip install openai-forward[rocksdb] # rocksdb backend
```
-- 配置环境变量中`CACHE_BACKEND`以使用相应的数据库后端进行存储。 可选值`MEMORY`、`LMDB`、`ROCKSDB`、`LEVELDB`
-- 配置`CACHE_CHAT_COMPLETION`为`true`以缓存/v1/chat/completions 结果。
+- Configure `CACHE_BACKEND` in the environment variable to use the respective database backend for storage. Options are `MEMORY`, `LMDB`, `ROCKSDB`, and `LEVELDB`.
+- Set `CACHE_CHAT_COMPLETION` to `true` to cache /v1/chat/completions results.
```diff
import openai
@@ -293,7 +300,7 @@ pip install openai-forward[rocksdb] # rocksdb后端
openai.api_key = "sk-******"
completion = openai.ChatCompletion.create(
-+ caching=False, # 默认缓存,可以设置为不缓存
++ caching=False, # Cache by default, can be set to not cache
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "Hello!"}
@@ -301,19 +308,19 @@ pip install openai-forward[rocksdb] # rocksdb后端
)
```
-### 自定义秘钥
+### Custom Keys
Click for more details
-需要配置 OPENAI_API_KEY 和 FORWARD_KEY, 如
+Configure OPENAI_API_KEY and FORWARD_KEY, for example:
```bash
OPENAI_API_KEY=sk-*******
-FORWARD_KEY=fk-****** # 这里fk-token由我们自己定义
+FORWARD_KEY=fk-****** # Here, the fk-token is customized
```
-**用例:**
+**Use case:**
```diff
import openai
@@ -324,33 +331,30 @@ FORWARD_KEY=fk-****** # 这里fk-token由我们自己定义
-### 多目标服务转发
+### Multi-Target Service Forwarding
-支持转发不同地址的服务至同一端口的不同路由下
-用例见 `.env.example`
+Supports forwarding services from different addresses to different routes under the same port. Refer to the `.env.example` for examples.
-### 对话日志
+### Conversation Logs
-默认不记录对话日志,若要开启需设置环境变量`LOG_CHAT=true`
+Chat logs are not recorded by default. If you wish to enable it, set the `LOG_CHAT=true` environment variable.
Click for more details
-保存路径在当前目录下的`Log/openai/chat/chat.log`路径中。
-记录格式为
-
+Logs are saved in the current directory under `Log/openai/chat/chat.log`. The recording format is:
```text
{'messages': [{'role': 'user', 'content': 'hi'}], 'model': 'gpt-3.5-turbo', 'stream': True, 'max_tokens': None, 'n': 1, 'temperature': 1, 'top_p': 1, 'logit_bias': None, 'frequency_penalty': 0, 'presence_penalty': 0, 'stop': None, 'user': None, 'ip': '127.0.0.1', 'uid': '2155fe1580e6aed626aa1ad74c1ce54e', 'datetime': '2023-10-17 15:27:12'}
{'assistant': 'Hello! How can I assist you today?', 'is_function_call': False, 'uid': '2155fe1580e6aed626aa1ad74c1ce54e'}
```
-转换为`json`格式:
+
+To convert to `json` format:
```bash
aifd convert
```
-得到`chat_openai.json`:
-
+You'll get `chat_openai.json`:
```json
[
{
@@ -370,15 +374,16 @@ aifd convert
]
```
+
-## 赞助者与支持者
+## Backer and Sponsor
-## 许可证
+## License
-OpenAI-Forward 采用 [MIT](https://opensource.org/license/mit/) 许可证。
+OpenAI-Forward is licensed under the [MIT](https://opensource.org/license/mit/) license.
diff --git a/README_EN.md b/README_EN.md
deleted file mode 100644
index ac3b312..0000000
--- a/README_EN.md
+++ /dev/null
@@ -1,382 +0,0 @@
-**English** | [**简体中文**](./README.md)
-
-
-
- OpenAI Forward
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/KenyonY/openai-forward)
-
-
-[Features](#Key-Features) |
-[Deployment Guide](deploy_en.md) |
-[User Guide](#User-Guide) |
-[Configuration](#Configuration) |
-[Conversation Logs](#Conversation-Logs)
-
-
-
-
-
----
-
-**OpenAI-Forward** is an efficient forwarding service designed for large language models. Its core features include user request rate control, Token rate limits, intelligent prediction caching, log management, and API key management, aiming to provide a fast and convenient model forwarding service. Whether proxying local language models or cloud-based language models, such as [LocalAI](https://github.com/go-skynet/LocalAI) or [OpenAI](https://api.openai.com), OpenAI Forward facilitates easy implementation. With the support of libraries like fastapi, aiohttp, and asyncio, OpenAI-Forward achieves impressive asynchronous performance.
-
-
-
-
-
-## Key Features
-
-OpenAI-Forward offers the following capabilities:
-
-- **Universal Forwarding**: Supports forwarding of almost all types of requests.
-- **Performance First**: Boasts outstanding asynchronous performance.
-- **Cache AI Predictions**: Caches AI predictions, accelerating service access and saving costs.
-- **User Traffic Control**: Customize request and Token rates.
-- **Real-time Response Logs**: Enhances observability of the call chain.
-- **Custom Secret Keys**: Replaces the original API keys.
-- **Multi-target Routing**: Forwards to multiple service addresses under a single service to different routes.
-- **Automatic Retries**: Ensures service stability; will automatically retry on failed requests.
-- **Quick Deployment**: Supports fast deployment locally or on the cloud via pip and docker.
-
-**Proxy services set up by this project include**:
-
-- Original OpenAI Service Address:
- > https://api.openai-forward.com
- > https://render.openai-forward.com
-
-- Cached Service Address (User request results will be saved for some time):
- > https://smart.openai-forward.com
-
-
-Note: The proxy services deployed here are for personal learning and research purposes only and should not be used for any commercial purposes.
-
-
-
-
----
-
-## Deployment Guide
-
-👉 [Deployment Documentation](deploy_en.md)
-
-
-
-
-
-## User Guide
-
-### Quick Start
-
-**Installation**
-
-```bash
-pip install openai-forward
-```
-
-**Starting the Service**
-
-```bash
-aifd run
-```
-
-If the configuration from the `.env` file at the root path is read, you will see the following startup information.
-
-```bash
-❯ aifd run
-╭────── 🤗 openai-forward is ready to serve! ───────╮
-│ │
-│ base url https://api.openai.com │
-│ route prefix / │
-│ api keys False │
-│ forward keys False │
-│ cache_backend MEMORY │
-╰────────────────────────────────────────────────────╯
-╭──────────── ⏱️ Rate Limit configuration ───────────╮
-│ │
-│ backend memory │
-│ strategy moving-window │
-│ global rate limit 100/minute (req) │
-│ /v1/chat/completions 100/2minutes (req) │
-│ /v1/completions 60/minute;600/hour (req) │
-│ /v1/chat/completions 60/second (token) │
-│ /v1/completions 60/second (token) │
-╰────────────────────────────────────────────────────╯
-INFO: Started server process [191471]
-INFO: Waiting for application startup.
-INFO: Application startup complete.
-INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
-```
-
----
-
-### Proxy OpenAI Model:
-
-The default option for `aifd run` is to proxy `https://api.openai.com`.
-
-The following uses the set up service address `https://api.openai-forward.com` as an example.
-
-
- Click to expand
-
-#### Use in Third-party Applications
-
-Integrate within the open-source project [ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web):
-Replace the `BASE_URL` in the Docker startup command with the address of your self-hosted proxy service.
-
-```bash
-docker run -d \
- -p 3000:3000 \
- -e OPENAI_API_KEY="sk-******" \
- -e BASE_URL="https://api.openai-forward.com" \
- -e CODE="******" \
- yidadaa/chatgpt-next-web
-```
-
-#### Integrate within Code
-
----
-
-**Python**
-
-```diff
- import openai
-+ openai.api_base = "https://api.openai-forward.com/v1"
- openai.api_key = "sk-******"
-```
-
-**JS/TS**
-
-```diff
- import { Configuration } from "openai";
-
- const configuration = new Configuration({
-+ basePath: "https://api.openai-forward.com/v1",
- apiKey: "sk-******",
- });
-```
-
-**gpt-3.5-turbo**
-
-```bash
-curl https://api.openai-forward.com/v1/chat/completions \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer sk-******" \
- -d '{
- "model": "gpt-3.5-turbo",
- "messages": [{"role": "user", "content": "Hello!"}]
- }'
-```
-
-**Image Generation (DALL-E)**
-
-```bash
-curl --location 'https://api.openai-forward.com/v1/images/generations' \
---header 'Authorization: Bearer sk-******' \
---header 'Content-Type: application/json' \
---data '{
- "prompt": "A photo of a cat",
- "n": 1,
- "size": "512x512"
-}'
-```
-
-
-
----
-### Proxy Local Model
-
-- **Applicable scenarios:** To be used in conjunction with projects such as [LocalAI](https://github.com/go-skynet/LocalAI) and [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm).
-
-- **How to operate:**
- Using LocalAI as an example, if the LocalAI service has been deployed at http://localhost:8080, you only need to set `OPENAI_BASE_URL=http://localhost:8080` in the environment variable or in the .env file.
- After that, you can access LocalAI through http://localhost:8000.
-
-(More)
-
-### Proxy Other Cloud Models
-
-- **Applicable scenarios:**
- For instance, through [LiteLLM](https://github.com/BerriAI/litellm), you can convert the API format of many cloud models to the OpenAI API format and then use this service as a proxy.
-
-(More)
-
-
-
-
-
-## Configuration
-
-### Command Line Arguments
-
-Execute `aifd run --help` to get details on arguments.
-
-
- Click for more details
-
-| Configuration | Description | Default Value |
-|---------------|-------------|:-------------:|
-| --port | Service port | 8000 |
-| --workers | Number of working processes | 1 |
-
-
-
-### Environment Variable Details
-
-You can create a .env file in the project's run directory to customize configurations. For a reference configuration, see the [.env.example](.env.example) file in the root directory.
-
-| Environment Variable | Description | Default Value |
-|-----------------------|-------------------------------------------------------------------------------------------------|:-----------------------------:|
-| OPENAI_BASE_URL | Set base address for OpenAI-style API | https://api.openai.com |
-| OPENAI_ROUTE_PREFIX | Define a route prefix for the OPENAI_BASE_URL interface address | / |
-| OPENAI_API_KEY | Configure API key in OpenAI style, supports using multiple keys separated by commas | None |
-| FORWARD_KEY | Set a custom key for proxying, multiple keys can be separated by commas. If not set (not recommended), it will directly use `OPENAI_API_KEY` | None |
-| EXTRA_BASE_URL | Configure the base URL for additional proxy services | None |
-| EXTRA_ROUTE_PREFIX | Define the route prefix for additional proxy services | None |
-| REQ_RATE_LIMIT | Set the user request rate limit for specific routes (user distinguished) | None |
-| GLOBAL_RATE_LIMIT | Configure a global request rate limit applicable to routes not specified in `REQ_RATE_LIMIT` | None |
-| RATE_LIMIT_STRATEGY | Choose a rate limit strategy, options include: fixed-window, fixed-window-elastic-expiry, moving-window | None |
-| TOKEN_RATE_LIMIT | Limit the output rate of each token (or SSE chunk) in a streaming response | None |
-| PROXY | Set HTTP proxy address | None |
-| LOG_CHAT | Toggle chat content logging for debugging and monitoring | `false` |
-| CACHE_BACKEND | Cache backend, supports memory backend and database backend. By default, it's memory backend, optional database backends are lmdb, rocksdb, and leveldb | `MEMORY` |
-| CACHE_CHAT_COMPLETION | Whether to cache /v1/chat/completions results | `false` |
-
-Detailed configuration descriptions can be seen in the [.env.example](.env.example) file. (To be completed)
-
-> Note: If you set OPENAI_API_KEY but did not set FORWARD_KEY, clients will not need to provide a key when calling. As this may pose a security risk, it's not recommended to leave FORWARD_KEY unset unless there's a specific need.
-
-### Caching
-
-By default, caching uses a memory backend. You can choose a database backend but need to install the corresponding environment:
-
-```bash
-pip install openai-forward[lmdb] # lmdb backend
-pip install openai-forward[leveldb] # leveldb backend
-pip install openai-forward[rocksdb] # rocksdb backend
-```
-
-- Configure `CACHE_BACKEND` in the environment variable to use the respective database backend for storage. Options are `MEMORY`, `LMDB`, `ROCKSDB`, and `LEVELDB`.
-- Set `CACHE_CHAT_COMPLETION` to `true` to cache /v1/chat/completions results.
-
-```diff
- import openai
- openai.api_base = "https://smart.openai-forward.com/v1"
- openai.api_key = "sk-******"
-
- completion = openai.ChatCompletion.create(
-+ caching=False, # Cache by default, can be set to not cache
- model="gpt-3.5-turbo",
- messages=[
- {"role": "user", "content": "Hello!"}
- ]
-)
-```
-
-### Custom Keys
-
-
- Click for more details
-
-Configure OPENAI_API_KEY and FORWARD_KEY, for example:
-
-```bash
-OPENAI_API_KEY=sk-*******
-FORWARD_KEY=fk-****** # Here, the fk-token is customized
-```
-
-**Use case:**
-
-```diff
- import openai
-+ openai.api_base = "https://api.openai-forward.com/v1"
-- openai.api_key = "sk-******"
-+ openai.api_key = "fk-******"
-```
-
-
-
-### Multi-Target Service Forwarding
-
-Supports forwarding services from different addresses to different routes under the same port. Refer to the `.env.example` for examples.
-
-### Conversation Logs
-
-Chat logs are not recorded by default. If you wish to enable it, set the `LOG_CHAT=true` environment variable.
-
- Click for more details
-
-Logs are saved in the current directory under `Log/openai/chat/chat.log`. The recording format is:
-```text
-{'messages': [{'role': 'user', 'content': 'hi'}], 'model': 'gpt-3.5-turbo', 'stream': True, 'max_tokens': None, 'n': 1, 'temperature': 1, 'top_p': 1, 'logit_bias': None, 'frequency_penalty': 0, 'presence_penalty': 0, 'stop': None, 'user': None, 'ip': '127.0.0.1', 'uid': '2155fe1580e6aed626aa1ad74c1ce54e', 'datetime': '2023-10-17 15:27:12'}
-{'assistant': 'Hello! How can I assist you today?', 'is_function_call': False, 'uid': '2155fe1580e6aed626aa1ad74c1ce54e'}
-```
-
-
-To convert to `json` format:
-
-```bash
-aifd convert
-```
-
-You'll get `chat_openai.json`:
-```json
-[
- {
- "datetime": "2023-10-17 15:27:12",
- "ip": "127.0.0.1",
- "model": "gpt-3.5-turbo",
- "temperature": 1,
- "messages": [
- {
- "user": "hi"
- }
- ],
- "functions": null,
- "is_function_call": false,
- "assistant": "Hello! How can I assist you today?"
- }
-]
-```
-
-
-
-
-
-## Backer and Sponsor
-
-
-
-
-
-## License
-
-OpenAI-Forward is licensed under the [MIT](https://opensource.org/license/mit/) license.
diff --git a/README_ZH.md b/README_ZH.md
new file mode 100644
index 0000000..973b378
--- /dev/null
+++ b/README_ZH.md
@@ -0,0 +1,385 @@
+**简体中文** | [**English**](./README.md)
+
+
+
+ OpenAI Forward
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/KenyonY/openai-forward)
+
+[特点](#主要特性) |
+[部署指南](deploy.md) |
+[使用指南](#使用指南) |
+[配置](#配置) |
+[对话日志](#对话日志)
+
+
+
+
+**OpenAI-Forward** 是为大型语言模型设计的高效转发服务。其核心功能包括
+用户请求速率控制、Token速率限制、智能预测缓存、日志管理和API密钥管理等,旨在提供高效、便捷的模型转发服务。
+无论是代理本地语言模型还是云端语言模型,如 [LocalAI](https://github.com/go-skynet/LocalAI) 或 [OpenAI](https://api.openai.com),都可以由 OpenAI Forward 轻松实现。
+凭借 [uvicorn](https://github.com/encode/uvicorn), [aiohttp](https://github.com/aio-libs/aiohttp), 和 [asyncio](https://docs.python.org/3/library/asyncio.html)
+等库支持,OpenAI-Forward 实现了出色的异步性能。
+
+
+
+
+
+
+## 主要特性
+
+OpenAI-Forward 提供以下核心功能:
+
+- **全能转发**:可转发几乎所有类型的请求
+- **性能优先**:拥有出色的异步性能
+- **缓存AI预测**:对AI预测进行缓存,加速服务访问并节省费用
+- **用户流量控制**:自定义请求与Token速率
+- **实时响应日志**:优化调用链的可观察性
+- **自定义秘钥**:替代原始API密钥
+- **多目标路由**:转发多个服务地址至同一服务下的不同路由
+- **自动重试**:确保服务的稳定性,请求失败时将自动重试
+- **快速部署**:支持通过pip和docker在本地或云端进行快速部署
+
+
+
+**由本项目搭建的代理服务地址:**
+
+- 原始OpenAI 服务地址
+ > https://api.openai-forward.com
+ > https://render.openai-forward.com
+
+- 开启缓存的服务地址(用户请求结果将被保存一段时间)
+ > https://smart.openai-forward.com
+
+
+注:此处部署的代理服务仅供个人学习和研究目的使用,勿用于任何商业用途。
+
+
+## 部署指南
+
+👉 [部署文档](deploy.md)
+
+
+
+
+
+
+## 使用指南
+
+### 快速入门
+
+**安装**
+
+```bash
+pip install openai-forward
+```
+
+**启动服务**
+
+```bash
+aifd run
+```
+
+如果读入了根路径的`.env`的配置, 将会看到以下启动信息
+
+```bash
+❯ aifd run
+╭────── 🤗 openai-forward is ready to serve! ───────╮
+│ │
+│ base url https://api.openai.com │
+│ route prefix / │
+│ api keys False │
+│ forward keys False │
+│ cache_backend MEMORY │
+╰────────────────────────────────────────────────────╯
+╭──────────── ⏱️ Rate Limit configuration ───────────╮
+│ │
+│ backend memory │
+│ strategy moving-window │
+│ global rate limit 100/minute (req) │
+│ /v1/chat/completions 100/2minutes (req) │
+│ /v1/completions 60/minute;600/hour (req) │
+│ /v1/chat/completions 60/second (token) │
+│ /v1/completions 60/second (token) │
+╰────────────────────────────────────────────────────╯
+INFO: Started server process [191471]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
+INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
+```
+
+### 代理OpenAI模型:
+
+`aifd run`的默认选项便是代理`https://api.openai.com`
+
+下面以搭建好的服务地址`https://api/openai-forward.com` 为例
+
+
+ 点击展开
+
+#### 在三方应用中使用
+
+基于开源项目[ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web)中接入:
+替换docker启动命令中的 `BASE_URL`为自己搭建的代理服务地址
+
+```bash
+docker run -d \
+ -p 3000:3000 \
+ -e OPENAI_API_KEY="sk-******" \
+ -e BASE_URL="https://api.openai-forward.com" \
+ -e CODE="******" \
+ yidadaa/chatgpt-next-web
+```
+
+#### 在代码中接入
+
+**Python**
+
+```diff
+ import openai
++ openai.api_base = "https://api.openai-forward.com/v1"
+ openai.api_key = "sk-******"
+```
+
+**JS/TS**
+
+```diff
+ import { Configuration } from "openai";
+
+ const configuration = new Configuration({
++ basePath: "https://api.openai-forward.com/v1",
+ apiKey: "sk-******",
+ });
+```
+
+**gpt-3.5-turbo**
+
+```bash
+curl https://api.openai-forward.com/v1/chat/completions \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer sk-******" \
+ -d '{
+ "model": "gpt-3.5-turbo",
+ "messages": [{"role": "user", "content": "Hello!"}]
+ }'
+```
+
+**Image Generation (DALL-E)**
+
+```bash
+curl --location 'https://api.openai-forward.com/v1/images/generations' \
+--header 'Authorization: Bearer sk-******' \
+--header 'Content-Type: application/json' \
+--data '{
+ "prompt": "A photo of a cat",
+ "n": 1,
+ "size": "512x512"
+}'
+```
+
+
+
+### 代理本地模型
+
+- **适用场景:** 与 [LocalAI](https://github.com/go-skynet/LocalAI),
+ [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)等项目一起使用
+
+- **如何操作:**
+ 以LocalAI为例,如果已在 http://localhost:8080 部署了LocalAI服务,仅需在环境变量或 .env
+ 文件中设置 `OPENAI_BASE_URL=http://localhost:8080`。
+ 然后即可通过访问 http://localhost:8000 使用LocalAI。
+
+(更多)
+
+### 代理其它云端模型
+
+- **适用场景:**
+ 例如可通过 [LiteLLM](https://github.com/BerriAI/litellm) 可以将 众多云模型的 API 格式转换为 openai
+ 的api格式,
+ 然后使用本服务进行代理。
+
+(更多)
+
+
+
+
+
+## 配置
+
+### 命令行参数
+
+执行 `aifd run --help` 获取参数详情
+
+
+ Click for more details
+
+| 配置项 | 说明 | 默认值 |
+|-----------|-------|:----:|
+| --port | 服务端口号 | 8000 |
+| --workers | 工作进程数 | 1 |
+
+
+
+### 环境变量详情
+
+你可以在项目的运行目录下创建 .env 文件来定制各项配置。参考配置可见根目录下的
+[.env.example](.env.example)文件
+
+| 环境变量 | 说明 | 默认值 |
+|-----------------------|----------------------------------------------------------------------|:----------------------:|
+| OPENAI_BASE_URL | 设置OpenAI API风格的基础地址 | https://api.openai.com |
+| OPENAI_ROUTE_PREFIX | 为OPENAI_BASE_URL接口地址定义路由前缀 | / |
+| OPENAI_API_KEY | 配置OpenAI 接口风格的API密钥,支持使用多个密钥,通过逗号分隔 | 无 |
+| FORWARD_KEY | 设定用于代理的自定义密钥,多个密钥可用逗号分隔。如果未设置(不建议),将直接使用 `OPENAI_API_KEY` | 无 |
+| EXTRA_BASE_URL | 用于配置额外代理服务的基础URL | 无 |
+| EXTRA_ROUTE_PREFIX | 定义额外代理服务的路由前缀 | 无 |
+| REQ_RATE_LIMIT | 设置特定路由的用户请求速率限制 (区分用户) | 无 |
+| GLOBAL_RATE_LIMIT | 配置全局请求速率限制,适用于未在 `REQ_RATE_LIMIT` 中指定的路由 | 无 |
+| RATE_LIMIT_STRATEGY | 选择速率限制策略,选项包括:fixed-window、fixed-window-elastic-expiry、moving-window | 无 |
+| TOKEN_RATE_LIMIT | 限制流式响应中每个token(或SSE chunk)的输出速率 | 无 |
+| PROXY | 设置HTTP代理地址 | 无 |
+| LOG_CHAT | 开关聊天内容的日志记录,用于调试和监控 | `false` |
+| CACHE_BACKEND | cache后端,支持内存后端和数据库后端,默认为内存后端,可选lmdb, rocksdb和leveldb数据库后端 | `MEMORY` |
+| CACHE_CHAT_COMPLETION | 是否缓存/v1/chat/completions 结果 | `false` |
+
+详细配置说明可参见 [.env.example](.env.example) 文件。(待完善)
+
+> 注意:如果你设置了 OPENAI_API_KEY 但未设置 FORWARD_KEY,客户端在调用时将不需要提供密钥。由于这可能存在安全风险,除非有明确需求,否则不推荐将
+> FORWARD_KEY 置空。
+
+### Caching
+
+缓存默认使用内存后端,可选择数据库后端,需安装相应的环境:
+
+```bash
+pip install openai-forward[lmdb] # lmdb后端
+pip install openai-forward[leveldb] # leveldb后端
+pip install openai-forward[rocksdb] # rocksdb后端
+```
+
+- 配置环境变量中`CACHE_BACKEND`以使用相应的数据库后端进行存储。 可选值`MEMORY`、`LMDB`、`ROCKSDB`、`LEVELDB`
+- 配置`CACHE_CHAT_COMPLETION`为`true`以缓存/v1/chat/completions 结果。
+
+```diff
+ import openai
+ openai.api_base = "https://smart.openai-forward.com/v1"
+ openai.api_key = "sk-******"
+
+ completion = openai.ChatCompletion.create(
++ caching=False, # 默认缓存,可以设置为不缓存
+ model="gpt-3.5-turbo",
+ messages=[
+ {"role": "user", "content": "Hello!"}
+ ]
+)
+```
+
+### 自定义秘钥
+
+
+ Click for more details
+
+需要配置 OPENAI_API_KEY 和 FORWARD_KEY, 如
+
+```bash
+OPENAI_API_KEY=sk-*******
+FORWARD_KEY=fk-****** # 这里fk-token由我们自己定义
+```
+
+**用例:**
+
+```diff
+ import openai
++ openai.api_base = "https://api.openai-forward.com/v1"
+- openai.api_key = "sk-******"
++ openai.api_key = "fk-******"
+```
+
+
+
+### 多目标服务转发
+
+支持转发不同地址的服务至同一端口的不同路由下
+用例见 `.env.example`
+
+### 对话日志
+
+默认不记录对话日志,若要开启需设置环境变量`LOG_CHAT=true`
+
+ Click for more details
+
+保存路径在当前目录下的`Log/openai/chat/chat.log`路径中。
+记录格式为
+
+```text
+{'messages': [{'role': 'user', 'content': 'hi'}], 'model': 'gpt-3.5-turbo', 'stream': True, 'max_tokens': None, 'n': 1, 'temperature': 1, 'top_p': 1, 'logit_bias': None, 'frequency_penalty': 0, 'presence_penalty': 0, 'stop': None, 'user': None, 'ip': '127.0.0.1', 'uid': '2155fe1580e6aed626aa1ad74c1ce54e', 'datetime': '2023-10-17 15:27:12'}
+{'assistant': 'Hello! How can I assist you today?', 'is_function_call': False, 'uid': '2155fe1580e6aed626aa1ad74c1ce54e'}
+```
+
+转换为`json`格式:
+
+```bash
+aifd convert
+```
+
+得到`chat_openai.json`:
+
+```json
+[
+ {
+ "datetime": "2023-10-17 15:27:12",
+ "ip": "127.0.0.1",
+ "model": "gpt-3.5-turbo",
+ "temperature": 1,
+ "messages": [
+ {
+ "user": "hi"
+ }
+ ],
+ "functions": null,
+ "is_function_call": false,
+ "assistant": "Hello! How can I assist you today?"
+ }
+]
+```
+
+
+
+
+## 赞助者与支持者
+
+
+
+
+
+## 许可证
+
+OpenAI-Forward 采用 [MIT](https://opensource.org/license/mit/) 许可证。
diff --git a/deploy.md b/deploy.md
index dbaaab9..d1390da 100644
--- a/deploy.md
+++ b/deploy.md
@@ -41,7 +41,7 @@ pip install openai-forward
aifd run
```
服务就搭建完成了。
-配置见[配置](README.md#配置)
+配置见[配置](README_ZH.md#配置)
### 服务调用
@@ -80,7 +80,7 @@ docker run -d -p 8000:8000 beidongjiedeguang/openai-forward:latest
注:同样可以在启动命令中通过-e传入环境变量OPENAI_API_KEY=sk-xxx作为默认api key
启用SSL同上.
-环境变量配置见[环境变量配置](README.md#配置)
+环境变量配置见[环境变量配置](README_ZH.md#配置)
## 源码部署
diff --git a/deploy_en.md b/deploy_en.md
index c252a6c..59100c9 100644
--- a/deploy_en.md
+++ b/deploy_en.md
@@ -40,7 +40,7 @@ pip install openai-forward
aifd run
```
The service is now set up.
-Configuration can be found [here](README_EN.md#configuration).
+Configuration can be found [here](README.md#configuration).
### Service Invocation
@@ -78,7 +78,7 @@ This will map the host's 8000 port. Access the service via `http://{ip}:8000`.
The log path inside the container is `/home/openai-forward/Log/`. It can be mapped when starting up.
Note: Similarly, the default API key can be passed in as an environment variable OPENAI_API_KEY=sk-xxx during startup using the -e flag.
-For SSL setup, refer to the above. Environment variable configuration can be found [here](README_EN.md#configuration).
+For SSL setup, refer to the above. Environment variable configuration can be found [here](README.md#configuration).
## Source Code Deployment
diff --git a/openai_forward/__init__.py b/openai_forward/__init__.py
index b2fadb7..75236dd 100644
--- a/openai_forward/__init__.py
+++ b/openai_forward/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.6.2"
+__version__ = "0.6.3"
from dotenv import load_dotenv
diff --git a/openai_forward/cache/database.py b/openai_forward/cache/database.py
index edf7211..6a5ffe6 100644
--- a/openai_forward/cache/database.py
+++ b/openai_forward/cache/database.py
@@ -1,75 +1,149 @@
-import queue
import threading
from abc import ABC, abstractmethod
+from dataclasses import dataclass
-# import numpy as np
import orjson
from loguru import logger
from ..settings import CACHE_BACKEND
+class DBContextManager:
+ def __init__(self, use_db: str, path: str, **kwargs):
+ self.use_db = use_db
+ if use_db == "lmdb":
+ import lmdb
+
+ self._db = lmdb.open(
+ path,
+ max_dbs=kwargs.get('max_dbs', 1),
+ map_size=kwargs.get('map_size', 1024**3),
+ )
+
+ elif use_db == "leveldb":
+ import plyvel
+
+ self._db = plyvel.DB(path, create_if_missing=True)
+ else:
+ raise ValueError(f"Unsupported DB type {use_db}.")
+
+ def get_db(self):
+ return self._db
+
+ def static_view(self):
+ if self.use_db == "lmdb":
+ return self._db.begin()
+ elif self.use_db == "leveldb":
+ return self._db.snapshot()
+ else:
+ raise ValueError(f"Unsupported DB type {self.use_db}.")
+
+ def close_view(self, static_view):
+ if self.use_db == "lmdb":
+ return static_view.abort()
+ elif self.use_db == "leveldb":
+ return static_view.close()
+ else:
+ raise ValueError(f"Unsupported DB type {self.use_db}.")
+
+ def write(self):
+ if self.use_db == "lmdb":
+ return self._db.begin(write=True)
+ elif self.use_db == "leveldb":
+ return self._db.write_batch()
+ else:
+ raise ValueError(f"Unsupported DB type {self.use_db}.")
+
+ def close(self):
+ if self.use_db == "lmdb":
+ return self._db.close()
+ elif self.use_db == "leveldb":
+ return self._db.close()
+ else:
+ raise ValueError(f"Unsupported DB type {self.use_db}.")
+
+
class BaseDBDict(ABC):
MAX_BUFFER_SIZE = 1000
- COMMIT_TIME_INTERVAL = 60
-
- def __init__(self):
- self.buffered_queue = queue.Queue()
+ COMMIT_TIME_INTERVAL = 60 * 60
+
+ @dataclass(frozen=True)
+ class enc_prefix:
+ str: bytes = b's'
+ int: bytes = b'i'
+ float: bytes = b'f'
+ bool: bytes = b'b'
+ list: bytes = b'l'
+ tuple: bytes = b't'
+ dict: bytes = b'd'
+ array: bytes = b'a'
+
+ def __init__(self, db, path, **kwargs):
+ self.db = DBContextManager(use_db=db, path=path, **kwargs)
+ self.static_view = self.db.static_view()
+
+ self.buffered_count = 0
self.buffer_dict = {}
- self.lock = threading.RLock()
+ self.delete_buffer_set = set()
+ self.buffer_lock = threading.Lock()
+ self.db_lock = threading.Lock()
+
+ self._write_event = threading.Event()
+ self._latest_write_num = 0
+ self.thread_running = True
+ self._thread = threading.Thread(target=self.background_worker)
+ self._thread.daemon = True
# Start the background worker
+ self.start()
+
+ def start(self):
self.thread_running = True
- self._write_event = threading.Event()
- self.thread = threading.Thread(target=self.background_worker)
- self.thread.daemon = True
- self.thread.start()
+ self._thread.start()
@staticmethod
- def _encode(value):
+ def diff_buffer(a: dict, b: dict):
+ return {
+ key: value for key, value in a.items() if key not in b or b[key] != value
+ }
+
+ @classmethod
+ def _encode(cls, value):
if isinstance(value, int):
- return b'i:' + str(value).encode()
+ return cls.enc_prefix.int + str(value).encode()
elif isinstance(value, float):
- return b'f:' + str(value).encode()
+ return cls.enc_prefix.float + str(value).encode()
elif isinstance(value, bool):
- return b'b:' + str(value).encode()
+ return cls.enc_prefix.bool + str(value).encode()
elif isinstance(value, str):
- return b's:' + value.encode()
- # elif isinstance(value, np.ndarray):
- # return b'a:' + value.tobytes()
+ return cls.enc_prefix.str + value.encode()
elif isinstance(value, list):
- # return b'l:' + np.array(value).tobytes()
- return b'd:' + orjson.dumps(value)
+ return cls.enc_prefix.list + orjson.dumps(value)
elif isinstance(value, tuple):
- # return b't:' + np.array(value).tobytes()
- return b'd:' + orjson.dumps(value)
+ return cls.enc_prefix.tuple + orjson.dumps(value)
elif isinstance(value, dict):
- # why use orjson to serialize? Because it's faster than pickle, MessagePack, etc.
- return b'd:' + orjson.dumps(value)
+ return cls.enc_prefix.dict + orjson.dumps(value)
else:
raise ValueError(f"Unsupported type {type(value)}")
- @staticmethod
- def _decode(value):
- type_prefix, b_data = value[:2], value[2:]
+ @classmethod
+ def _decode(cls, value):
+
+ type_prefix, b_data = value[:1], value[1:]
- if type_prefix == b'i:':
+ if type_prefix == cls.enc_prefix.int:
return int(b_data.decode())
- elif type_prefix == b'f:':
+ elif type_prefix == cls.enc_prefix.float:
return float(b_data.decode())
- elif type_prefix == b'b:':
+ elif type_prefix == cls.enc_prefix.bool:
return b_data.decode() == 'True'
- elif type_prefix == b's:':
+ elif type_prefix == cls.enc_prefix.str:
return b_data.decode()
- # elif type_prefix == b'a:':
- # return np.frombuffer(b_data)
- elif type_prefix == b'l:':
- # return np.frombuffer(b_data).tolist()
+ elif type_prefix == cls.enc_prefix.list:
return orjson.loads(b_data)
- elif type_prefix == b't:':
- # return tuple(np.frombuffer(b_data).tolist())
+ elif type_prefix == cls.enc_prefix.tuple:
return tuple(orjson.loads(b_data))
- elif type_prefix == b'd:':
+ elif type_prefix == cls.enc_prefix.dict:
return orjson.loads(b_data)
else:
raise ValueError(f"Unsupported type prefix {type_prefix}")
@@ -77,29 +151,98 @@ def _decode(value):
def background_worker(self):
while self.thread_running:
self._write_event.wait(timeout=self.COMMIT_TIME_INTERVAL)
- self._write_buffer_to_db()
+ self._write_event.clear()
+ self._write_buffer_to_db(current_write_num=self._latest_write_num)
+
+ def write_db_immediately(self):
+ self._write_event.set()
def close_background_worker(self):
- self._write_buffer_to_db() # Ensure all buffered items are written to DB
+ self._latest_write_num += 1
+ self.write_db_immediately()
self.thread_running = False
- self._write_event.set()
- self.thread.join(timeout=30)
- if self.thread.is_alive():
+ self._thread.join(timeout=10)
+ if self._thread.is_alive():
logger.warning(
"Warning: Background thread did not finish in time. Some data might not be saved."
)
- @abstractmethod
def get(self, key: str):
- pass
+ with self.buffer_lock:
+ if key in self.delete_buffer_set:
+ return None
- @abstractmethod
- def set(self, key: str, value):
- pass
+ if key in self.buffer_dict:
+ return self.buffer_dict[key]
- @abstractmethod
- def _write_buffer_to_db(self):
- pass
+ value = self.static_view.get(key.encode())
+ if value is None:
+ return None
+ return self._decode(value)
+
+ def get_db_value(self, key: str):
+ return self.static_view.get(key.encode())
+
+ def get_batch(self, keys):
+ values = []
+ for key in keys:
+ if self.delete_buffer_set and key in self.delete_buffer_set:
+ values.append(None)
+ continue
+ if key in self.buffer_dict:
+ values.append(self.buffer_dict[key])
+ continue
+ value = self.static_view.get(key.encode())
+ if value is not None:
+ value = self._decode(value)
+ values.append(value)
+ return values
+
+ def set(self, key, value):
+ with self.buffer_lock:
+ self.buffer_dict[key] = value
+ self.delete_buffer_set.discard(key)
+
+ self.buffered_count += 1
+ # Trigger immediate write if buffer size exceeds MAX_BUFFER_SIZE
+ if self.buffered_count >= self.MAX_BUFFER_SIZE:
+ print("Trigger immediate write")
+ self._latest_write_num += 1
+ self.buffered_count = 0
+ self.write_db_immediately()
+
+ def _write_buffer_to_db(self, current_write_num: int):
+ with self.buffer_lock:
+ logger.debug(f"Trigger write")
+ logger.debug(f"{current_write_num=}")
+ if not self.buffer_dict or self.delete_buffer_set:
+ logger.debug(f"buffer为空{self._latest_write_num=} {current_write_num=}")
+ return
+ else:
+ buffer_dict_snapshot = self.buffer_dict.copy()
+ delete_buffer_set_snapshot = self.delete_buffer_set.copy()
+ try:
+ # enforce atomicity
+ with self.db.write() as wb:
+ for key, value in buffer_dict_snapshot.items():
+ wb.put(key.encode(), self._encode(value))
+ for key in delete_buffer_set_snapshot:
+ wb.delete(key.encode())
+
+ with self.buffer_lock:
+ logger.info("reset buffer")
+ self.delete_buffer_set = (
+ self.delete_buffer_set - delete_buffer_set_snapshot
+ )
+ self.buffer_dict = self.diff_buffer(
+ self.buffer_dict, buffer_dict_snapshot
+ )
+
+ self.db.close_view(self.static_view)
+ self.static_view = self.db.static_view()
+
+ except Exception as e:
+ logger.error(f"Error writing to {self.db.use_db}: {e}")
@abstractmethod
def keys(self):
@@ -113,9 +256,37 @@ def values(self):
def items(self):
pass
- @abstractmethod
+ def __getitem__(self, key: str):
+ value = self.get(key)
+ if value is None:
+ raise KeyError(f"Key {key} not found in the database.")
+ return value
+
+ def __setitem__(self, key: str, value):
+ self.set(key, value)
+
+ def __delitem__(self, key: str):
+ if key in self:
+ with self.buffer_lock:
+ self.delete_buffer_set.add(key)
+ self.buffered_count += 1
+ if key in self.buffer_dict:
+ del self.buffer_dict[key]
+ return
+ else:
+ raise KeyError(f"Key {key} not found in the database.")
+
+ def __contains__(self, key: str):
+ with self.buffer_lock:
+ if key in self.buffer_dict:
+ return True
+
+ return self.static_view.get(key.encode()) is not None
+
def close(self):
- pass
+ self.close_background_worker()
+ self.db.close()
+ logger.info(f"Closed ({self.db.use_db.upper()}) successfully")
class LMDBDict(BaseDBDict):
@@ -123,134 +294,71 @@ class LMDBDict(BaseDBDict):
A dictionary-like class that stores key-value pairs in an LMDB database.
Type:
key: str
- value: int, float, bool, str, np.ndarray, list, tuple, dict
+ value: int, float, bool, str, list, tuple, dict, and np.ndarray,
"""
def __init__(self, path, map_size=1024**3):
- super().__init__()
- import lmdb
-
- self.env = lmdb.open(path, max_dbs=1, map_size=map_size)
+ super().__init__("lmdb", path, max_dbs=1, map_size=map_size)
- def get(self, key: str):
- with self.lock:
- if key in self.buffer_dict:
- return self.buffer_dict[key]
-
- with self.env.begin() as txn:
- value = txn.get(key.encode())
-
- if value is None:
- return None
- return self._decode(value)
-
- def set(self, key, value):
- b_value = self._encode(value)
- with self.lock:
- self.buffer_dict[key] = value
- self.buffered_queue.put((key, b_value))
- # Trigger immediate write if buffer size exceeds MAX_BUFFER_SIZE
- if self.buffered_queue.qsize() >= self.MAX_BUFFER_SIZE:
- self._write_buffer_to_db()
-
- def _write_buffer_to_db(self):
- items = []
- with self.lock:
- while not self.buffered_queue.empty():
- key, value = self.buffered_queue.get()
- items.append((key, value))
-
- if items:
- try:
- # print(f"{items=}")
- with self.env.begin(write=True) as txn:
- for key, value in items:
- txn.put(key.encode(), value)
- # print("key", key, "value", value)
- with self.lock:
- if key in self.buffer_dict:
- del self.buffer_dict[key]
- except Exception as e:
- logger.error(f"Error writing to LMDB: {e}")
+ def get_db_value(self, key: str):
+ value = self.static_view.get(key.encode())
+ return value
def keys(self):
- with self.env.begin() as txn:
- cursor = txn.cursor()
- lmdb_keys = set(
- key.decode() for key in cursor.iternext(keys=True, values=False)
- )
-
- with self.lock:
+ with self.buffer_lock:
+ session = self.db.static_view()
+ cursor = session.cursor()
+ delete_buffer_set = self.delete_buffer_set.copy()
buffer_keys = set(self.buffer_dict.keys())
- return list(lmdb_keys.union(buffer_keys))
+ lmdb_keys = set(
+ key.decode() for key in cursor.iternext(keys=True, values=False)
+ )
+ self.db.close_view(session)
+
+ return list(lmdb_keys.union(buffer_keys) - delete_buffer_set)
def values(self):
- with self.env.begin() as txn:
- cursor = txn.cursor()
- lmdb_values = [
- self._decode(value)
- for _, value in cursor.iternext(keys=False, values=True)
- ]
-
- with self.lock:
+
+ with self.buffer_lock:
+ session = self.db.static_view()
+ cursor = session.cursor()
+ delete_buffer_set = self.delete_buffer_set.copy()
buffer_values = list(self.buffer_dict.values())
+ lmdb_values = [
+ self._decode(value)
+ for key, value in cursor.iternext(keys=True, values=True)
+ if key.decode() not in delete_buffer_set
+ ]
+
+ session.abort()
return lmdb_values + buffer_values
def items(self):
- with self.env.begin() as txn:
- cursor = txn.cursor()
- lmdb_items = [
- (key.decode(), self._decode(value))
- for key, value in cursor.iternext(keys=True, values=True)
- ]
+ with self.buffer_lock:
+ session = self.db.static_view()
+ cursor = session.cursor()
+ buffer_dict = self.buffer_dict.copy()
+ delete_buffer_set = self.delete_buffer_set.copy()
- with self.lock:
- buffer_items = list(self.buffer_dict.items())
+ db_dict = {
+ key.decode(): self._decode(value)
+ for key, value in cursor.iternext(keys=True, values=True)
+ if key not in delete_buffer_set
+ }
+ db_dict.update(buffer_dict)
- return lmdb_items + buffer_items
+ session.abort()
- def __getitem__(self, key: str):
- value = self.get(key)
- if value is None:
- raise KeyError(f"Key {key} not found in the database.")
- return value
-
- def __setitem__(self, key: str, value):
- self.set(key, value)
-
- def __contains__(self, key: str):
- with self.lock:
- if key in self.buffer_dict:
- return True
-
- with self.env.begin() as txn:
- return txn.get(key.encode()) is not None
-
- def __delitem__(self, key: str):
- if key in self:
- with self.lock:
- if key in self.buffer_dict:
- del self.buffer_dict[key]
- return
-
- with self.env.begin(write=True) as txn:
- txn.delete(key.encode())
- else:
- raise KeyError(f"Key {key} not found in the database.")
-
- def close(self):
- self.close_background_worker()
- self.env.close()
- logger.info("Closed LMDB successfully.")
+ return db_dict.items()
def set_mapsize(self, map_size):
"""Change the maximum size of the map file.
This function will fail if any transactions are active in the current process.
"""
try:
- self.env.set_mapsize(map_size)
+ self.db.get_db().set_mapsize(map_size)
except Exception as e:
logger.error(f"Error setting map size: {e}")
@@ -264,113 +372,52 @@ class LevelDBDict(BaseDBDict):
"""
def __init__(self, path):
- super().__init__()
- import plyvel
-
- self.db = plyvel.DB(path, create_if_missing=True)
-
- def get(self, key: str):
- with self.lock:
- if key in self.buffer_dict:
- return self.buffer_dict[key]
-
- value = self.db.get(key.encode())
- if value is None:
- return None
- return self._decode(value)
-
- def set(self, key, value):
- b_value = self._encode(value)
- with self.lock:
- self.buffer_dict[key] = value
- self.buffered_queue.put((key, b_value))
- if self.buffered_queue.qsize() >= self.MAX_BUFFER_SIZE:
- self._write_buffer_to_db()
-
- def _write_buffer_to_db(self):
- items = []
- with self.lock:
- while not self.buffered_queue.empty():
- key, value = self.buffered_queue.get()
- items.append((key, value))
-
- if items:
- try:
- with self.db.write_batch() as wb:
- for key, value in items:
- wb.put(key.encode(), value)
- with self.lock:
- if key in self.buffer_dict:
- del self.buffer_dict[key]
- except Exception as e:
- logger.error(f"Error writing to LevelDB: {e}")
+ super().__init__("leveldb", path=path)
def keys(self):
- with self.db.snapshot() as snapshot:
- db_keys = set(key.decode() for key, _ in snapshot.iterator())
-
- with self.lock:
+ with self.buffer_lock:
+ session = self.db.static_view()
+ delete_buffer_set = self.delete_buffer_set.copy()
buffer_keys = set(self.buffer_dict.keys())
+ snapshot = self.db.static_view()
- return list(db_keys.union(buffer_keys))
+ db_keys = set(key.decode() for key, _ in snapshot.iterator())
+ self.db.close_view(session)
- def values(self):
- with self.db.snapshot() as snapshot:
- db_values = [self._decode(value) for _, value in snapshot.iterator()]
+ return list(db_keys.union(buffer_keys) - delete_buffer_set)
- with self.lock:
+ def values(self):
+ with self.buffer_lock:
+ snapshot = self.db.static_view()
+ delete_buffer_set = self.delete_buffer_set.copy()
buffer_values = list(self.buffer_dict.values())
- return db_values + buffer_values
-
- def items(self):
- with self.db.snapshot() as snapshot:
- db_items = [
- (key.decode(), self._decode(value))
- for key, value in snapshot.iterator()
- ]
-
- with self.lock:
- buffer_items = list(self.buffer_dict.items())
-
- return db_items + buffer_items
+ db_values = [
+ self._decode(value)
+ for key, value in snapshot.iterator()
+ if key not in delete_buffer_set
+ ]
- def __getitem__(self, key: str):
- value = self.get(key)
- if value is None:
- raise KeyError(f"Key {key} not found in the database.")
- return value
+ snapshot.close()
- def __setitem__(self, key: str, value):
- self.set(key, value)
+ return db_values + buffer_values
- # def __contains__(self, key: str):
- # with self.lock:
- # if key in self.buffer_dict:
- # return True
- # return self.db.get(key.encode()) is not None
+ def items(self):
+ with self.buffer_lock:
+ snapshot = self.db.static_view()
+ delete_buffer_set = self.delete_buffer_set.copy()
+ buffer_dict = self.buffer_dict.copy()
- def __contains__(self, key: str):
- with self.lock:
- if key in self.buffer_dict:
- return True
- with self.db.snapshot() as snapshot:
- return snapshot.get(key.encode()) is not None
+ _db_dict = {}
+ for key, value in snapshot.iterator():
+ dk = self._decode(key)
+ if dk not in delete_buffer_set:
+ _db_dict[dk] = self._decode(value)
- def __delitem__(self, key: str):
- if key in self:
- with self.lock:
- if key in self.buffer_dict:
- del self.buffer_dict[key]
- return
- self.db.delete(key.encode())
- else:
- raise KeyError(f"Key {key} not found in the database.")
+ _db_dict.update(buffer_dict)
- def close(self):
- self.close_background_worker()
- self.db.close()
- logger.info("Closed LevelDB successfully.")
+ snapshot.close()
+ return _db_dict.items()
if CACHE_BACKEND.upper() == "LMDB":
diff --git a/openai_forward/content/config.py b/openai_forward/content/config.py
index 3221a0f..ca03491 100644
--- a/openai_forward/content/config.py
+++ b/openai_forward/content/config.py
@@ -62,10 +62,6 @@ def get_utc_offset(timezone_str):
{"sink": sys.stdout, "level": "INFO" if print_chat else "DEBUG"},
]
- # def filter_func(_prefix, _postfix, record):
- # chat_key = f"{_prefix}{_postfix}"
- # return chat_key in record["extra"]
-
for prefix in openai_route_prefix or []:
_prefix = route_prefix_to_str(prefix)
@@ -76,7 +72,6 @@ def get_utc_offset(timezone_str):
"sink": f"./Log/{_prefix}/chat/chat.log",
"enqueue": multi_process,
"rotation": "50 MB",
- # "filter": functools.partial(filter_func, _prefix, "_chat"),
"filter": lambda record: f"{_prefix}_chat" in record["extra"],
"format": "{message}",
},
@@ -84,7 +79,6 @@ def get_utc_offset(timezone_str):
"sink": f"./Log/{_prefix}/completion/completion.log",
"enqueue": multi_process,
"rotation": "50 MB",
- # "filter": functools.partial(filter_func, _prefix, "_completion"),
"filter": lambda record: f"{_prefix}_completion" in record["extra"],
"format": "{message}",
},
@@ -92,7 +86,6 @@ def get_utc_offset(timezone_str):
"sink": f"./Log/{_prefix}/whisper/whisper.log",
"enqueue": multi_process,
"rotation": "30 MB",
- # "filter": functools.partial(filter_func, _prefix, "_whisper"),
"filter": lambda record: f"{_prefix}_whisper" in record["extra"],
"format": "{message}",
},
diff --git a/openai_forward/content/openai.py b/openai_forward/content/openai.py
index d2693a5..ec81554 100644
--- a/openai_forward/content/openai.py
+++ b/openai_forward/content/openai.py
@@ -26,6 +26,7 @@ def __init__(self, route_prefix: str):
async def parse_payload(request: Request):
uid = get_unique_id()
payload = await request.json()
+ print(f"{payload=}")
content = {
"prompt": payload['prompt'],
diff --git a/openai_forward/forward/base.py b/openai_forward/forward/base.py
index 29b925e..7421ad3 100644
--- a/openai_forward/forward/base.py
+++ b/openai_forward/forward/base.py
@@ -22,7 +22,7 @@
from ..settings import *
-class ForwardBase:
+class GenericForward:
"""
Base class for handling request forwarding to another service.
Provides methods for request validation, logging, and proxying.
@@ -164,7 +164,7 @@ async def reverse_proxy(self, request: Request):
)
-class OpenaiBase(ForwardBase):
+class OpenaiForward(GenericForward):
"""
Derived class for handling request forwarding specifically for the OpenAI (Style) API.
"""
@@ -364,9 +364,9 @@ async def aiter_bytes(
def handle_authorization(self, client_config):
auth, auth_prefix = client_config["auth"], "Bearer "
if self._no_auth_mode or auth and auth[len(auth_prefix) :] in FWD_KEY:
- client_config["headers"]["Authorization"] = auth_prefix + next(
- self._cycle_api_key
- )
+ auth = auth_prefix + next(self._cycle_api_key)
+ client_config["headers"]["Authorization"] = auth
+ return auth
@staticmethod
def _get_cached_response(payload_info, valid_payload, request):
@@ -392,7 +392,7 @@ def construct_cache_key():
payload_info['messages'],
payload_info['model'],
payload_info["max_tokens"],
- payload_info['temperature'],
+ # payload_info['temperature'],
]
functions = payload_info.get("functions")
if functions:
diff --git a/openai_forward/forward/extra.py b/openai_forward/forward/extra.py
index bd74b26..71afa9b 100644
--- a/openai_forward/forward/extra.py
+++ b/openai_forward/forward/extra.py
@@ -1,4 +1,4 @@
-from .base import ForwardBase as GenericForward
+from .base import GenericForward
def create_generic_proxies():
diff --git a/openai_forward/forward/openai.py b/openai_forward/forward/openai.py
index 0b5d92c..e988b6c 100644
--- a/openai_forward/forward/openai.py
+++ b/openai_forward/forward/openai.py
@@ -1,4 +1,4 @@
-from .base import OpenaiBase as OpenaiForward
+from .base import OpenaiForward
def create_openai_proxies():
diff --git a/openai_forward/helper.py b/openai_forward/helper.py
index 6f5ba30..9f165e6 100644
--- a/openai_forward/helper.py
+++ b/openai_forward/helper.py
@@ -163,9 +163,18 @@ def clean_str(original_str: str):
clean_content = {}
for key, value in content.items():
if key == "messages":
- clean_content[key] = [
- {i['role']: clean_str(i['content'])} for i in value
- ]
+ try:
+ clean_content[key] = [
+ {i['role']: clean_str(i['content'])} for i in value
+ ]
+ except Exception as e:
+ if not isinstance(value, list):
+ print(
+ f"Warning(`message` should be a list): {content=}"
+ )
+ else:
+ print(f"Warning({e=}): {content=}")
+ continue
else:
clean_content[key] = value
messages.append(clean_content)
diff --git a/tests/test_db.py b/tests/test_db.py
index bc63a38..243e9e4 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -5,7 +5,7 @@
import pytest
try:
- import imdb
+ import lmdb
from openai_forward.cache.database import LMDBDict
except ImportError:
@@ -41,7 +41,7 @@ def temp_db(request):
def test_set_get_items(temp_db):
- if temp_db is None or isinstance(temp_db, Rdict):
+ if isinstance(temp_db, (Rdict, type(None))):
pytest.skip("Skipping")
temp_db.set("test_key", "test_value")
assert temp_db.get("test_key") == "test_value"
@@ -67,12 +67,12 @@ def test_data_types(temp_db):
def test_buffered_writing(temp_db):
- if temp_db is None or isinstance(temp_db, Rdict):
+ if isinstance(temp_db, (Rdict, type(None))):
pytest.skip("Skipping")
for i in range(temp_db.MAX_BUFFER_SIZE + 10):
temp_db[f"key_{i}"] = f"value_{i}"
- assert len(temp_db.buffer_dict) == 10
+ assert len(temp_db.buffer_dict) == temp_db.MAX_BUFFER_SIZE + 10
def test_key_checks_and_deletion(temp_db):
@@ -87,18 +87,14 @@ def test_list_keys_values_items(temp_db):
for key, value in data.items():
temp_db[key] = value
-
assert set(temp_db.keys()) == set(data.keys())
assert set(temp_db.values()) == set(data.values())
assert set(temp_db.items()) == set(data.items())
def test_error_scenarios(temp_db):
- if temp_db is None or isinstance(temp_db, Rdict):
+ if isinstance(temp_db, (Rdict, type(None))):
pytest.skip("Skipping")
- # Set unsupported type
- with pytest.raises(ValueError):
- temp_db["unsupported_key"] = {1, 2, 3}
# Get non-existent key
with pytest.raises(KeyError):