vertext-ai-proxy 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. vertext_ai_proxy-0.2.1/PKG-INFO +227 -0
  2. vertext_ai_proxy-0.2.1/README.md +201 -0
  3. vertext_ai_proxy-0.2.1/pyproject.toml +54 -0
  4. vertext_ai_proxy-0.2.1/pyproject.toml.orig +43 -0
  5. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/__init__.py +4 -0
  6. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/_version.py +16 -0
  7. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/app.py +144 -0
  8. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/auth.py +10 -0
  9. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/cli.py +158 -0
  10. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/config.py +22 -0
  11. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/__init__.py +1 -0
  12. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/auth.py +122 -0
  13. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/config.py +104 -0
  14. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/exceptions.py +40 -0
  15. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/security.py +49 -0
  16. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/templates.py +40 -0
  17. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/main.py +19 -0
  18. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/models.py +19 -0
  19. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/proxy.py +52 -0
  20. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/__init__.py +1 -0
  21. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/health.py +16 -0
  22. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/__init__.py +1 -0
  23. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/chat.py +15 -0
  24. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/embeddings.py +17 -0
  25. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/models.py +24 -0
  26. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/__init__.py +1 -0
  27. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/chat.py +18 -0
  28. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/common.py +18 -0
  29. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/embeddings.py +60 -0
  30. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/models.py +17 -0
  31. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/security.py +14 -0
  32. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/__init__.py +1 -0
  33. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/chat_service.py +118 -0
  34. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/embedding_service.py +98 -0
  35. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/model_service.py +128 -0
  36. vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/vertex_client.py +67 -0
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: vertext-ai-proxy
3
+ Version: 0.2.1
4
+ Summary: 轻量级 Google Cloud Vertex AI(Gemini Enterprise Agent Platform)OpenAI 协议反向代理网关
5
+ Keywords: vertex-ai,gemini,openai,reverse-proxy,llm,embeddings
6
+ Author: entr0pia
7
+ Author-email: entr0pia <30486766+entr0pia@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Framework :: FastAPI
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Dist: fastapi>=0.162.0
15
+ Requires-Dist: google-auth>=2.62.0
16
+ Requires-Dist: httpx>=0.28.1
17
+ Requires-Dist: pydantic>=2.14.1
18
+ Requires-Dist: pyyaml>=6.0.3
19
+ Requires-Dist: requests>=2.34.2
20
+ Requires-Dist: uvicorn>=0.54.0
21
+ Requires-Python: >=3.11
22
+ Project-URL: Homepage, https://github.com/entr0pia/vertext-ai-proxy
23
+ Project-URL: Repository, https://github.com/entr0pia/vertext-ai-proxy
24
+ Project-URL: Issues, https://github.com/entr0pia/vertext-ai-proxy/issues
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Vertex AI OpenAI Proxy
28
+
29
+ [![PyPI version](https://img.shields.io/pypi/v/vertext-ai-proxy.svg)](https://pypi.org/project/vertext-ai-proxy/)
30
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://pypi.org/project/vertext-ai-proxy/)
31
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
32
+
33
+ 轻量级 Google Cloud Vertex AI(Gemini Enterprise Agent Platform)OpenAI 协议反向代理网关。
34
+
35
+ 将 Google Cloud 的服务账号(Service Account)OAuth 2.0 认证转换为通用的 OpenAI API Key 鉴权模式,下游客户端(如 Cursor、NextChat、Cherry Studio、OpenAI SDK 等)只需配置标准的 `Base URL` 和静态 `API Key`,即可无缝调用 Vertex AI 模型。
36
+
37
+ ---
38
+
39
+ ## 快速上手
40
+
41
+ ### 方式 A:通过 PyPI 全局安装(推荐)
42
+
43
+ 本工具已正式发布至 PyPI,支持通过 `uv tool`、`uvx` 或 `pip` 全局安装与运行:
44
+
45
+ ```powershell
46
+ # 1. 全局安装 CLI 工具(推荐使用 uv)
47
+ uv tool install vertext-ai-proxy
48
+
49
+ # 或者使用 pip 全局安装:
50
+ # pip install vertext-ai-proxy
51
+
52
+ # 2. 在任意工作目录下一键初始化配置文件
53
+ vap init
54
+
55
+ # 3. 按提示编辑当前目录下的 config.local.yaml:
56
+ # - 设置您的自定义 API Key (例如 sk-my-secret-token)
57
+ # - 将 GCP 服务账号 service_account.json 放在该目录(或配置绝对路径)
58
+
59
+ # 4. 启动反向代理服务
60
+ vap run
61
+ ```
62
+
63
+ > [!TIP]
64
+ > **免安装运行(uvx)**:如果您不想全局安装,也可以通过 `uvx` 直接执行:
65
+ > ```powershell
66
+ > uvx vertext-ai-proxy vap init
67
+ > uvx vertext-ai-proxy vap run
68
+ > ```
69
+ > **版本升级**:
70
+ > ```powershell
71
+ > uv tool upgrade vertext-ai-proxy
72
+ > # 或 pip install --upgrade vertext-ai-proxy
73
+ > ```
74
+
75
+ 支持的 CLI 参数:
76
+ - `vap init [-f/--force]`:在当前目录创建 `config.local.yaml`(带防误覆盖保护)。
77
+ - `vap run [-c/--config <path>] [-H/--host <host>] [-p/--port <port>]`:启动服务。
78
+ - `vap -v/--version`:查看当前工具版本。
79
+
80
+ ---
81
+
82
+ ### 方式 B:源码本地开发运行
83
+
84
+ #### 1. 放置 GCP 服务账号密钥
85
+
86
+ 从 Google Cloud 控制台下载服务账号密钥 JSON,重命名为 `service_account.json` 放置在项目根目录:
87
+
88
+ ```text
89
+ Vertext-AI-Proxy/
90
+ ├── service_account.json <-- 放置在此
91
+ ├── config.example.yaml
92
+ └── ...
93
+ ```
94
+
95
+ ### 2. 创建私有配置文件
96
+
97
+ 复制示例配置文件为私有覆盖配置 `config.local.yaml`(此文件已被 `.gitignore` 自动忽略):
98
+
99
+ ```powershell
100
+ Copy-Item config.example.yaml config.local.yaml
101
+ ```
102
+
103
+ 打开 `config.local.yaml`,**将示例 Key 修改为您自己的私密 Key**:
104
+
105
+ ```yaml
106
+ server:
107
+ host: "127.0.0.1" # 默认仅允许本地访问
108
+ port: 8766 # 默认端口
109
+
110
+ proxy:
111
+ api_keys:
112
+ - "sk-my-secret-token-123456" # ⚠️ 请修改为您自己的密码,勿使用示例 Key
113
+
114
+ auto_prefix_model: true
115
+
116
+ # 可选:如果在国内需要配置代理访问 Google,取消注释并填入代理端口:
117
+ # http_proxy: "http://127.0.0.1:7890"
118
+
119
+ gcp:
120
+ service_account_file: "service_account.json"
121
+ project_id: "" # 留空将自动从 service_account.json 读取
122
+ location: "global" # 推荐 global 或对应部署区域
123
+ ```
124
+
125
+ ### 3. 启动服务
126
+
127
+ 使用 `uv` 启动服务:
128
+
129
+ ```powershell
130
+ $env:PYTHONUTF8=1; uv run python -m vertext_ai_proxy.app
131
+ ```
132
+
133
+ 启动成功后,终端将输出类似如下信息:
134
+ ```text
135
+ INFO: Started server process
136
+ INFO: Waiting for application startup.
137
+ INFO: Vertex AI OpenAI Proxy started on http://127.0.0.1:8766 (GCP Project: your-project-id, Location: global)
138
+ INFO: Application startup complete.
139
+ INFO: Uvicorn running on http://127.0.0.1:8766 (Press CTRL+C to quit)
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 核心特性
145
+
146
+ - **协议原生兼容**:直接对接 Google 官方 `endpoints/openapi`,无需进行复杂的数据结构重写与字段转换。
147
+ - **全能向量生成(Embeddings)**:提供标准 OpenAI `/v1/embeddings` 端点,透明桥接 Google Vertex AI 原生预测能力,原生支持 `text-embedding-004`、`gemini-embedding-001` 和 `text-multilingual-embedding-002`,支持自定义向量维度与批量生成。
148
+ - **自动化 Token 管理**:读取 `service_account.json` 自动解析 `project_id`,在内存中管理并自动异步续签 Google OAuth Bearer Token(有效时长 1 小时,过期前 5 分钟自动刷新)。
149
+ - **零缓冲流式输出**:基于 `httpx` 实现 Server-Sent Events(SSE)无缓冲实时打字机输出,长文本生成更稳定。
150
+ - **安全优先(Secure by Default)**:
151
+ - 默认仅监听本地回环地址(`127.0.0.1:8766`),杜绝局域网或公网非预期暴露。
152
+ - **启动期强校验**:启动时检测到示例/占位 Key(如 `sk-change-this-...`)或空 Key 时,立即终止运行并报错提示。
153
+ - `.gitignore` 默认严格忽略 `config.local.yaml` 与 `service_account.json`。
154
+ - **模型名智能兼容**:客户端请求 `gemini-2.5-flash` 时,代理可自动重写为 `google/gemini-2.5-flash`。
155
+ - **多 Key 与网络代理**:支持多客户端 API Key 白名单配置,支持配置本地网络代理端口(`http_proxy`)。
156
+
157
+ ---
158
+
159
+ ## 客户端接入方式
160
+
161
+ ### 1. Cursor / NextChat / Cherry Studio 配置
162
+
163
+ - **API 接口地址(Base URL)**:`http://127.0.0.1:8766/v1`
164
+ - **API Key**:您在 `config.local.yaml` 中设置的 Key(如 `sk-my-secret-token-123456`)
165
+ - **模型名称**:`gemini-2.5-flash`、`gemini-2.5-pro`、`gemini-1.5-pro`(代理会自动补全 `google/` 前缀)
166
+
167
+ ### 2. Python OpenAI SDK
168
+
169
+ ```python
170
+ import openai
171
+
172
+ client = openai.OpenAI(
173
+ base_url="http://127.0.0.1:8766/v1",
174
+ api_key="sk-my-secret-token-123456",
175
+ )
176
+
177
+ response = client.chat.completions.create(
178
+ model="gemini-2.5-flash",
179
+ messages=[
180
+ {"role": "user", "content": "你好,请做个自我介绍!"},
181
+ ],
182
+ stream=True,
183
+ )
184
+
185
+ for chunk in response:
186
+ content = chunk.choices[0].delta.content or ""
187
+ print(content, end="", flush=True)
188
+ ```
189
+
190
+ ### 3. Python OpenAI SDK 生成文本向量 (Embeddings)
191
+
192
+ ```python
193
+ import openai
194
+
195
+ client = openai.OpenAI(
196
+ base_url="http://127.0.0.1:8766/v1",
197
+ api_key="sk-my-secret-token-123456",
198
+ )
199
+
200
+ # 1. 单文本嵌入
201
+ embedding_res = client.embeddings.create(
202
+ model="text-embedding-004",
203
+ input="The quick brown fox jumps over the lazy dog",
204
+ )
205
+ print("Vector dims:", len(embedding_res.data[0].embedding))
206
+
207
+ # 2. 多文本批量嵌入并自定义维度
208
+ batch_res = client.embeddings.create(
209
+ model="gemini-embedding-001",
210
+ input=["First document", "Second document"],
211
+ dimensions=512,
212
+ )
213
+ print(f"Batch count: {len(batch_res.data)}, Dims: {len(batch_res.data[0].embedding)}")
214
+ ```
215
+
216
+ ### 4. cURL 命令行测试
217
+
218
+ ```bash
219
+ curl http://127.0.0.1:8766/v1/chat/completions \
220
+ -H "Content-Type: application/json" \
221
+ -H "Authorization: Bearer sk-my-secret-token-123456" \
222
+ -d '{
223
+ "model": "gemini-2.5-flash",
224
+ "messages": [{"role": "user", "content": "Hello!"}]
225
+ }'
226
+ ```
227
+
@@ -0,0 +1,201 @@
1
+ # Vertex AI OpenAI Proxy
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/vertext-ai-proxy.svg)](https://pypi.org/project/vertext-ai-proxy/)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://pypi.org/project/vertext-ai-proxy/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ 轻量级 Google Cloud Vertex AI(Gemini Enterprise Agent Platform)OpenAI 协议反向代理网关。
8
+
9
+ 将 Google Cloud 的服务账号(Service Account)OAuth 2.0 认证转换为通用的 OpenAI API Key 鉴权模式,下游客户端(如 Cursor、NextChat、Cherry Studio、OpenAI SDK 等)只需配置标准的 `Base URL` 和静态 `API Key`,即可无缝调用 Vertex AI 模型。
10
+
11
+ ---
12
+
13
+ ## 快速上手
14
+
15
+ ### 方式 A:通过 PyPI 全局安装(推荐)
16
+
17
+ 本工具已正式发布至 PyPI,支持通过 `uv tool`、`uvx` 或 `pip` 全局安装与运行:
18
+
19
+ ```powershell
20
+ # 1. 全局安装 CLI 工具(推荐使用 uv)
21
+ uv tool install vertext-ai-proxy
22
+
23
+ # 或者使用 pip 全局安装:
24
+ # pip install vertext-ai-proxy
25
+
26
+ # 2. 在任意工作目录下一键初始化配置文件
27
+ vap init
28
+
29
+ # 3. 按提示编辑当前目录下的 config.local.yaml:
30
+ # - 设置您的自定义 API Key (例如 sk-my-secret-token)
31
+ # - 将 GCP 服务账号 service_account.json 放在该目录(或配置绝对路径)
32
+
33
+ # 4. 启动反向代理服务
34
+ vap run
35
+ ```
36
+
37
+ > [!TIP]
38
+ > **免安装运行(uvx)**:如果您不想全局安装,也可以通过 `uvx` 直接执行:
39
+ > ```powershell
40
+ > uvx vertext-ai-proxy vap init
41
+ > uvx vertext-ai-proxy vap run
42
+ > ```
43
+ > **版本升级**:
44
+ > ```powershell
45
+ > uv tool upgrade vertext-ai-proxy
46
+ > # 或 pip install --upgrade vertext-ai-proxy
47
+ > ```
48
+
49
+ 支持的 CLI 参数:
50
+ - `vap init [-f/--force]`:在当前目录创建 `config.local.yaml`(带防误覆盖保护)。
51
+ - `vap run [-c/--config <path>] [-H/--host <host>] [-p/--port <port>]`:启动服务。
52
+ - `vap -v/--version`:查看当前工具版本。
53
+
54
+ ---
55
+
56
+ ### 方式 B:源码本地开发运行
57
+
58
+ #### 1. 放置 GCP 服务账号密钥
59
+
60
+ 从 Google Cloud 控制台下载服务账号密钥 JSON,重命名为 `service_account.json` 放置在项目根目录:
61
+
62
+ ```text
63
+ Vertext-AI-Proxy/
64
+ ├── service_account.json <-- 放置在此
65
+ ├── config.example.yaml
66
+ └── ...
67
+ ```
68
+
69
+ ### 2. 创建私有配置文件
70
+
71
+ 复制示例配置文件为私有覆盖配置 `config.local.yaml`(此文件已被 `.gitignore` 自动忽略):
72
+
73
+ ```powershell
74
+ Copy-Item config.example.yaml config.local.yaml
75
+ ```
76
+
77
+ 打开 `config.local.yaml`,**将示例 Key 修改为您自己的私密 Key**:
78
+
79
+ ```yaml
80
+ server:
81
+ host: "127.0.0.1" # 默认仅允许本地访问
82
+ port: 8766 # 默认端口
83
+
84
+ proxy:
85
+ api_keys:
86
+ - "sk-my-secret-token-123456" # ⚠️ 请修改为您自己的密码,勿使用示例 Key
87
+
88
+ auto_prefix_model: true
89
+
90
+ # 可选:如果在国内需要配置代理访问 Google,取消注释并填入代理端口:
91
+ # http_proxy: "http://127.0.0.1:7890"
92
+
93
+ gcp:
94
+ service_account_file: "service_account.json"
95
+ project_id: "" # 留空将自动从 service_account.json 读取
96
+ location: "global" # 推荐 global 或对应部署区域
97
+ ```
98
+
99
+ ### 3. 启动服务
100
+
101
+ 使用 `uv` 启动服务:
102
+
103
+ ```powershell
104
+ $env:PYTHONUTF8=1; uv run python -m vertext_ai_proxy.app
105
+ ```
106
+
107
+ 启动成功后,终端将输出类似如下信息:
108
+ ```text
109
+ INFO: Started server process
110
+ INFO: Waiting for application startup.
111
+ INFO: Vertex AI OpenAI Proxy started on http://127.0.0.1:8766 (GCP Project: your-project-id, Location: global)
112
+ INFO: Application startup complete.
113
+ INFO: Uvicorn running on http://127.0.0.1:8766 (Press CTRL+C to quit)
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 核心特性
119
+
120
+ - **协议原生兼容**:直接对接 Google 官方 `endpoints/openapi`,无需进行复杂的数据结构重写与字段转换。
121
+ - **全能向量生成(Embeddings)**:提供标准 OpenAI `/v1/embeddings` 端点,透明桥接 Google Vertex AI 原生预测能力,原生支持 `text-embedding-004`、`gemini-embedding-001` 和 `text-multilingual-embedding-002`,支持自定义向量维度与批量生成。
122
+ - **自动化 Token 管理**:读取 `service_account.json` 自动解析 `project_id`,在内存中管理并自动异步续签 Google OAuth Bearer Token(有效时长 1 小时,过期前 5 分钟自动刷新)。
123
+ - **零缓冲流式输出**:基于 `httpx` 实现 Server-Sent Events(SSE)无缓冲实时打字机输出,长文本生成更稳定。
124
+ - **安全优先(Secure by Default)**:
125
+ - 默认仅监听本地回环地址(`127.0.0.1:8766`),杜绝局域网或公网非预期暴露。
126
+ - **启动期强校验**:启动时检测到示例/占位 Key(如 `sk-change-this-...`)或空 Key 时,立即终止运行并报错提示。
127
+ - `.gitignore` 默认严格忽略 `config.local.yaml` 与 `service_account.json`。
128
+ - **模型名智能兼容**:客户端请求 `gemini-2.5-flash` 时,代理可自动重写为 `google/gemini-2.5-flash`。
129
+ - **多 Key 与网络代理**:支持多客户端 API Key 白名单配置,支持配置本地网络代理端口(`http_proxy`)。
130
+
131
+ ---
132
+
133
+ ## 客户端接入方式
134
+
135
+ ### 1. Cursor / NextChat / Cherry Studio 配置
136
+
137
+ - **API 接口地址(Base URL)**:`http://127.0.0.1:8766/v1`
138
+ - **API Key**:您在 `config.local.yaml` 中设置的 Key(如 `sk-my-secret-token-123456`)
139
+ - **模型名称**:`gemini-2.5-flash`、`gemini-2.5-pro`、`gemini-1.5-pro`(代理会自动补全 `google/` 前缀)
140
+
141
+ ### 2. Python OpenAI SDK
142
+
143
+ ```python
144
+ import openai
145
+
146
+ client = openai.OpenAI(
147
+ base_url="http://127.0.0.1:8766/v1",
148
+ api_key="sk-my-secret-token-123456",
149
+ )
150
+
151
+ response = client.chat.completions.create(
152
+ model="gemini-2.5-flash",
153
+ messages=[
154
+ {"role": "user", "content": "你好,请做个自我介绍!"},
155
+ ],
156
+ stream=True,
157
+ )
158
+
159
+ for chunk in response:
160
+ content = chunk.choices[0].delta.content or ""
161
+ print(content, end="", flush=True)
162
+ ```
163
+
164
+ ### 3. Python OpenAI SDK 生成文本向量 (Embeddings)
165
+
166
+ ```python
167
+ import openai
168
+
169
+ client = openai.OpenAI(
170
+ base_url="http://127.0.0.1:8766/v1",
171
+ api_key="sk-my-secret-token-123456",
172
+ )
173
+
174
+ # 1. 单文本嵌入
175
+ embedding_res = client.embeddings.create(
176
+ model="text-embedding-004",
177
+ input="The quick brown fox jumps over the lazy dog",
178
+ )
179
+ print("Vector dims:", len(embedding_res.data[0].embedding))
180
+
181
+ # 2. 多文本批量嵌入并自定义维度
182
+ batch_res = client.embeddings.create(
183
+ model="gemini-embedding-001",
184
+ input=["First document", "Second document"],
185
+ dimensions=512,
186
+ )
187
+ print(f"Batch count: {len(batch_res.data)}, Dims: {len(batch_res.data[0].embedding)}")
188
+ ```
189
+
190
+ ### 4. cURL 命令行测试
191
+
192
+ ```bash
193
+ curl http://127.0.0.1:8766/v1/chat/completions \
194
+ -H "Content-Type: application/json" \
195
+ -H "Authorization: Bearer sk-my-secret-token-123456" \
196
+ -d '{
197
+ "model": "gemini-2.5-flash",
198
+ "messages": [{"role": "user", "content": "Hello!"}]
199
+ }'
200
+ ```
201
+
@@ -0,0 +1,54 @@
1
+ [project]
2
+ name = "vertext-ai-proxy"
3
+ version = "0.2.1"
4
+ description = "轻量级 Google Cloud Vertex AI(Gemini Enterprise Agent Platform)OpenAI 协议反向代理网关"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.11"
8
+ keywords = [
9
+ "vertex-ai",
10
+ "gemini",
11
+ "openai",
12
+ "reverse-proxy",
13
+ "llm",
14
+ "embeddings",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Framework :: FastAPI",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ "fastapi>=0.162.0",
25
+ "google-auth>=2.62.0",
26
+ "httpx>=0.28.1",
27
+ "pydantic>=2.14.1",
28
+ "pyyaml>=6.0.3",
29
+ "requests>=2.34.2",
30
+ "uvicorn>=0.54.0",
31
+ ]
32
+
33
+ [[project.authors]]
34
+ name = "entr0pia"
35
+ email = "30486766+entr0pia@users.noreply.github.com"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/entr0pia/vertext-ai-proxy"
39
+ Repository = "https://github.com/entr0pia/vertext-ai-proxy"
40
+ Issues = "https://github.com/entr0pia/vertext-ai-proxy/issues"
41
+
42
+ [project.scripts]
43
+ vap = "vertext_ai_proxy.cli:main"
44
+ vertext-ai-proxy = "vertext_ai_proxy.cli:main"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.13.5,<0.14.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=9.1.1",
53
+ "pytest-asyncio>=1.4.1",
54
+ ]
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "vertext-ai-proxy"
3
+ version = "0.2.1"
4
+ description = "轻量级 Google Cloud Vertex AI(Gemini Enterprise Agent Platform)OpenAI 协议反向代理网关"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "entr0pia", email = "30486766+entr0pia@users.noreply.github.com" },
9
+ ]
10
+ requires-python = ">=3.11"
11
+ keywords = ["vertex-ai", "gemini", "openai", "reverse-proxy", "llm", "embeddings"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Framework :: FastAPI",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
+ ]
19
+ dependencies = [
20
+ "fastapi>=0.162.0",
21
+ "google-auth>=2.62.0",
22
+ "httpx>=0.28.1",
23
+ "pydantic>=2.14.1",
24
+ "pyyaml>=6.0.3",
25
+ "requests>=2.34.2",
26
+ "uvicorn>=0.54.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/entr0pia/vertext-ai-proxy"
31
+ Repository = "https://github.com/entr0pia/vertext-ai-proxy"
32
+ Issues = "https://github.com/entr0pia/vertext-ai-proxy/issues"
33
+
34
+ [project.scripts]
35
+ vap = "vertext_ai_proxy.cli:main"
36
+ vertext-ai-proxy = "vertext_ai_proxy.cli:main"
37
+
38
+ [build-system]
39
+ requires = ["uv_build>=0.13.5,<0.14.0"]
40
+ build-backend = "uv_build"
41
+
42
+ [dependency-groups]
43
+ dev = ["pytest>=9.1.1", "pytest-asyncio>=1.4.1"]
@@ -0,0 +1,4 @@
1
+ from vertext_ai_proxy._version import __version__
2
+ from vertext_ai_proxy.cli import main
3
+
4
+ __all__ = ["main", "__version__"]
@@ -0,0 +1,16 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+ from pathlib import Path
3
+ import re
4
+
5
+ try:
6
+ __version__ = version("vertext-ai-proxy")
7
+ except PackageNotFoundError:
8
+ try:
9
+ pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
10
+ if pyproject_path.is_file():
11
+ match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', pyproject_path.read_text(encoding="utf-8"))
12
+ __version__ = match.group(1) if match else "0.0.0.dev0"
13
+ else:
14
+ __version__ = "0.0.0.dev0"
15
+ except Exception:
16
+ __version__ = "0.0.0.dev0"
@@ -0,0 +1,144 @@
1
+ from collections.abc import AsyncGenerator
2
+ from contextlib import asynccontextmanager
3
+ import logging
4
+ from fastapi import FastAPI, Request
5
+ from fastapi.exceptions import RequestValidationError
6
+ from fastapi.responses import JSONResponse
7
+ import httpx
8
+ import uvicorn
9
+
10
+ from vertext_ai_proxy.core.auth import GCPTokenManager
11
+ from vertext_ai_proxy._version import __version__
12
+ from vertext_ai_proxy.core.config import AppConfig, load_config, validate_config
13
+ from vertext_ai_proxy.core.exceptions import (
14
+ OpenAIAPIException,
15
+ openai_exception_handler,
16
+ )
17
+ from vertext_ai_proxy.core.security import APIKeyValidator
18
+ from vertext_ai_proxy.proxy import VertexAIProxy
19
+ from vertext_ai_proxy.routers.health import router as health_router
20
+ from vertext_ai_proxy.routers.v1.chat import router as chat_router
21
+ from vertext_ai_proxy.routers.v1.embeddings import router as embeddings_router
22
+ from vertext_ai_proxy.routers.v1.models import router as models_router
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ def create_app(
28
+ config: AppConfig | None = None,
29
+ token_manager: GCPTokenManager | None = None,
30
+ http_client: httpx.AsyncClient | None = None,
31
+ ) -> FastAPI:
32
+ """FastAPI 应用工厂函数,支持依赖注入以便利单元测试。"""
33
+ active_config = config or load_config()
34
+
35
+ # 启动前强制执行配置安全校验(阻断示例占位 Key 和空 Key)
36
+ validate_config(active_config)
37
+
38
+ # 初始化 Token 管理器
39
+ active_token_manager = token_manager or GCPTokenManager(active_config.gcp)
40
+
41
+ created_client = False
42
+ effective_http_client = http_client
43
+ if effective_http_client is None:
44
+ proxy_url = active_config.proxy.http_proxy
45
+ effective_http_client = httpx.AsyncClient(
46
+ proxy=proxy_url if proxy_url else None,
47
+ follow_redirects=True,
48
+ )
49
+ created_client = True
50
+
51
+ proxy_instance = VertexAIProxy(
52
+ config=active_config,
53
+ token_manager=active_token_manager,
54
+ http_client=effective_http_client,
55
+ )
56
+
57
+ @asynccontextmanager
58
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
59
+ logger.info(
60
+ f"Vertex AI OpenAI Proxy started on http://{active_config.server.host}:{active_config.server.port} "
61
+ f"(GCP Project: {active_token_manager.project_id}, Location: {active_token_manager.location})"
62
+ )
63
+ try:
64
+ yield
65
+ finally:
66
+ if created_client and effective_http_client:
67
+ await effective_http_client.aclose()
68
+ logger.info("HTTP client closed.")
69
+
70
+ app = FastAPI(
71
+ title="Vertex AI OpenAI Proxy",
72
+ description="Lightweight reverse proxy translating Google Cloud Vertex AI OpenAPI to OpenAI format",
73
+ version=__version__,
74
+ lifespan=lifespan,
75
+ )
76
+
77
+ # 注册依赖服务与组件至 app.state
78
+ app.state.config = active_config
79
+ app.state.proxy = proxy_instance
80
+ app.state.client = proxy_instance.client
81
+ app.state.chat_service = proxy_instance.chat_service
82
+ app.state.embedding_service = proxy_instance.embedding_service
83
+ app.state.model_service = proxy_instance.model_service
84
+ app.state.api_key_validator = APIKeyValidator(active_config)
85
+
86
+ # 注册符合 OpenAI 标准的异常拦截器
87
+ app.add_exception_handler(OpenAIAPIException, openai_exception_handler)
88
+
89
+ @app.exception_handler(RequestValidationError)
90
+ async def validation_exception_handler(
91
+ request: Request, exc: RequestValidationError
92
+ ) -> JSONResponse:
93
+ errors = exc.errors()
94
+ err_msg = "; ".join([f"{err.get('loc', [])}: {err.get('msg')}" for err in errors])
95
+ return JSONResponse(
96
+ status_code=400,
97
+ content={
98
+ "error": {
99
+ "message": err_msg,
100
+ "type": "invalid_request_error",
101
+ "param": str(errors[0].get("loc", [None])[-1]) if errors else None,
102
+ "code": "invalid_parameter",
103
+ }
104
+ },
105
+ )
106
+
107
+ @app.exception_handler(Exception)
108
+ async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
109
+ logger.exception(f"Unhandled error processing {request.method} {request.url}: {exc}")
110
+ return JSONResponse(
111
+ status_code=500,
112
+ content={
113
+ "error": {
114
+ "message": f"Internal proxy error: {str(exc)}",
115
+ "type": "api_error",
116
+ "param": None,
117
+ "code": "internal_error",
118
+ }
119
+ },
120
+ )
121
+
122
+ # 挂载模块化路由
123
+ app.include_router(health_router)
124
+ app.include_router(models_router)
125
+ app.include_router(chat_router)
126
+ app.include_router(embeddings_router)
127
+
128
+ return app
129
+
130
+
131
+ def main() -> None:
132
+ """服务主入口命令行。"""
133
+ config = load_config()
134
+ app = create_app(config=config)
135
+ uvicorn.run(
136
+ app,
137
+ host=config.server.host,
138
+ port=config.server.port,
139
+ log_level=config.server.log_level.lower(),
140
+ )
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()