├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README.zh-cn.md ├── go.mod ├── go.sum ├── main.go └── pkg ├── azure ├── proxy.go └── types.go └── openai └── proxy.go /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-push: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Login to DockerHub 15 | uses: docker/login-action@v1 16 | with: 17 | username: ${{ secrets.DOCKERHUB_USERNAME }} 18 | password: ${{ secrets.DOCKERHUB_TOKEN }} 19 | - name: Build and push Docker image 20 | uses: docker/build-push-action@v2 21 | with: 22 | context: . 23 | push: true 24 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/azure-openai-proxy:latest 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build step 2 | FROM golang:1.18 AS builder 3 | ENV GOPROXY=https://goproxy.cn,direct 4 | RUN mkdir -p /build 5 | WORKDIR /build 6 | COPY . . 7 | RUN go build 8 | 9 | # Final step 10 | FROM debian:buster-slim 11 | RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 12 | ca-certificates && \ 13 | rm -rf /var/lib/apt/lists/* \ 14 | 15 | EXPOSE 8080 16 | WORKDIR /app 17 | COPY --from=builder /build/azure-openai-proxy /app/azure-openai-proxy 18 | ENTRYPOINT ["/app/azure-openai-proxy"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cross Fire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure OpenAI Proxy 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/diemus/azure-openai-proxy)](https://goreportcard.com/report/github.com/diemus/azure-openai-proxy) 4 | [![License](https://badgen.net/badge/license/MIT/cyan)](https://github.com/diemus/azure-openai-proxy/blob/main/LICENSE) 5 | [![Release](https://badgen.net/github/release/diemus/azure-openai-proxy/latest)](https://github.com/diemus/azure-openai-proxy) 6 | [![Azure](https://badgen.net/badge/icon/Azure?icon=azure&label)](https://github.com/diemus/azure-openai-proxy) 7 | [![Azure](https://badgen.net/badge/icon/OpenAI?icon=azure&label)](https://github.com/diemus/azure-openai-proxy) 8 | [![Azure](https://badgen.net/badge/icon/docker?icon=docker&label)](https://github.com/diemus/azure-openai-proxy) 9 | 10 | ## Introduction 11 | 12 | English | 13 | 中文 14 | 15 | Azure OpenAI Proxy is a proxy for Azure OpenAI API that can convert an OpenAI request to an Azure OpenAI request. It is designed to use as a backend for various open source ChatGPT web project. It also supports being used as a simple OpenAI API proxy to solve the problem of OpenAI API being restricted in some regions. 16 | 17 | Highlights: 18 | 19 | - 🌐 Supports proxying all Azure OpenAI APIs 20 | - 🧠 Supports proxying all Azure OpenAI models and custom fine-tuned models 21 | - 🗺️ Supports custom mapping between Azure deployment names and OpenAI models 22 | - 🔄 Supports both reverse proxy and forward proxy usage 23 | - 👍 Support mocking of OpenAI APIs that are not supported by Azure. 24 | 25 | 26 | ## Supported APIs 27 | 28 | The latest version of the Azure OpenAI service currently supports the following 3 APIs: 29 | 30 | | Path | Status | 31 | | --------------------- | ------ | 32 | | /v1/chat/completions | ✅ | 33 | | /v1/completions | ✅ | 34 | | /v1/embeddings | ✅ | 35 | 36 | > Other APIs not supported by Azure will be returned in a mock format (such as OPTIONS requests initiated by browsers). If you find your project need additional OpenAI-supported APIs, feel free to submit a PR. 37 | 38 | ## Recently Updated 39 | 40 | + 2023-04-06 supported `/v1/models` interface, fixed the issue of some web projects depending on `models` interface error. 41 | + 2023-04-04 supported `options` interface, fixed the cross-domain check error issue of some web projects. 42 | 43 | ## Usage 44 | 45 | ### 1. Used as reverse proxy (i.e. an OpenAI API gateway) 46 | 47 | Environment Variables 48 | 49 | | Parameters | Description | Default Value | 50 | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | 51 | | AZURE_OPENAI_PROXY_ADDRESS | Service listening address | 0.0.0.0:8080 | 52 | | AZURE_OPENAI_PROXY_MODE | Proxy mode, can be either "azure" or "openai". | azure | 53 | | AZURE_OPENAI_ENDPOINT | Azure OpenAI Endpoint, usually looks like https://{custom}.openai.azure.com. Required. | | 54 | | AZURE_OPENAI_APIVERSION | Azure OpenAI API version. Default is 2023-03-15-preview. | 2023-03-15-preview | 55 | | AZURE_OPENAI_MODEL_MAPPER | A comma-separated list of model=deployment pairs. Maps model names to deployment names. For example, `gpt-3.5-turbo=gpt-35-turbo`, `gpt-3.5-turbo-0301=gpt-35-turbo-0301`. If there is no match, the proxy will pass model as deployment name directly (in fact, most Azure model names are same with OpenAI). | `gpt-3.5-turbo=gpt-35-turbo`
`gpt-3.5-turbo-0301=gpt-35-turbo-0301` | 56 | | AZURE_OPENAI_TOKEN | Azure OpenAI API Token. If this environment variable is set, the token in the request header will be ignored. | "" | 57 | 58 | Use in command line 59 | 60 | ```shell 61 | curl https://{your-custom-domain}/v1/chat/completions \ 62 | -H "Content-Type: application/json" \ 63 | -H "Authorization: Bearer {your azure api key}" \ 64 | -d '{ 65 | "model": "gpt-3.5-turbo", 66 | "messages": [{"role": "user", "content": "Hello!"}] 67 | }' 68 | ``` 69 | 70 | ### 2. Used as forward proxy (i.e. an HTTP proxy) 71 | 72 | When accessing Azure OpenAI API through HTTP, it can be used directly as a proxy, but this tool does not have built-in HTTPS support, so you need an HTTPS proxy such as Nginx to support accessing HTTPS version of OpenAI API. 73 | 74 | Assuming that the proxy domain you configured is `https://{your-domain}.com`, you can execute the following commands in the terminal to use the https proxy: 75 | 76 | ```shell 77 | export https_proxy=https://{your-domain}.com 78 | 79 | curl https://api.openai.com/v1/chat/completions \ 80 | -H "Content-Type: application/json" \ 81 | -H "Authorization: Bearer {your azure api key}" \ 82 | -d '{ 83 | "model": "gpt-3.5-turbo", 84 | "messages": [{"role": "user", "content": "Hello!"}] 85 | }' 86 | ``` 87 | 88 | Or configure it as an HTTP proxy in other open source Web ChatGPT projects: 89 | 90 | ``` 91 | export HTTPS_PROXY=https://{your-domain}.com 92 | ``` 93 | 94 | ## Deploy 95 | 96 | Deploying through Docker 97 | 98 | ```shell 99 | docker pull ishadows/azure-openai-proxy:latest 100 | docker run -d -p 8080:8080 --name=azure-openai-proxy \ 101 | --env AZURE_OPENAI_ENDPOINT={your azure endpoint} \ 102 | --env AZURE_OPENAI_MODEL_MAPPER={your custom model mapper ,like: gpt-3.5-turbo=gpt-35-turbo,gpt-3.5-turbo-0301=gpt-35-turbo-0301} \ 103 | ishadows/azure-openai-proxy:latest 104 | ``` 105 | 106 | Calling 107 | 108 | ```shell 109 | curl https://localhost:8080/v1/chat/completions \ 110 | -H "Content-Type: application/json" \ 111 | -H "Authorization: Bearer {your azure api key}" \ 112 | -d '{ 113 | "model": "gpt-3.5-turbo", 114 | "messages": [{"role": "user", "content": "Hello!"}] 115 | }' 116 | ``` 117 | 118 | ## Model Mapping Mechanism 119 | 120 | There are a series of rules for model mapping pre-defined in `AZURE_OPENAI_MODEL_MAPPER`, and the default configuration basically satisfies the mapping of all Azure models. The rules include: 121 | 122 | - `gpt-3.5-turbo` -> `gpt-35-turbo` 123 | - `gpt-3.5-turbo-0301` -> `gpt-35-turbo-0301` 124 | - A mapping mechanism that pass model name directly as fallback. 125 | 126 | For custom fine-tuned models, the model name can be passed directly. For models with deployment names different from the model names, custom mapping relationships can be defined, such as: 127 | 128 | | Model Name | Deployment Name | 129 | | :----------------- | :--------------------------- | 130 | | gpt-3.5-turbo | gpt-35-turbo-upgrade | 131 | | gpt-3.5-turbo-0301 | gpt-35-turbo-0301-fine-tuned | 132 | 133 | ## License 134 | 135 | MIT 136 | 137 | ## Star History 138 | 139 | [![Star History Chart](https://api.star-history.com/svg?repos=diemus/azure-openai-proxy&type=Date)](https://star-history.com/#diemus/azure-openai-proxy&Date) 140 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # Azure OpenAI Proxy 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/diemus/azure-openai-proxy)](https://goreportcard.com/report/github.com/diemus/azure-openai-proxy) 4 | [![License](https://badgen.net/badge/license/MIT/cyan)](https://github.com/diemus/azure-openai-proxy/blob/main/LICENSE) 5 | [![Release](https://badgen.net/github/release/diemus/azure-openai-proxy/latest)](https://github.com/diemus/azure-openai-proxy) 6 | [![Azure](https://badgen.net/badge/icon/Azure?icon=azure&label)](https://github.com/diemus/azure-openai-proxy) 7 | [![Azure](https://badgen.net/badge/icon/OpenAI?icon=azure&label)](https://github.com/diemus/azure-openai-proxy) 8 | [![Azure](https://badgen.net/badge/icon/docker?icon=docker&label)](https://github.com/diemus/azure-openai-proxy) 9 | 10 | ## 介绍 11 | 12 | English | 13 | 中文 14 | 15 | 一个 Azure OpenAI API 的代理工具,可以将一个 OpenAI 请求转化为 Azure 16 | OpenAI 请求,方便作为各类开源 ChatGPT 的后端使用。同时也支持作为单纯的 OpenAI 接口代理使用,用来解决 OpenAI 接口在部分地区的被限制使用的问题。 17 | 18 | 亮点: 19 | 20 | - 🌐 支持代理所有 Azure OpenAI 接口 21 | - 🧠 支持代理所有 Azure OpenAI 模型以及自定义微调模型 22 | - 🗺️ 支持自定义 Azure 部署名与 OpenAI 模型的映射关系 23 | - 🔄 支持反向代理和正向代理两种方式使用 24 | - 👍 支持对Azure不支持的OpenAI接口进行Mock 25 | 26 | ## 支持的接口 27 | 28 | 目前最新版本的Azure OpenAI服务支持以下3个接口: 29 | 30 | | Path | Status | 31 | | --------------------- |------| 32 | | /v1/chat/completions | ✅ | 33 | | /v1/completions | ✅ | 34 | | /v1/embeddings | ✅ | 35 | 36 | > 其他Azure不支持的接口会通过mock的形式返回(比如浏览器发起的OPTIONS类型的请求,或者列出所有模型等)。如果你发现需要某些额外的OpenAI支持的接口,欢迎提交PR 37 | 38 | ## 最近更新 39 | 40 | + 2023-04-06 支持了`/v1/models`接口,修复了部分web项目依赖models接口报错的问题 41 | + 2023-04-04 支持了`options`接口,修复了部分web项目跨域检查时报错的问题 42 | 43 | ## 使用方式 44 | 45 | ### 1. 作为反向代理使用(即一个 OpenAI API 网关) 46 | 47 | 环境变量 48 | 49 | | 参数名 | 描述 | 默认值 | 50 | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------- | 51 | | AZURE_OPENAI_PROXY_ADDRESS | 服务监听地址 | 0.0.0.0:8080 | 52 | | AZURE_OPENAI_PROXY_MODE | 代理模式,可以为 azure/openai 2 种模式 | azure | 53 | | AZURE_OPENAI_ENDPOINT | Azure OpenAI Endpoint,一般类似 https://{custom}.openai.azure.com 的格式。必需。 | | 54 | | AZURE_OPENAI_APIVERSION | Azure OpenAI API 的 API 版本。默认为 2023-03-15-preview。 | 2023-03-15-preview | 55 | | AZURE_OPENAI_MODEL_MAPPER | 一个逗号分隔的 model=deployment 对列表。模型名称映射到部署名称。例如,`gpt-3.5-turbo=gpt-35-turbo`,`gpt-3.5-turbo-0301=gpt-35-turbo-0301`。未匹配到的情况下,代理会直接透传 model 作为 deployment 使用(其实 Azure 大部分模型名字和 OpenAI 的保持一致)。 | `gpt-3.5-turbo=gpt-35-turbo`
`gpt-3.5-turbo-0301=gpt-35-turbo-0301` | 56 | | AZURE_OPENAI_TOKEN | Azure OpenAI API Token。 如果设置该环境变量则忽略请求头中的 Token | "" | 57 | 58 | 在命令行调用 59 | 60 | ```shell 61 | curl https://{your-custom-domain}/v1/chat/completions \ 62 | -H "Content-Type: application/json" \ 63 | -H "Authorization: Bearer {your azure api key}" \ 64 | -d '{ 65 | "model": "gpt-3.5-turbo", 66 | "messages": [{"role": "user", "content": "Hello!"}] 67 | }' 68 | 69 | ``` 70 | 71 | ### 2. 作为正向代理使用(即一个 HTTP Proxy) 72 | 73 | 通过 HTTP 访问 Azure OpenAI 接口时,可以直接作为代理使用,但是这个工具没有内置原生的 HTTPS 支持,需要在工具前架设一个类似 Nginx 的 HTTPS 代理,来支持访问 HTTPS 版本的 OpenAI 接口。 74 | 75 | 假设你配置好后的代理域名为`https://{your-domain}.com`,你可以在终端中执行以下命令来配置 http 代理: 76 | 77 | ```shell 78 | export https_proxy=https://{your-domain}.com 79 | 80 | curl https://api.openai.com/v1/chat/completions \ 81 | -H "Content-Type: application/json" \ 82 | -H "Authorization: Bearer {your azure api key}" \ 83 | -d '{ 84 | "model": "gpt-3.5-turbo", 85 | "messages": [{"role": "user", "content": "Hello!"}] 86 | }' 87 | 88 | ``` 89 | 90 | 或者在其他开源 Web ChatGPT 项目中配置为 HTTP 代理 91 | 92 | ``` 93 | export HTTPS_PROXY=https://{your-domain}.com 94 | ``` 95 | 96 | ## 部署方式 97 | 98 | 通过 docker 部署 99 | 100 | ```shell 101 | docker pull ishadows/azure-openai-proxy:latest 102 | docker run -d -p 8080:8080 --name=azure-openai-proxy \ 103 | --env AZURE_OPENAI_ENDPOINT={your azure endpoint} \ 104 | --env AZURE_OPENAI_MODEL_MAPPER={your custom model mapper ,like: gpt-3.5-turbo=gpt-35-turbo,gpt-3.5-turbo-0301=gpt-35-turbo-0301} \ 105 | ishadows/azure-openai-proxy:latest 106 | ``` 107 | 108 | 调用 109 | 110 | ```shell 111 | curl https://localhost:8080/v1/chat/completions \ 112 | -H "Content-Type: application/json" \ 113 | -H "Authorization: Bearer {your azure api key}" \ 114 | -d '{ 115 | "model": "gpt-3.5-turbo", 116 | "messages": [{"role": "user", "content": "Hello!"}] 117 | }' 118 | ``` 119 | 120 | ## 模型映射机制 121 | 122 | `AZURE_OPENAI_MODEL_MAPPER`中预定义了一系列模型映射的规则,默认配置基本上满足了所有 Azure 模型的映射,规则包括: 123 | 124 | - `gpt-3.5-turbo` -> `gpt-35-turbo` 125 | - `gpt-3.5-turbo-0301` -> `gpt-35-turbo-0301` 126 | - 以及一个透传模型名的机制作为 fallback 手段 127 | 128 | 对于自定义的微调模型,可以直接透传模型名。对于部署名字和模型名不一样的,可以自定义映射关系,比如: 129 | 130 | | 模型名称 | 部署名称 | 131 | | :----------------- | :--------------------------- | 132 | | gpt-3.5-turbo | gpt-35-turbo-upgrade | 133 | | gpt-3.5-turbo-0301 | gpt-35-turbo-0301-fine-tuned | 134 | 135 | ## 许可证 136 | 137 | MIT 138 | 139 | ## Star History 140 | 141 | [![Star History Chart](https://api.star-history.com/svg?repos=diemus/azure-openai-proxy&type=Date)](https://star-history.com/#diemus/azure-openai-proxy&Date) 142 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/diemus/azure-openai-proxy 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.0 7 | github.com/tidwall/gjson v1.14.4 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.8.0 // indirect 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 13 | github.com/gin-contrib/sse v0.1.0 // indirect 14 | github.com/go-playground/locales v0.14.1 // indirect 15 | github.com/go-playground/universal-translator v0.18.1 // indirect 16 | github.com/go-playground/validator/v10 v10.11.2 // indirect 17 | github.com/goccy/go-json v0.10.0 // indirect 18 | github.com/json-iterator/go v1.1.12 // indirect 19 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 20 | github.com/leodido/go-urn v1.2.1 // indirect 21 | github.com/mattn/go-isatty v0.0.17 // indirect 22 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 23 | github.com/modern-go/reflect2 v1.0.2 // indirect 24 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 25 | github.com/tidwall/match v1.1.1 // indirect 26 | github.com/tidwall/pretty v1.2.0 // indirect 27 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 28 | github.com/ugorji/go/codec v1.2.9 // indirect 29 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 30 | golang.org/x/crypto v0.5.0 // indirect 31 | golang.org/x/net v0.7.0 // indirect 32 | golang.org/x/sys v0.5.0 // indirect 33 | golang.org/x/text v0.7.0 // indirect 34 | google.golang.org/protobuf v1.28.1 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= 3 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 11 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 12 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 13 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 14 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 15 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 16 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 17 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 18 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 19 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 20 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 21 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 22 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 25 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 27 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 28 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 29 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 30 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 31 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 32 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 33 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 34 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 35 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 36 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 37 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 38 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 39 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 40 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 41 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 42 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 48 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 49 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 50 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 51 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 53 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 54 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 55 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 56 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 57 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 58 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 59 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 60 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 61 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 62 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 63 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 64 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= 65 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 66 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= 67 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 68 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 69 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 70 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 71 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 72 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 74 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 76 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 77 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 80 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 81 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 84 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 85 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 86 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 88 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/diemus/azure-openai-proxy/pkg/azure" 5 | "github.com/diemus/azure-openai-proxy/pkg/openai" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | var ( 13 | Address = "0.0.0.0:8080" 14 | ProxyMode = "azure" 15 | ) 16 | 17 | func init() { 18 | gin.SetMode(gin.ReleaseMode) 19 | if v := os.Getenv("AZURE_OPENAI_PROXY_ADDRESS"); v != "" { 20 | Address = v 21 | } 22 | if v := os.Getenv("AZURE_OPENAI_PROXY_MODE"); v != "" { 23 | ProxyMode = v 24 | } 25 | log.Printf("loading azure openai proxy address: %s", Address) 26 | log.Printf("loading azure openai proxy mode: %s", ProxyMode) 27 | } 28 | 29 | func main() { 30 | router := gin.Default() 31 | if ProxyMode == "azure" { 32 | router.GET("/v1/models", handleGetModels) 33 | router.OPTIONS("/v1/*path", handleOptions) 34 | 35 | router.POST("/v1/chat/completions", handleAzureProxy) 36 | router.POST("/v1/completions", handleAzureProxy) 37 | router.POST("/v1/embeddings", handleAzureProxy) 38 | } else { 39 | router.Any("*path", handleOpenAIProxy) 40 | } 41 | 42 | router.Run(Address) 43 | 44 | } 45 | 46 | func handleGetModels(c *gin.Context) { 47 | // BUGFIX: fix options request, see https://github.com/diemus/azure-openai-proxy/issues/3 48 | models := []string{"gpt-4", "gpt-4-0314", "gpt-4-32k", "gpt-4-32k-0314", "gpt-3.5-turbo", "gpt-3.5-turbo-0301", "text-davinci-003", "text-embedding-ada-002"} 49 | result := azure.ListModelResponse{ 50 | Object: "list", 51 | } 52 | for _, model := range models { 53 | result.Data = append(result.Data, azure.Model{ 54 | ID: model, 55 | Object: "model", 56 | Created: 1677649963, 57 | OwnedBy: "openai", 58 | Permission: []azure.ModelPermission{ 59 | { 60 | ID: "", 61 | Object: "model", 62 | Created: 1679602087, 63 | AllowCreateEngine: true, 64 | AllowSampling: true, 65 | AllowLogprobs: true, 66 | AllowSearchIndices: true, 67 | AllowView: true, 68 | AllowFineTuning: true, 69 | Organization: "*", 70 | Group: nil, 71 | IsBlocking: false, 72 | }, 73 | }, 74 | Root: model, 75 | Parent: nil, 76 | }) 77 | } 78 | c.JSON(200, result) 79 | } 80 | 81 | func handleOptions(c *gin.Context) { 82 | // BUGFIX: fix options request, see https://github.com/diemus/azure-openai-proxy/issues/1 83 | c.Header("Access-Control-Allow-Origin", "*") 84 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 85 | c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") 86 | c.Status(200) 87 | return 88 | } 89 | 90 | func handleAzureProxy(c *gin.Context) { 91 | // BUGFIX: fix options request, see https://github.com/diemus/azure-openai-proxy/issues/1 92 | if c.Request.Method == http.MethodOptions { 93 | c.Header("Access-Control-Allow-Origin", "*") 94 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 95 | c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") 96 | c.Status(200) 97 | return 98 | } 99 | 100 | server := azure.NewOpenAIReverseProxy() 101 | server.ServeHTTP(c.Writer, c.Request) 102 | //BUGFIX: try to fix the difference between azure and openai 103 | //Azure's response is missing a \n at the end of the stream 104 | //see https://github.com/Chanzhaoyu/chatgpt-web/issues/831 105 | if c.Writer.Header().Get("Content-Type") == "text/event-stream" { 106 | if _, err := c.Writer.Write([]byte("\n")); err != nil { 107 | log.Printf("rewrite azure response error: %v", err) 108 | } 109 | } 110 | } 111 | 112 | func handleOpenAIProxy(c *gin.Context) { 113 | server := openai.NewOpenAIReverseProxy() 114 | server.ServeHTTP(c.Writer, c.Request) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/azure/proxy.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/http/httputil" 10 | "net/url" 11 | "os" 12 | "path" 13 | "regexp" 14 | "strings" 15 | 16 | "github.com/tidwall/gjson" 17 | ) 18 | 19 | var ( 20 | AzureOpenAIToken = "" 21 | AzureOpenAIAPIVersion = "2023-03-15-preview" 22 | AzureOpenAIEndpoint = "" 23 | AzureOpenAIModelMapper = map[string]string{ 24 | "gpt-3.5-turbo": "gpt-35-turbo", 25 | "gpt-3.5-turbo-0301": "gpt-35-turbo-0301", 26 | } 27 | fallbackModelMapper = regexp.MustCompile(`[.:]`) 28 | ) 29 | 30 | func init() { 31 | if v := os.Getenv("AZURE_OPENAI_APIVERSION"); v != "" { 32 | AzureOpenAIAPIVersion = v 33 | } 34 | if v := os.Getenv("AZURE_OPENAI_ENDPOINT"); v != "" { 35 | AzureOpenAIEndpoint = v 36 | } 37 | if v := os.Getenv("AZURE_OPENAI_MODEL_MAPPER"); v != "" { 38 | for _, pair := range strings.Split(v, ",") { 39 | info := strings.Split(pair, "=") 40 | if len(info) != 2 { 41 | log.Printf("error parsing AZURE_OPENAI_MODEL_MAPPER, invalid value %s", pair) 42 | os.Exit(1) 43 | } 44 | AzureOpenAIModelMapper[info[0]] = info[1] 45 | } 46 | } 47 | if v := os.Getenv("AZURE_OPENAI_TOKEN"); v != "" { 48 | AzureOpenAIToken = v 49 | log.Printf("loading azure api token from env") 50 | } 51 | 52 | log.Printf("loading azure api endpoint: %s", AzureOpenAIEndpoint) 53 | log.Printf("loading azure api version: %s", AzureOpenAIAPIVersion) 54 | for k, v := range AzureOpenAIModelMapper { 55 | log.Printf("loading azure model mapper: %s -> %s", k, v) 56 | } 57 | } 58 | 59 | func NewOpenAIReverseProxy() *httputil.ReverseProxy { 60 | remote, err := url.Parse(AzureOpenAIEndpoint) 61 | if err != nil { 62 | log.Printf("error parse endpoint: %s\n", AzureOpenAIEndpoint) 63 | os.Exit(1) 64 | } 65 | director := func(req *http.Request) { 66 | // Get model and map it to deployment 67 | if req.Body == nil { 68 | log.Println("unsupported request, body is empty") 69 | return 70 | } 71 | body, _ := ioutil.ReadAll(req.Body) 72 | req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) 73 | model := gjson.GetBytes(body, "model").String() 74 | deployment := GetDeploymentByModel(model) 75 | 76 | // Replace the Bearer field in the Authorization header with api-key 77 | token := "" 78 | 79 | // use the token from the environment variable if it is set 80 | if AzureOpenAIToken != "" { 81 | token = AzureOpenAIToken 82 | } else { 83 | token = strings.ReplaceAll(req.Header.Get("Authorization"), "Bearer ", "") 84 | } 85 | 86 | req.Header.Set("api-key", token) 87 | req.Header.Del("Authorization") 88 | 89 | // Set the Host, Scheme, Path, and RawPath of the request to the remote host and path 90 | originURL := req.URL.String() 91 | req.Host = remote.Host 92 | req.URL.Scheme = remote.Scheme 93 | req.URL.Host = remote.Host 94 | req.URL.Path = path.Join(fmt.Sprintf("/openai/deployments/%s", deployment), strings.Replace(req.URL.Path, "/v1/", "/", 1)) 95 | req.URL.RawPath = req.URL.EscapedPath() 96 | 97 | // Add the api-version query parameter to the request URL 98 | query := req.URL.Query() 99 | query.Add("api-version", AzureOpenAIAPIVersion) 100 | req.URL.RawQuery = query.Encode() 101 | 102 | log.Printf("proxying request [%s] %s -> %s", model, originURL, req.URL.String()) 103 | } 104 | return &httputil.ReverseProxy{Director: director} 105 | } 106 | 107 | func GetDeploymentByModel(model string) string { 108 | if v, ok := AzureOpenAIModelMapper[model]; ok { 109 | return v 110 | } 111 | // This is a fallback strategy in case the model is not found in the AzureOpenAIModelMapper 112 | return fallbackModelMapper.ReplaceAllString(model, "") 113 | } 114 | -------------------------------------------------------------------------------- /pkg/azure/types.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | type ListModelResponse struct { 4 | Object string `json:"object"` 5 | Data []Model `json:"data"` 6 | } 7 | 8 | type Model struct { 9 | ID string `json:"id"` 10 | Object string `json:"object"` 11 | Created int `json:"created"` 12 | OwnedBy string `json:"owned_by"` 13 | Permission []ModelPermission `json:"permission"` 14 | Root string `json:"root"` 15 | Parent any `json:"parent"` 16 | } 17 | 18 | type ModelPermission struct { 19 | ID string `json:"id"` 20 | Object string `json:"object"` 21 | Created int `json:"created"` 22 | AllowCreateEngine bool `json:"allow_create_engine"` 23 | AllowSampling bool `json:"allow_sampling"` 24 | AllowLogprobs bool `json:"allow_logprobs"` 25 | AllowSearchIndices bool `json:"allow_search_indices"` 26 | AllowView bool `json:"allow_view"` 27 | AllowFineTuning bool `json:"allow_fine_tuning"` 28 | Organization string `json:"organization"` 29 | Group any `json:"group"` 30 | IsBlocking bool `json:"is_blocking"` 31 | } 32 | -------------------------------------------------------------------------------- /pkg/openai/proxy.go: -------------------------------------------------------------------------------- 1 | package openai 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/http/httputil" 7 | "net/url" 8 | ) 9 | 10 | func NewOpenAIReverseProxy() *httputil.ReverseProxy { 11 | remote, _ := url.Parse("https://api.openai.com") 12 | director := func(req *http.Request) { 13 | // Set the Host, Scheme, Path, and RawPath of the request to the remote host and path 14 | originURL := req.URL.String() 15 | req.Host = remote.Host 16 | req.URL.Scheme = remote.Scheme 17 | req.URL.Host = remote.Host 18 | 19 | log.Printf("proxying request %s -> %s", originURL, req.URL.String()) 20 | } 21 | return &httputil.ReverseProxy{Director: director} 22 | } 23 | --------------------------------------------------------------------------------