mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
fix(gateway): normalize Gemini Vertex embedding transport
This commit is contained in:
@@ -28,14 +28,14 @@ Aether 还必须区分 Google 官方的 OpenAI-compatible 表面。它们使用
|
||||
| --- | --- | --- | --- |
|
||||
| Generate Content | `models/{model}:generateContent` | `projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent` | 两边都支持,但 URL 构造不同 |
|
||||
| Stream Generate Content | `models/{model}:streamGenerateContent?alt=sse` | `projects/{project}/locations/{location}/publishers/google/models/{model}:streamGenerateContent?alt=sse` | 两边都支持,但 URL 构造不同 |
|
||||
| Single Embedding | `models/{model}:embedContent` | `projects/{project}/locations/{location}/publishers/google/models/{model}:embedContent` | 两边都支持,但 URL 构造不同 |
|
||||
| Batch Embedding | `models/{model}:batchEmbedContents` | 官方 REST reference 当前未提供同名 Vertex 方法 | Developer API 可批量;Vertex 必须显式拒绝或拆分,不得伪装成 Vertex batch |
|
||||
| Single Embedding | `models/{model}:embedContent` | `projects/{project}/locations/{location}/publishers/google/models/{model}:predict` | Vertex 文本 embedding 使用 Predict contract:`instances[]` + `parameters` |
|
||||
| Batch Embedding | `models/{model}:batchEmbedContents` | 同一个 `:predict`,由 `instances[]` 表达多输入 | Aether 不切到 Developer API;模型自身的批量限制由 Vertex 明确返回 |
|
||||
|
||||
工程不变量:
|
||||
|
||||
1. 默认 Gemini provider 只能生成 Gemini Developer API URL,不得因为模型名是 Gemini 就走 Vertex。
|
||||
2. `provider_type = vertex_ai` 或明确的 Vertex auth/host 只能生成 Vertex URL,不得回退到 Gemini Developer API URL。
|
||||
3. Vertex embedding 批量请求在没有官方 batch 端点前不能静默改走 `generativelanguage.googleapis.com:batchEmbedContents`。
|
||||
3. Vertex embedding 请求必须使用 Vertex Predict contract,不得把 Developer API 的 `model/content/requests` body 原样发给 `:predict`。
|
||||
4. 任何“不支持”的情况必须在调度/URL 构造阶段显式暴露为不可用,不能伪成功。
|
||||
5. Provider 模板、runtime policy、URL builder、conversion policy、测试连接、live DB reconciliation 必须消费同一个语义模型。
|
||||
6. Google 官方 OpenAI-compatible root 已经包含 API root,Aether 不得额外拼接 `/v1`,否则会生成 `.../openai/v1/...` 或 `.../endpoints/openapi/v1/...` 这类错误 URL。
|
||||
@@ -77,6 +77,7 @@ Vertex AI 的 Gemini API REST reference 使用 `aiplatform.googleapis.com` 或 r
|
||||
- Vertex AI Generate Content REST: <https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/projects.locations.publishers.models/generateContent>
|
||||
- Vertex AI Stream Generate Content REST: <https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/projects.locations.publishers.models/streamGenerateContent>
|
||||
- Vertex AI Embed Content REST: <https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/projects.locations.publishers.models/embedContent>
|
||||
- Vertex AI Predict REST: <https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/projects.locations.publishers.models/predict>
|
||||
- Vertex AI REST resources: <https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/projects.locations.publishers.models>
|
||||
- Vertex AI text embeddings API: <https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api>
|
||||
- Vertex AI OpenAI compatibility: <https://cloud.google.com/vertex-ai/generative-ai/docs/start/openai>
|
||||
@@ -88,7 +89,9 @@ Vertex AI 的 Gemini API REST reference 使用 `aiplatform.googleapis.com` 或 r
|
||||
- 对 `global` location,可使用 `https://aiplatform.googleapis.com/v1/projects/{project}/locations/global/...`
|
||||
- Vertex API key 路径可走:
|
||||
- `https://aiplatform.googleapis.com/v1/publishers/google/models/{model}:{action}?key=...`
|
||||
- Vertex REST reference 当前列出 `embedContent`,未列出 `batchEmbedContents`。因此 Aether 不得自行构造 Vertex batch endpoint。
|
||||
- Vertex 文本 embedding API 文档使用 `:predict`,请求体是 `instances[]`,可选参数在 `parameters` 下;响应是 `predictions[].embeddings.values`。
|
||||
- Vertex REST reference 也列出 `embedContent`,但 Aether 当前 text embedding 主链使用 text embeddings guide 和 Predict API 的 contract。
|
||||
- Vertex `instances[]` 是在线 Predict 请求体,不等同于异步 batch prediction job。模型级输入数量限制由 Vertex 返回;Aether 不把超出限制的请求静默改走其他产品面。
|
||||
- Vertex OpenAI-compatible root 是 `/v1/projects/{project}/locations/{location}/endpoints/openapi`,其 OpenAI path 直接挂在这个 root 之后。
|
||||
- 自定义 Vertex OpenAI-compatible endpoint 可以使用 service account token 刷新,但只有 base URL 明确落在 `/endpoints/openapi` 时才能启用该 Vertex auth 语义。普通 `aiplatform.googleapis.com` + `openai:*` 不能被误判成 Vertex OpenAI compatibility。
|
||||
|
||||
@@ -175,15 +178,24 @@ Developer API 的批量 embedding 支持顶层 `requests[]`。Aether 可以继
|
||||
| --- | --- | --- | --- |
|
||||
| `gemini:generate_content` | false | 不适用 | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent` |
|
||||
| `gemini:generate_content` | true | 不适用 | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:streamGenerateContent?alt=sse` |
|
||||
| `gemini:embedding` | false | false | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:embedContent` |
|
||||
| `gemini:embedding` | false | true | unsupported, fail closed | 官方 REST reference 未提供 Vertex batch method |
|
||||
| `gemini:embedding` | false | false | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:predict` |
|
||||
| `gemini:embedding` | false | true | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:predict` |
|
||||
|
||||
Vertex 单条 embedding 的模型由 URL path 承载,body 不得重复携带顶层 `model` 字段,否则会触发 Vertex `oneof field '_model' is already set` 一类错误。body 应只保留 `content` 与显式 embedding options。批量请求如果来自 OpenAI embedding 的数组输入,在没有官方 Vertex batch 端点前有两个可选工程策略:
|
||||
Vertex text embedding 的模型由 URL path 承载,body 不得重复携带顶层 `model` 字段,否则会触发 Vertex `oneof field '_model' is already set` 一类错误。Aether 在 Vertex transport context 下必须把 Gemini Developer API embedding body 转成 Predict body:
|
||||
|
||||
1. 第一阶段 fail closed:返回明确 unsupported,不让它伪成功。
|
||||
2. 第二阶段显式 fan-out:Aether 自己把数组拆成多条 Vertex `embedContent` 调用,再按 OpenAI embedding 响应格式合并。
|
||||
```json
|
||||
{
|
||||
"instances": [
|
||||
{ "content": "text", "task_type": "RETRIEVAL_QUERY", "title": "optional" }
|
||||
],
|
||||
"parameters": {
|
||||
"outputDimensionality": 768,
|
||||
"autoTruncate": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
本次先做第一阶段,因为它不会隐藏批量语义差异;后续如果实现 fan-out,必须有单独设计与负载控制,不得把 fan-out 塞进 URL builder。
|
||||
如果输入已经是 Predict body,Aether 只移除重复的顶层 `model`。如果输入仍是 OpenAI body 或无法确定可转换,调度阶段必须显式失败,不得把未转换 body 发到 Vertex native endpoint。
|
||||
|
||||
### Google OpenAI-Compatible URL
|
||||
|
||||
@@ -215,10 +227,11 @@ OpenAI-compatible URL 属于显式 passthrough root,不参与 native Gemini UR
|
||||
|
||||
设计要求:
|
||||
|
||||
1. 该 converter 可以继续生成 Gemini batch body,但 transport 层必须知道这对 Vertex 不可直接消费。
|
||||
2. 如果未来实现 Vertex fan-out,fan-out 应发生在 gateway execution 层,而不是让 `request_url` 或 body converter 假装一个 Vertex batch endpoint 存在。
|
||||
1. 该 converter 可以继续生成 Gemini Developer API 的 `embedContent` / `batchEmbedContents` body。
|
||||
2. Transport 层必须在 Vertex context 下把该 body 转成 Predict body,并在无法转换时 fail closed。
|
||||
3. 所有 taskType / outputDimensionality 必须保持显式传递;不得默认注入会改变语义的 task 或维度。
|
||||
4. Developer API 单条 embedding body 可以保留 `model`;Vertex 单条 embedding 在 gateway transport 语义层必须删除顶层 `model`,因为 Vertex 模型已在 path 中指定。
|
||||
4. Vertex Predict 的 `instances[]` 只能表示在线 Predict 请求的一次调用;它不是异步 batch prediction job,也不是 Developer API `batchEmbedContents` 的静默替身。
|
||||
5. Developer API 单条 embedding body 可以保留 `model`;Vertex 单条 embedding 在 gateway transport 语义层必须删除顶层 `model`,因为 Vertex 模型已在 path 中指定。
|
||||
|
||||
### 格式转换矩阵
|
||||
|
||||
@@ -229,17 +242,17 @@ OpenAI-compatible URL 属于显式 passthrough root,不参与 native Gemini UR
|
||||
|
||||
`gemini:generate_content` 在 Developer API 与 Vertex AI 上使用同一 Gemini generate-content body 形态,因此格式转换器不应区分这两个产品面。产品面差异只留给 URL/auth 层处理。
|
||||
|
||||
`gemini:embedding` 同样应使用同一 Gemini embedding body 形态,但 URL 层必须区分单条和批量能力。
|
||||
`gemini:embedding` 在格式层仍先表达为 Gemini Developer API 的 embedding body。进入 Vertex transport context 时,transport 层再把它收敛到 Vertex Predict body。这样格式转换器不需要知道认证方式,URL/body transport 也不会把 Developer API body 原样发给 Vertex。
|
||||
|
||||
| 客户端格式 | Provider 格式 | Developer API | Vertex AI | 处理要求 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `openai:chat` | `gemini:generate_content` | 支持 | 支持 | OpenAI chat -> Gemini contents / generationConfig |
|
||||
| `gemini:generate_content` | `openai:chat` | 支持 | 支持 | Gemini contents -> OpenAI messages |
|
||||
| `openai:embedding` | `gemini:embedding` 单条 | 支持 | 支持 | OpenAI input string 或单项数组 -> Gemini `embedContent` body |
|
||||
| `openai:embedding` | `gemini:embedding` 多条 | 支持 | fail closed | Developer API -> `batchEmbedContents`; Vertex 无官方 batch endpoint |
|
||||
| `openai:embedding` | `gemini:embedding` 单条 | 支持 | 支持 | OpenAI input string 或单项数组 -> Gemini `embedContent` body;Vertex transport 再转 `instances[]` |
|
||||
| `openai:embedding` | `gemini:embedding` 多条 | 支持 | 支持于 transport 层 | Developer API -> `batchEmbedContents`;Vertex transport -> Predict `instances[]`,模型限制由 Vertex 返回 |
|
||||
| `gemini:embedding` 单条 | `openai:embedding` | 支持 | 支持 | Gemini `content.parts[].text` -> OpenAI `input` string |
|
||||
| `gemini:embedding` 批量 | `openai:embedding` | 支持 | 支持于格式层;执行层仍受 Vertex batch 限制 | Gemini `requests[]` -> OpenAI `input[]` |
|
||||
| `gemini:embedding` response | `openai:embedding` response | 支持 | 支持 | Gemini `embedding.values` / `embeddings[].values` -> OpenAI `data[].embedding` |
|
||||
| `gemini:embedding` 批量 | `openai:embedding` | 支持 | 支持 | Gemini `requests[]` -> OpenAI `input[]`;Vertex Predict response 同样可转 |
|
||||
| `gemini:embedding` response | `openai:embedding` response | 支持 | 支持 | Gemini `embedding.values` / `embeddings[].values` / Vertex `predictions[].embeddings.values` -> OpenAI `data[].embedding` |
|
||||
| `openai:embedding` response | `gemini:embedding` response | 支持 | 支持于格式层 | OpenAI `data[]` -> Gemini single `embedding` 或 batch `embeddings[]` |
|
||||
| `openai:chat` | `openai:chat` on Google OpenAI-compatible root | 支持 | 支持 | passthrough OpenAI schema,不做 native Gemini 转换 |
|
||||
| `openai:embedding` | `openai:embedding` on Google OpenAI-compatible root | 支持 | 支持 | passthrough OpenAI schema,不做 native Gemini 转换 |
|
||||
@@ -247,8 +260,8 @@ OpenAI-compatible URL 属于显式 passthrough root,不参与 native Gemini UR
|
||||
这张矩阵的关键点:
|
||||
|
||||
1. 格式层必须能双向理解 Gemini native embedding request/response 与 OpenAI embedding request/response。
|
||||
2. Vertex 不支持 batch endpoint 是 transport/execution 能力限制,不是格式转换器不能表达 batch。
|
||||
3. 一旦 provider family 是 Vertex,批量请求不能借格式转换之名回退到 Developer API。
|
||||
2. Vertex text embedding 使用 Predict contract;多输入由 `instances[]` 表达,不构造不存在的 `:batchEmbedContents`。
|
||||
3. 一旦 provider family 是 Vertex,任何 embedding 请求都不能借格式转换之名回退到 Developer API。
|
||||
4. 对 OpenAI embedding 单项数组,转换器必须生成 Gemini 单条 body,避免把“单条业务请求”误判成 Vertex batch。
|
||||
5. Google OpenAI-compatible passthrough 与 OpenAI -> Gemini native conversion 是两条显式路径。管理员通过 provider endpoint format 选择路径,Aether 不得自动“择优”改路。
|
||||
|
||||
@@ -265,14 +278,14 @@ Vertex provider 的固定模板必须包含:
|
||||
Runtime policy 必须表达:
|
||||
|
||||
- Vertex 能本地消费 Gemini generate content。
|
||||
- Vertex 能本地消费 Gemini single embedding。
|
||||
- Vertex 不支持直接消费 Gemini batch embedding,除非未来实现 Aether fan-out execution。
|
||||
- Vertex 能本地消费 Gemini embedding,并在 transport 层生成 Predict URL/body。
|
||||
- Vertex text embedding 的模型级输入数量限制由 Vertex 返回;Aether 不静默拆分、不静默降级到 Developer API。
|
||||
- 全局模型名与 Vertex 实际 provider 模型名必须可以分离。例如客户端继续请求全局 `gemini-embedding-2-preview` 时,Vertex provider model 可以映射到官方可用的 `gemini-embedding-2`;调度、key allowed_models、URL builder 必须消费映射后的 provider model,不得拿全局 preview 名直打 Vertex。
|
||||
|
||||
调度与 conversion policy 必须表达:
|
||||
|
||||
- `openai:embedding -> gemini:embedding` 可以被 Vertex provider 接收,仅限单条或 execution 层能处理的形态。
|
||||
- 对批量 input,不能只因为 provider endpoint 叫 `gemini:embedding` 就认为 Vertex 已经完整支持 batch。
|
||||
- `openai:embedding -> gemini:embedding` 可以被 Vertex provider 接收,transport 层负责把 Gemini Developer API body 转成 Vertex Predict body。
|
||||
- 对批量 input,不能生成 Vertex `:batchEmbedContents`,也不能回退到 `generativelanguage.googleapis.com`。
|
||||
- `request_pair_direct_auth` 对 Vertex API key 必须返回 `key` query auth;service account auth 由 OAuth refresh path 处理,不能伪造成普通 bearer key。
|
||||
|
||||
---
|
||||
@@ -291,10 +304,13 @@ Runtime policy 必须表达:
|
||||
- API key auth -> `aiplatform.googleapis.com/v1/publishers/google/models/...`
|
||||
- service account -> project/location path
|
||||
4. Vertex embedding URL:
|
||||
- API key auth -> `...:embedContent?key=...`
|
||||
- service account -> project/location `...:embedContent`
|
||||
5. Vertex batch embedding:
|
||||
- body 含顶层 `requests[]` 时,URL builder 返回 unsupported / `None`
|
||||
- API key auth -> `...:predict?key=...`
|
||||
- service account -> project/location `...:predict`
|
||||
5. Vertex embedding body:
|
||||
- 单条 `model/content` body -> `instances[]`,且移除顶层 `model`
|
||||
- body 含顶层 `requests[]` 时 -> `instances[]`
|
||||
- body 已经是 `instances[]` 时只清理重复 `model`
|
||||
- 无法映射的 body 在调度/模型测试阶段显式失败
|
||||
- 不得生成 `generativelanguage.googleapis.com`
|
||||
- 不得生成 `aiplatform.googleapis.com/...:batchEmbedContents`
|
||||
6. Provider template:
|
||||
@@ -302,9 +318,9 @@ Runtime policy 必须表达:
|
||||
- provider embedding support 矩阵包含 Vertex -> Gemini embedding
|
||||
7. Conversion:
|
||||
- OpenAI embedding 可以被转换到 Gemini embedding provider format
|
||||
- Vertex single embedding transport 可通过支持检查
|
||||
- Vertex single embedding execution plan 的 URL 使用 mapped provider model,body 不含顶层 `model`
|
||||
- Vertex batch embedding 不得通过 direct URL 构造检查
|
||||
- Vertex embedding transport 可通过支持检查
|
||||
- Vertex embedding execution plan 的 URL 使用 mapped provider model,body 不含顶层 `model`
|
||||
- Vertex embedding execution plan 的 body 使用 `instances[]` / `parameters`
|
||||
8. Gateway test connection:
|
||||
- Gemini generate content 测试不能强制 `maxOutputTokens = 5`
|
||||
- Google OpenAI-compatible `openai:chat` 测试不能强制 `max_tokens = 5`,否则 Gemini thinking 模型仍可能只返回 thought token / 空 visible content
|
||||
@@ -318,7 +334,7 @@ Runtime policy 必须表达:
|
||||
- Vertex API key key formats 允许 `gemini:generate_content` 与 `gemini:embedding`
|
||||
- Vertex service account key formats 允许 `claude:messages`、`gemini:generate_content` 与 `gemini:embedding`
|
||||
|
||||
测试断言必须检查具体 URL、具体 action、具体 unsupported 结果,不能只检查 `Some(url)` 或状态码。
|
||||
测试断言必须检查具体 URL、具体 action、具体 body contract 和具体失败原因,不能只检查 `Some(url)` 或状态码。
|
||||
|
||||
---
|
||||
|
||||
@@ -337,8 +353,8 @@ Runtime policy 必须表达:
|
||||
- Developer API embedding 单条可用
|
||||
- Developer API embedding 批量可用
|
||||
- Vertex generate content 返回 visible content 才算成功
|
||||
- Vertex single embedding 可用
|
||||
- Vertex batch embedding 显式 unsupported,不能伪成功
|
||||
- Vertex embedding 单输入可用
|
||||
- Vertex embedding 多输入如果模型拒绝,必须暴露 Vertex 原始失败,不能降级成 Developer API 成功
|
||||
4. 接入方地址核验:
|
||||
- astrbot plugin ltm
|
||||
- codex cli config
|
||||
@@ -356,7 +372,7 @@ http://aether-app:8084/v1
|
||||
|
||||
## 明确不做的事
|
||||
|
||||
1. 不把 Vertex batch embedding 写成隐藏循环。隐藏 fan-out 会改变成本、延迟、断路器行为和重试语义,必须另开设计。
|
||||
1. 不把 Vertex embedding 多输入写成隐藏循环。隐藏 fan-out 会改变成本、延迟、断路器行为和重试语义,必须另开设计。
|
||||
2. 不为了测试通过把 Vertex 请求降级到 Developer API。
|
||||
3. 不为了让 HTTP 200 看起来成功而接受空 candidate / MAX_TOKENS 无 visible content。
|
||||
4. 不改前端视觉定制、字体、品牌名、landing page 设计。
|
||||
@@ -369,8 +385,8 @@ http://aether-app:8084/v1
|
||||
|
||||
1. 固化 endpoint family 判定与 URL helper。
|
||||
2. 为 Vertex `gemini:embedding` 补齐 provider template、runtime policy、conversion policy。
|
||||
3. 让 request URL builder 对 Vertex single embedding 走 Vertex helper。
|
||||
4. 让 request URL builder 对 Vertex batch embedding fail closed。
|
||||
3. 让 request URL builder 对 Vertex embedding 走 Vertex Predict helper。
|
||||
4. 让 transport body semantics 对 Vertex embedding 生成 `instances[]` / `parameters`,无法转换时 fail closed。
|
||||
5. 移除测试连接中对 Gemini generate content 的过低 `maxOutputTokens` 硬编码,防止 Gemini 3 thinking 被预算挤空。
|
||||
6. 移除公开 test-connection 对 OpenAI-compatible chat 的过低 `max_tokens` 硬编码,防止 Google OpenAI-compatible root 复发同类空输出。
|
||||
7. 跑 red/green 测试。
|
||||
@@ -384,9 +400,8 @@ http://aether-app:8084/v1
|
||||
如果 7 天 embedding 重算必须在 Vertex 上高吞吐完成,建议后续单独实现 `VertexEmbeddingFanoutExecutor`:
|
||||
|
||||
- 输入 OpenAI embedding 数组。
|
||||
- 按配置分片,每片发单条或有限并发 Vertex `embedContent`。
|
||||
- 按配置分片,每片发单条或有限并发 Vertex `predict`。
|
||||
- 合并为 OpenAI embedding response。
|
||||
- 将每个子请求的失败、重试、成本、断路器状态独立记录。
|
||||
- UI 上明确显示这是 Aether fan-out,不是 Google 官方 Vertex batch endpoint。
|
||||
|
||||
这项增强不能混入本次 endpoint 语义修复,否则会扩大风险面。
|
||||
|
||||
Reference in New Issue
Block a user