├── .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 | [](https://goreportcard.com/report/github.com/diemus/azure-openai-proxy)
4 | [](https://github.com/diemus/azure-openai-proxy/blob/main/LICENSE)
5 | [](https://github.com/diemus/azure-openai-proxy)
6 | [](https://github.com/diemus/azure-openai-proxy)
7 | [](https://github.com/diemus/azure-openai-proxy)
8 | [](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 | [](https://star-history.com/#diemus/azure-openai-proxy&Date)
140 |
--------------------------------------------------------------------------------
/README.zh-cn.md:
--------------------------------------------------------------------------------
1 | # Azure OpenAI Proxy
2 |
3 | [](https://goreportcard.com/report/github.com/diemus/azure-openai-proxy)
4 | [](https://github.com/diemus/azure-openai-proxy/blob/main/LICENSE)
5 | [](https://github.com/diemus/azure-openai-proxy)
6 | [](https://github.com/diemus/azure-openai-proxy)
7 | [](https://github.com/diemus/azure-openai-proxy)
8 | [](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 | [](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 |
--------------------------------------------------------------------------------