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.
- vertext_ai_proxy-0.2.1/PKG-INFO +227 -0
- vertext_ai_proxy-0.2.1/README.md +201 -0
- vertext_ai_proxy-0.2.1/pyproject.toml +54 -0
- vertext_ai_proxy-0.2.1/pyproject.toml.orig +43 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/__init__.py +4 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/_version.py +16 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/app.py +144 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/auth.py +10 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/cli.py +158 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/config.py +22 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/__init__.py +1 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/auth.py +122 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/config.py +104 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/exceptions.py +40 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/security.py +49 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/core/templates.py +40 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/main.py +19 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/models.py +19 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/proxy.py +52 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/__init__.py +1 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/health.py +16 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/__init__.py +1 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/chat.py +15 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/embeddings.py +17 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/routers/v1/models.py +24 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/__init__.py +1 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/chat.py +18 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/common.py +18 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/embeddings.py +60 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/schemas/models.py +17 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/security.py +14 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/__init__.py +1 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/chat_service.py +118 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/embedding_service.py +98 -0
- vertext_ai_proxy-0.2.1/src/vertext_ai_proxy/services/model_service.py +128 -0
- 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
|
+
[](https://pypi.org/project/vertext-ai-proxy/)
|
|
30
|
+
[](https://pypi.org/project/vertext-ai-proxy/)
|
|
31
|
+
[](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
|
+
[](https://pypi.org/project/vertext-ai-proxy/)
|
|
4
|
+
[](https://pypi.org/project/vertext-ai-proxy/)
|
|
5
|
+
[](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,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()
|