- embedder.py:embed() 调 llama-server /v1/embeddings -> 对称 per-vector int8 量化(scale=127/max|v|,1024 维余弦扰动 ~1e-4 << 1e-2 验收); 超时/连接/非200/空形状 -> EmbedderDown(D-G4 fail-closed); quantize_int8/cosine_int8 辅助(int8 直接点积,scale 正标量不改方向) - routes.py 拆双路由组:/sense 前缀组 + /v1 无前缀组(/v1/embeddings OpenAI 兼容透传,embedder 不可用 503);build_sense_routers 返回列表, api.py 逐个 include;build_sense_router 保留向后兼容 - 测试 +6:量化余弦误差 200 组 <1e-2/范围/零向量/int8 余弦/ 正常量化/三种 fail-closed/透传端点(503+形状+400),全量 381 passed - 待真机项:llama-server embedder 端点冒烟(需用户配置 embedder.base_url)
53 lines
2.0 KiB
Python
53 lines
2.0 KiB
Python
"""Sense 路由(T-G1):/sense 前缀组 + /v1 无前缀组(embeddings 透传)。
|
||
|
||
/v1/route(T-G5 grader)后续加入无前缀组;/sense/admin/*(T-G3+)加入前缀组。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import List
|
||
|
||
from fastapi import APIRouter, Request
|
||
from fastapi.responses import JSONResponse
|
||
|
||
from gateway.sense.config import SenseConfig
|
||
from gateway.sense.embedder import embed
|
||
from gateway.sense.errors import EmbedderDown
|
||
|
||
|
||
def build_sense_routers(cfg: SenseConfig) -> List[APIRouter]:
|
||
"""组装 sense 路由组:[/sense 前缀组, /v1 无前缀组]。"""
|
||
main = APIRouter(prefix="/sense")
|
||
v1 = APIRouter()
|
||
|
||
@main.get("/health", tags=["sense"])
|
||
async def health():
|
||
"""sense 面健康检查(含灰度状态,供看板/运维)。"""
|
||
return {"enabled": cfg.enabled, "mode": cfg.mode,
|
||
"embedder": cfg.embedder.base_url}
|
||
|
||
@v1.post("/v1/embeddings", tags=["sense"])
|
||
async def embeddings(request: Request):
|
||
"""OpenAI 兼容透传 embedder(客户端/代理共用)。
|
||
|
||
请求:{"model"?, "input": str};响应:{"object":"list","data":[{"object":
|
||
"embedding","index":0,"embedding":[int8...]}],"model":...}。
|
||
embedder 不可用 -> 503(D-G4 fail-closed,客户端可感知降级)。
|
||
"""
|
||
try:
|
||
body = await request.json()
|
||
except Exception:
|
||
body = {}
|
||
text = str((body or {}).get("input") or "").strip()
|
||
if not text:
|
||
return JSONResponse({"error": {"message": "input 必填"}}, status_code=400)
|
||
model = str((body or {}).get("model") or cfg.embedder.model)
|
||
try:
|
||
vec = await embed(text, cfg.embedder)
|
||
except EmbedderDown as e:
|
||
return JSONResponse(
|
||
{"error": {"message": str(e), "type": "embedder_down"}}, status_code=503)
|
||
return {"object": "list", "model": model,
|
||
"data": [{"object": "embedding", "index": 0, "embedding": vec}]}
|
||
|
||
return [main, v1]
|