背景
最近在做多模态能力整合,需求可以概括为三点:
- 对话:希望继续用 OpenAI SDK / LangChain 等现有生态,不想重写 HTTP 层;
- 生图 / 生音乐 :需要 Gemini 原生
v1beta的generateContent,参数与官方文档一致; - 鉴权统一:一套 API Key,对话和多模态分开端点但密钥相同。
本文把实际跑通的请求格式和踩坑点记下来,方便同类项目对照。相关服务端实现与 README 已放在 Gitee 开源仓库(见文末「开源参考」),读者可 clone 后本地阅读接口设计与示例。下文代码里的 BASE_URL 请替换为你自己的网关地址。
整体架构:两套端点、一套密钥
| 能力 | 协议形态 | 典型路径 |
|---|---|---|
| 对话补全 | OpenAI 兼容 | POST /v1/chat/completions |
| 模型列表 | OpenAI 兼容 | GET /v1/models |
| 图片 / 音乐等 | Gemini 原生 | POST /v1beta/models/{model}:generateContent |
鉴权头统一为:
http
Authorization: Bearer <你的API密钥>
对话侧模型 ID 示例:gemini-3.5-flash、gemini-3.1-pro-preview。
图片侧示例:gemini-2.5-flash-image。
音乐侧示例:lyria-3-clip-preview。
第一步:确认模型列表
先用 GET 拉模型,确认 ID 与权限(不要用 POST):
bash
curl "${BASE_URL}/v1/models" \
-H "Authorization: Bearer ${API_KEY}"
返回结构与 OpenAI /v1/models 类似,客户端可按 id 字段筛选。
第二步:非流式对话
bash
curl "${BASE_URL}/v1/chat/completions" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.5-flash",
"messages": [
{"role": "user", "content": "用一句话解释什么是 SSE"}
]
}'
Python(OpenAI 官方 SDK):
python
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["API_KEY"],
base_url=os.environ.get("BASE_URL", "https://api.example.com/v1"),
)
resp = client.chat.completions.create(
model="gemini-3.5-flash",
messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)
迁移要点 :若项目里已有 OpenAI(api_key=..., base_url=...),通常只需改 base_url、api_key、model 三个参数,业务层 messages 结构可保持不变。
第三步:流式对话(SSE)
流式是最容易「看起来没响应」的一环,需要同时满足:
- 请求体:
"stream": true - 请求头:
Accept: text/event-stream
bash
curl "${BASE_URL}/v1/chat/completions" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"model": "gemini-3.5-flash",
"stream": true,
"messages": [
{"role": "user", "content": "写一首关于调试的短诗"}
]
}'
Python 流式读取:
python
stream = client.chat.completions.create(
model="gemini-3.5-flash",
stream=True,
messages=[{"role": "user", "content": "写一首关于调试的短诗"}],
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
若前端自行解析 SSE,注意按 data: 行拆分,并以 [DONE] 结束。
第四步:图片 generateContent(v1beta)
图片不走 OpenAI 的 /images/generations,而是 Gemini 原生 JSON。响应里图片通常在 candidates[0].content.parts[].inlineData.data,值为 Base64。
bash
curl "${BASE_URL}/v1beta/models/gemini-2.5-flash-image:generateContent" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"parts": [{"text": "一只在星空下奔跑的柴犬,赛博朋克风格"}]
}]
}'
Python 解码示例:
python
import base64
import json
import urllib.request
url = f"{os.environ['BASE_URL'].rstrip('/v1')}/v1beta/models/gemini-2.5-flash-image:generateContent"
req = urllib.request.Request(
url,
data=json.dumps({
"contents": [{"parts": [{"text": "一只在星空下奔跑的柴犬,赛博朋克风格"}]}]
}).encode(),
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
body = json.load(resp)
b64 = body["candidates"][0]["content"]["parts"][0]["inlineData"]["data"]
with open("out.png", "wb") as f:
f.write(base64.b64decode(b64))
图生图、多轮编辑可在 contents 里追加带 inlineData 的 parts,与 Google 官方 Gemini 文档结构相同。
第五步:音乐模型(同为 v1beta)
音乐模型同样走 generateContent,模型 ID 如 lyria-3-clip-preview(短片段)、lyria-3-pro-preview(完整曲)。请求体结构与官方一致;响应中音频同样可能以 inlineData 或官方定义的字段返回,需按实际 JSON 解析。
建议:先用 curl 打印完整 JSON,确认音频字段路径,再封装到业务代码。
与 LangChain 对接(可选)
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gemini-3.5-flash",
openai_api_key=os.environ["API_KEY"],
openai_api_base=os.environ["BASE_URL"],
)
print(llm.invoke("用三句话介绍 RAG").content)
本质仍是 OpenAI 兼容层;多模态 generateContent 需单独写 HTTP 客户端或封装 Tool。
调试清单(FAQ)
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 流式无输出 | 未设 stream: true 或缺少 Accept: text/event-stream |
两项同时检查 |
/v1/models 报错 |
误用 POST | 改为 GET |
| 图片接口 404 | 路径写成 /v1/images |
改用 /v1beta/models/{model}:generateContent |
| 图片无法显示 | 按 URL 取图 | 实际为 Base64,需 b64decode |
| 401 / 403 | Key 无该模型权限 | 在控制台检查 Key 的能力授权 |
安全与工程建议
- 密钥不要进仓库 :用环境变量或密钥管理服务,
git push前跑 secret scan。 - 前端不要直挂 Key:浏览器暴露密钥会被盗刷;走自有后端转发。
- 日志脱敏 :打印请求日志时截断
Authorization。 - 超时与重试:流式连接建议单独设 read timeout;非幂等写操作谨慎自动重试。
开源参考
本次实践对应的开源项目:Aisoui 模型中转站(Java 后端 + 控制台 + API 文档示例)。
仓库地址(Gitee):
https://gitee.com/mtq851/aisoui-model-transfer-station
Clone 命令:
bash
git clone https://gitee.com/mtq851/aisoui-model-transfer-station.git
仓库内可参考:
README.md--- 端点说明、模型列表、curl / Python 示例demo/stream-chat.html--- 浏览器侧 SSE 流式对话 Demoserver/--- Spring Boot 网关实现(OpenAI 兼容层 + Gemini v1beta 转发)
若你自建部署,将本文中的 ${BASE_URL} 指向自己的服务即可;仅阅读接口格式,看 README 与 Demo 足够。
小结
- 对话:OpenAI 兼容
/v1/chat/completions,迁移成本低。 - 多模态:Gemini 原生
/v1beta/...:generateContent,与官方参数一致。 - 流式:记得 SSE 请求头;图片:记得 Base64 解码。
以上为个人接入笔记,模型 ID、配额与错误码以你所用网关控制台为准。欢迎在评论区交流具体报错栈与集成问题。
免责声明
本文为个人技术实践记录,仅供开发参考。开源仓库按现有 License 使用;自建部署请自行评估合规与安全。第三方网关的上游能力、可用性及政策以各模型厂商实际为准;请勿将 API Key 提交至公开仓库,并自行承担密钥保管与数据安全责任。