"""Sense 路由(T-G2 补观察初始化):/sense 前缀组 + /v1 无前缀组。""" 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 from gateway.sense.observer import get_observer from gateway.sense.store import SenseStore def build_sense_routers(cfg: SenseConfig) -> List[APIRouter]: """组装 sense 路由组:[/sense 前缀组, /v1 无前缀组]。""" main = APIRouter(prefix="/sense") v1 = APIRouter() store = SenseStore.init_db(cfg.db_path) @main.on_event("startup") async def _start_observer(): get_observer(store) obs = get_observer() await obs.start() @main.on_event("shutdown") async def _stop_observer(): obs = get_observer() await obs.flush_once() await obs.stop() @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(客户端/代理共用)。""" 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}]} # /v1/route(T-G5 grader)与 /sense/admin/*(T-G3+)后续追加。 return [main, v1]