Spaces:
Sleeping
Sleeping
File size: 2,538 Bytes
70e641d a5d73ff bf9f7d1 a5d73ff bf9f7d1 a5d73ff bf9f7d1 70e641d 6ab7946 70e641d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | """Interfaz común de los clientes de modelo.
Abstrae la ruta híbrida: medGemma auto-alojado (privado por defecto) y Claude (opcional,
mayor precisión). Ambos deben devolver una InterpretacionClinica validada; la validación
de esquema vive en cada cliente para poder reintentar ante salida malformada.
"""
from __future__ import annotations
from typing import Protocol
from ..schemas import InterpretacionClinica
class ErrorModelo(Exception):
"""Fallo al invocar un modelo o validar su salida.
`reintentable` decide si el servicio vuelve a muestrear. La distinción importa de verdad:
el reintento existe para la salida malformada de medGemma (razonamiento filtrado o bucle de
repetición), donde volver a muestrear suele funcionar. Reintentar un 429 hace lo contrario —
duplica la presión sobre la cuota de ZeroGPU, que es justo el recurso agotado— y reintentar
un rechazo por seguridad o una clave mal configurada no puede cambiar nada.
`saturado` marca los casos de límite de tasa/cuota, para poder responder 503 + Retry-After
en vez de un 502 genérico.
`truncado` marca la respuesta cortada a mitad de frase. Es reintentable, pero repetir la
misma petición no sirve: se ha medido que el corte es determinista y lo provoca el
contexto RAG comiéndose el presupuesto de generación del Space. El servicio lo usa para
reintentar con MENOS literatura en vez de con el mismo prompt.
"""
def __init__(
self,
mensaje: str,
*,
reintentable: bool = True,
saturado: bool = False,
truncado: bool = False,
) -> None:
super().__init__(mensaje)
self.reintentable = reintentable
self.saturado = saturado
self.truncado = truncado
class ClienteModelo(Protocol):
nombre: str
modelo: str
# True si el cliente devuelve TEXTO libre envuelto en `interpretacion` en vez de rellenar
# los campos estructurados. El servicio lo consulta para elegir el system prompt, para no
# exigir campos que esta ruta no puede rellenar y para suplir `requiere_derivacion` desde
# el motor determinista. Antes se deducía del nombre del cliente, que dejó de bastar en
# cuanto la prosa pudo venir también de un modelo local.
prosa: bool
async def interpretar(
self,
sistema: str,
mensaje_usuario: str,
imagenes: list[str],
) -> InterpretacionClinica:
"""Devuelve una interpretación validada o lanza ErrorModelo."""
...
|