Update Dockerfile
Browse files- Dockerfile +12 -98
Dockerfile
CHANGED
|
@@ -2,60 +2,32 @@ FROM python:3.11-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
# 安装
|
| 6 |
RUN pip install fastapi uvicorn google-genai
|
| 7 |
|
| 8 |
-
# 生成 main.py
|
| 9 |
RUN cat > main.py << 'PYEOF'
|
| 10 |
from fastapi import FastAPI, Request
|
| 11 |
-
from fastapi.responses import JSONResponse
|
| 12 |
from google import genai
|
| 13 |
-
import os, uvicorn
|
| 14 |
|
| 15 |
app = FastAPI()
|
| 16 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 17 |
client = genai.Client(api_key=GOOGLE_API_KEY)
|
| 18 |
|
| 19 |
-
# ---------- 模型名称映射(短名 -> 完整 resources 名)----------
|
| 20 |
-
MODEL_MAP = {
|
| 21 |
-
# 聊天模型
|
| 22 |
-
"gemini-2.5-flash": "models/gemini-2.5-flash",
|
| 23 |
-
"gemini-2.5-pro": "models/gemini-2.5-pro",
|
| 24 |
-
"gemini-1.5-flash": "models/gemini-1.5-flash",
|
| 25 |
-
"gemini-1.5-pro": "models/gemini-1.5-pro",
|
| 26 |
-
# 嵌入模型
|
| 27 |
-
"gemini-embedding-001": "models/gemini-embedding-001",
|
| 28 |
-
"gemini-embedding-2-preview": "models/gemini-embedding-2-preview",
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
# 静态模型列表:作为 API 调用失败时的后备方案
|
| 32 |
STATIC_MODELS = [
|
| 33 |
-
{"id": "gemini-2.5-flash", "object": "model", "owned_by": "gemini"},
|
| 34 |
-
{"id": "gemini-2.5-pro", "object": "model", "owned_by": "gemini"},
|
| 35 |
-
{"id": "gemini-1.5-flash", "object": "model", "owned_by": "gemini"},
|
| 36 |
-
{"id": "gemini-1.5-pro", "object": "model", "owned_by": "gemini"},
|
| 37 |
{"id": "gemini-embedding-001", "object": "model", "owned_by": "gemini"},
|
| 38 |
{"id": "gemini-embedding-2-preview", "object": "model", "owned_by": "gemini"},
|
| 39 |
]
|
| 40 |
|
| 41 |
-
# -
|
| 42 |
-
|
| 43 |
-
""
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
"""将 OpenAI 格式的 messages 转换为 Gemini 要求的 contents 格式"""
|
| 48 |
-
contents = []
|
| 49 |
-
for msg in messages:
|
| 50 |
-
role = msg.get("role", "user")
|
| 51 |
-
text = msg.get("content", "")
|
| 52 |
-
# Gemini 使用 "user" 和 "model" 角色,将 "assistant" 映射为 "model"
|
| 53 |
-
if role == "assistant":
|
| 54 |
-
role = "model"
|
| 55 |
-
elif role not in ("user", "model"):
|
| 56 |
-
role = "user" # 其他角色一律视为 user
|
| 57 |
-
contents.append({"role": role, "parts": [{"text": text}]})
|
| 58 |
-
return contents
|
| 59 |
|
| 60 |
def fetch_embedding_models():
|
| 61 |
"""动态获取支持 embedContent 的 Gemini Embedding 模型列表"""
|
|
@@ -76,77 +48,19 @@ def fetch_embedding_models():
|
|
| 76 |
print(f"动态获取模型列表失败,回退到静态列表: {e}")
|
| 77 |
return STATIC_MODELS
|
| 78 |
|
| 79 |
-
# ---------- API 端点 ----------
|
| 80 |
@app.get("/v1/models")
|
| 81 |
async def list_models():
|
| 82 |
-
"""
|
| 83 |
-
返回可用模型列表。
|
| 84 |
-
优先动态获取嵌入模型,如果失败则返回静态列表。
|
| 85 |
-
"""
|
| 86 |
models = fetch_embedding_models()
|
| 87 |
-
# 确保基础聊天模型也出现在列表中(如果动态列表只返回嵌入模型)
|
| 88 |
-
chat_models = [m for m in STATIC_MODELS if "embedding" not in m["id"]]
|
| 89 |
-
for cm in chat_models:
|
| 90 |
-
if not any(m["id"] == cm["id"] for m in models):
|
| 91 |
-
models.append(cm)
|
| 92 |
return JSONResponse({"object": "list", "data": models})
|
| 93 |
|
| 94 |
-
@app.post("/v1/chat/completions")
|
| 95 |
-
async def chat_completions(request: Request):
|
| 96 |
-
"""OpenAI 兼容的聊天补全端点(非流式)"""
|
| 97 |
-
try:
|
| 98 |
-
body = await request.json()
|
| 99 |
-
model_short = body.get("model", "gemini-2.5-flash")
|
| 100 |
-
messages = body.get("messages", [])
|
| 101 |
-
temperature = body.get("temperature", 0.7)
|
| 102 |
-
max_tokens = body.get("max_tokens", 1000)
|
| 103 |
-
|
| 104 |
-
model_full = get_full_model_name(model_short)
|
| 105 |
-
contents = convert_openai_messages(messages)
|
| 106 |
-
|
| 107 |
-
# 调用 Gemini API
|
| 108 |
-
response = client.models.generate_content(
|
| 109 |
-
model=model_full,
|
| 110 |
-
contents=contents,
|
| 111 |
-
config={
|
| 112 |
-
"temperature": temperature,
|
| 113 |
-
"max_output_tokens": max_tokens,
|
| 114 |
-
}
|
| 115 |
-
)
|
| 116 |
-
|
| 117 |
-
# 构造 OpenAI 兼容响应
|
| 118 |
-
choice = {
|
| 119 |
-
"index": 0,
|
| 120 |
-
"message": {
|
| 121 |
-
"role": "assistant",
|
| 122 |
-
"content": response.text
|
| 123 |
-
},
|
| 124 |
-
"finish_reason": "stop"
|
| 125 |
-
}
|
| 126 |
-
return JSONResponse({
|
| 127 |
-
"id": f"chatcmpl-{int(time.time())}",
|
| 128 |
-
"object": "chat.completion",
|
| 129 |
-
"created": int(time.time()),
|
| 130 |
-
"model": model_short,
|
| 131 |
-
"choices": [choice],
|
| 132 |
-
"usage": {
|
| 133 |
-
"prompt_tokens": response.usage_metadata.prompt_token_count,
|
| 134 |
-
"completion_tokens": response.usage_metadata.candidates_token_count,
|
| 135 |
-
"total_tokens": response.usage_metadata.total_token_count
|
| 136 |
-
}
|
| 137 |
-
})
|
| 138 |
-
except Exception as e:
|
| 139 |
-
return JSONResponse({"error": str(e)}, status_code=500)
|
| 140 |
-
|
| 141 |
@app.post("/v1/embeddings")
|
| 142 |
async def create_embeddings(request: Request):
|
| 143 |
-
"""OpenAI 兼容的嵌入端点"""
|
| 144 |
try:
|
| 145 |
body = await request.json()
|
| 146 |
model_short = body.get("model", "gemini-embedding-001")
|
|
|
|
|
|
|
| 147 |
input_text = body.get("input", "")
|
| 148 |
-
model_full = get_full_model_name(model_short)
|
| 149 |
-
|
| 150 |
result = client.models.embed_content(
|
| 151 |
model=model_full,
|
| 152 |
contents=input_text
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
# 安装依赖
|
| 6 |
RUN pip install fastapi uvicorn google-genai
|
| 7 |
|
| 8 |
+
# 生成 main.py(支持自动获取模型列表)
|
| 9 |
RUN cat > main.py << 'PYEOF'
|
| 10 |
from fastapi import FastAPI, Request
|
| 11 |
+
from fastapi.responses import JSONResponse
|
| 12 |
from google import genai
|
| 13 |
+
import os, uvicorn
|
| 14 |
|
| 15 |
app = FastAPI()
|
| 16 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 17 |
client = genai.Client(api_key=GOOGLE_API_KEY)
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# 静态模型列表:作为 API 调用失败时的后备方案
|
| 20 |
STATIC_MODELS = [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
{"id": "gemini-embedding-001", "object": "model", "owned_by": "gemini"},
|
| 22 |
{"id": "gemini-embedding-2-preview", "object": "model", "owned_by": "gemini"},
|
| 23 |
]
|
| 24 |
|
| 25 |
+
# 模型名称映射(短名称 -> 完整路径),用于 /v1/embeddings 端点
|
| 26 |
+
MODEL_MAP = {
|
| 27 |
+
"text-embedding-004": "models/text-embedding-004",
|
| 28 |
+
"gemini-embedding-001": "models/gemini-embedding-001",
|
| 29 |
+
"gemini-embedding-2-preview": "models/gemini-embedding-2-preview",
|
| 30 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def fetch_embedding_models():
|
| 33 |
"""动态获取支持 embedContent 的 Gemini Embedding 模型列表"""
|
|
|
|
| 48 |
print(f"动态获取模型列表失败,回退到静态列表: {e}")
|
| 49 |
return STATIC_MODELS
|
| 50 |
|
|
|
|
| 51 |
@app.get("/v1/models")
|
| 52 |
async def list_models():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
models = fetch_embedding_models()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
return JSONResponse({"object": "list", "data": models})
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
@app.post("/v1/embeddings")
|
| 57 |
async def create_embeddings(request: Request):
|
|
|
|
| 58 |
try:
|
| 59 |
body = await request.json()
|
| 60 |
model_short = body.get("model", "gemini-embedding-001")
|
| 61 |
+
# 自动补全模型名称为完整路径
|
| 62 |
+
model_full = MODEL_MAP.get(model_short, f"models/{model_short}")
|
| 63 |
input_text = body.get("input", "")
|
|
|
|
|
|
|
| 64 |
result = client.models.embed_content(
|
| 65 |
model=model_full,
|
| 66 |
contents=input_text
|