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