ai-robot-core/ai-service/app/api/mid/slots.py

141 lines
4.0 KiB
Python

"""
Runtime Slot API.
[AC-MRS-09,10] 运行时槽位查询接口
"""
import logging
from datetime import datetime
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Query
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_session
from app.core.exceptions import MissingTenantIdException
from app.core.tenant import get_tenant_id
from app.services.mid.role_based_field_provider import RoleBasedFieldProvider, InvalidRoleError
from app.services.slot_definition_service import SlotDefinitionService
from app.schemas.metadata import VALID_FIELD_ROLES
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/mid/slots", tags=["RuntimeSlot"])
def get_current_tenant_id() -> str:
"""Get current tenant ID from context."""
tenant_id = get_tenant_id()
if not tenant_id:
raise MissingTenantIdException()
return tenant_id
@router.get(
"/by-role",
operation_id="getSlotsByRole",
summary="Get slots by role",
description="[AC-MRS-10] 运行时接口,按角色获取槽位定义及关联的元数据字段信息",
)
async def get_slots_by_role(
tenant_id: Annotated[str, Depends(get_current_tenant_id)],
session: Annotated[AsyncSession, Depends(get_session)],
role: Annotated[str, Query(
description="[AC-MRS-10] 字段角色: resource_filter/slot/prompt_var/routing_signal"
)] = "slot",
) -> JSONResponse:
"""
[AC-MRS-10] 按角色获取槽位定义
Args:
role: 字段角色,默认为 slot
"""
logger.info(
f"[AC-MRS-10] Getting slots by role: tenant={tenant_id}, role={role}"
)
provider = RoleBasedFieldProvider(session)
try:
slots = await provider.get_slot_definitions_by_role(tenant_id, role)
except InvalidRoleError as e:
return JSONResponse(
status_code=400,
content={
"error_code": "INVALID_ROLE",
"message": str(e),
"details": {
"valid_roles": e.valid_roles
}
}
)
return JSONResponse(content=slots)
@router.get(
"/{slot_key}",
operation_id="getSlotValue",
summary="Get runtime slot value",
description="[AC-MRS-09] 获取指定槽位的运行时值,包含来源、置信度、更新时间",
)
async def get_slot_value(
tenant_id: Annotated[str, Depends(get_current_tenant_id)],
session: Annotated[AsyncSession, Depends(get_session)],
slot_key: str,
user_id: Annotated[str | None, Query(
description="用户 ID"
)] = None,
session_id: Annotated[str | None, Query(
description="会话 ID"
)] = None,
) -> JSONResponse:
"""
[AC-MRS-09] 获取运行时槽位值
Args:
slot_key: 槽位键名
user_id: 用户 ID
session_id: 会话 ID
"""
logger.info(
f"[AC-MRS-09] Getting slot value: tenant={tenant_id}, slot_key={slot_key}, "
f"user_id={user_id}, session_id={session_id}"
)
service = SlotDefinitionService(session)
slot_def = await service.get_slot_definition_by_key(tenant_id, slot_key)
if not slot_def:
return JSONResponse(
status_code=404,
content={
"error_code": "NOT_FOUND",
"message": f"Slot '{slot_key}' not found",
}
)
value = slot_def.default_value
source = "default"
confidence = 1.0
if value is None:
if slot_def.type == "string":
value = ""
elif slot_def.type == "number":
value = 0
elif slot_def.type == "boolean":
value = False
elif slot_def.type in ["enum", "array_enum"]:
value = [] if slot_def.type == "array_enum" else ""
return JSONResponse(
content={
"key": slot_key,
"value": value,
"source": source,
"confidence": confidence,
"updated_at": datetime.utcnow().isoformat(),
}
)