English · Español
Lab 00 — FastAPI mínimo: POST /correct¶
🇪🇸 El primer paso: envolver el agente tutor en un endpoint HTTP. Sin batching, sin async; solo FastAPI + uvicorn + un handler que llama a
agent.correct().
Objetivo¶
Envolver el agente tutor de gramática de la Fase 32 en un servicio FastAPI. Verificar con curl que enviar {"sentence": "He goed to school"} devuelve una corrección. Establecer la estructura de proyecto para src/miniserve/.
Setup¶
- Agente de la Fase 32 (
from miniagent import GrammarTutorAgent). uv add fastapi uvicorn pydantic— añadir a las dependencias del proyecto.- Un
src/miniserve/app.pyen blanco.
Tareas¶
- Define los esquemas request/response en
src/miniserve/schemas.py:
from pydantic import BaseModel, Field
class CorrectRequest(BaseModel):
sentence: str = Field(min_length=1, max_length=500)
learner_id: str | None = Field(default=None, description="optional learner identifier")
class CorrectResponse(BaseModel):
corrected: str
explanation: str
was_correct: bool # true if the input was already grammatical
- Implementa
src/miniserve/app.py:
from fastapi import FastAPI
from miniagent import GrammarTutorAgent
from miniserve.schemas import CorrectRequest, CorrectResponse
app = FastAPI(title="Lynx Tutor")
agent = GrammarTutorAgent.load_default() # singleton
@app.post("/correct", response_model=CorrectResponse)
def correct(req: CorrectRequest) -> CorrectResponse:
result = agent.correct(req.sentence, learner_id=req.learner_id)
return CorrectResponse(
corrected=result.text,
explanation=result.why,
was_correct=result.was_correct,
)
- Añade
/healthzy/readyz:
@app.get("/healthz")
def healthz() -> dict[str, str]:
return {"status": "ok"}
_model_ready = False # set to True after agent.load_default() completes
@app.get("/readyz")
def readyz() -> dict[str, str | bool]:
return {"ready": _model_ready}
- Arranca el servidor:
- Roundtrip con curl:
curl -X POST http://127.0.0.1:8000/correct \
-H "Content-Type: application/json" \
-d '{"sentence": "He goed to school"}'
Esperado (módulo las frases exactas de la Fase 32):
{
"corrected": "He went to school",
"explanation": "The past tense of 'go' is the irregular form 'went'.",
"was_correct": false
}
-
Visita
/docsen el navegador: FastAPI autogenera la documentación OpenAPI enhttp://127.0.0.1:8000/docs. Verifica que tus esquemas se muestran correctamente. Prueba la UI "try it out" en el navegador. -
Prueba las rutas de error:
sentencevacío: debería dar HTTP 422 (error de validación Pydantic).sentenceausente: HTTP 422.- Payload sobredimensionado: HTTP 422.
sentenceválido, pero el modelo lanza excepción (mockéalo): HTTP 500.
Mediciones¶
Guarda en experiments/<date>-phase-33-lab-00/:
roundtrip.txt— output de los comandos curl.openapi.json— exportado deGET /openapi.json.manifest.json— versión de uvicorn, versión de FastAPI, hash del checkpoint del agente.
Aceptación¶
- El servidor arranca sin errores.
POST /correctcon un payload válido devuelve unCorrectResponseque cumple el esquema./healthzdevuelve 200./readyzdevuelve 200 después de cargar el agente, 503 antes.mypy --strict src/miniserve/pasa.
Trampas¶
- Cargar el agente en tiempo de import del módulo vs en un startup handler. Si la carga es lenta (~5s), el servidor aparece "started" antes de estar realmente listo —
/readyzmentiría. Usa el lifespan de FastAPI para fijar_model_readycorrectamente. Bonus: usalifespanen lugar de@app.on_event("startup")(deprecado desde FastAPI 0.95). - Compartir el agente entre peticiones. Es un singleton a nivel de módulo — pero si el agente tiene estado mutable (memoria, ver Fase 32), las peticiones concurrentes pueden corromperse entre sí. Confirma que el agente de la Fase 32 es stateless entre llamadas O añade un lock. (Debería ser stateless; la memoria por aprendiz se carga fresca en cada llamada.)
- Sintaxis Pydantic v1 vs v2. Esta fase usa v2 (
Field(...),BaseModel).
Siguiente: 01-sync-vs-async.md