feat(网关): 接口4 改由网关本机直接调豆包视觉模型,不再代理到 worker

- gateway/app.py: 接口4 handler 改为直接调用 face_features 模块
- face_features.py: API Key 读取支持 gateway/config.json
- gateway/config.example.json: 增加 ark_api_key 配置项
This commit is contained in:
Ubuntu
2026-06-15 20:46:12 +08:00
parent 043a4c0603
commit 68fe1c1406
3 changed files with 81 additions and 15 deletions
+12 -8
View File
@@ -55,17 +55,21 @@ _client = None
def _load_api_key() -> str | None:
"""ARK_API_KEY 环境变量优先,否则读 worker_config.json 的 ark_api_key。"""
"""ARK_API_KEY 环境变量优先,否则读 worker_config.json / gateway/config.json 的 ark_api_key。"""
key = os.getenv("ARK_API_KEY")
if key:
return key
cfg = os.path.join(os.path.dirname(__file__), "worker_config.json")
if os.path.isfile(cfg):
try:
with open(cfg, encoding="utf-8") as f:
return json.load(f).get("ark_api_key")
except Exception as e: # noqa: BLE001
logger.warning("读取 worker_config.json ark_api_key 失败:%s", e)
base = os.path.dirname(__file__)
for cfg_name in ("worker_config.json", "gateway/config.json"):
cfg = os.path.join(base, cfg_name)
if os.path.isfile(cfg):
try:
with open(cfg, encoding="utf-8") as f:
v = json.load(f).get("ark_api_key")
if v:
return v
except Exception as e: # noqa: BLE001
logger.warning("读取 %s ark_api_key 失败:%s", cfg_name, e)
return None
+68 -7
View File
@@ -5,11 +5,17 @@
"""
import asyncio
import base64
import json
import logging
import time
from contextlib import asynccontextmanager
from io import BytesIO
from pathlib import Path
from fastapi import FastAPI, Request
from typing import Optional
from fastapi import FastAPI, File, Form, Request, UploadFile
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from gateway.config import load_config
@@ -217,9 +223,6 @@ _GROW_B_FORMS = {
"marked_image_file": {"type": "file", "description": "划线图片文件"},
"marked_image_url": {"type": "string", "description": "划线图片 URL"},
"marked_image_base64": {"type": "string", "description": "划线图片 base64"},
"original_image_file": {"type": "file", "description": "原始用户照片文件"},
"original_image_url": {"type": "string", "description": "原始用户照片 URL"},
"original_image_base64": {"type": "string", "description": "原始用户照片 base64"},
}
@@ -242,9 +245,67 @@ async def hair_grow_b(request: Request):
@app.post("/api/v1/face/features", tags=["人脸分析"])
async def face_features(request: Request):
"""接口4:用户特征分析"""
return await _proxy(request, "/api/v1/face/features")
async def face_features(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带前缀)"),
):
"""接口4:用户特征分析 — 本机直接调豆包视觉模型,不经过 worker。"""
import uuid as _uuid
# 三选一校验
provided = [x for x in (image_file, image_url, image_base64) if x]
if len(provided) != 1:
return JSONResponse(status_code=400, content={
"code": 1007, "message": "图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
img_bytes = None
if image_file:
raw = await image_file.read()
if len(raw) > 1_000_000:
return JSONResponse(status_code=400, content={
"code": 1006, "message": "文件超出 1 MB 限制",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
img_bytes = raw
elif image_base64:
b64 = image_base64
if "," in b64:
b64 = b64.split(",", 1)[1]
try:
img_bytes = base64.b64decode(b64)
except Exception:
return JSONResponse(status_code=400, content={
"code": 1008, "message": "图片格式不支持(base64 解码失败)",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
from fastapi.concurrency import run_in_threadpool
from face_features import analyze_features, has_face
try:
feats = await run_in_threadpool(analyze_features, img_bytes, image_url)
except Exception as ex:
logger.exception("接口4 豆包调用失败")
return JSONResponse(status_code=503, content={
"code": 1007, "message": f"分析服务异常:{ex}",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
if not has_face(feats):
return JSONResponse(status_code=400, content={
"code": 1001, "message": "无法识别人像",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
})
return JSONResponse(status_code=200, content={
"code": 0,
"message": "success",
"request_id": f"gw-{_uuid.uuid4().hex[:8]}",
"data": {"features": json.dumps(feats, ensure_ascii=False)},
})
@app.post("/api/v1/hairline/generate", tags=["人脸分析"])
+1
View File
@@ -13,6 +13,7 @@
"unhealthy_threshold": 2,
"healthy_threshold": 1
},
"ark_api_key": "",
"dispatch": {
"per_worker_concurrency": 1,
"queue_wait_seconds": 30,