Math Service — Desarrollo local
Guía completa para desarrollar el Math Service localmente, incluyendo setup, tests y flujo de trabajo.
Prerrequisitos
- Python 3.13+
- uv (gestor de paquetes recomendado)
- MongoDB corriendo (local o via Docker)
- Ollama con modelo
nomic-embed-textinstalado - RAG Service arrancado (para endpoints de topics)
Instalar Ollama y el modelo de embeddings
# Instalar Ollama (Linux)
curl -fsSL https://ollama.ai/install.sh | sh
# Descargar el modelo de embeddings
ollama pull nomic-embed-text
Setup del entorno
1. Crear entorno virtual
# Desde la raíz del proyecto
uv venv
source .venv/bin/activate
2. Instalar dependencias
# Instalar el servicio y sus dependencias
uv pip install -e ./math_service
# Instalar el módulo de investigación matemática (requerido)
uv pip install -e ./math_investigation
# Instalar dependencias de desarrollo (tests, linting)
uv pip install -e "./math_service[dev]"
3. Crear archivo .env
cp .env.example .env # si existe, o crear manualmente
Contenido mínimo para desarrollo:
# MongoDB
MONGO_HOSTNAME=localhost
MONGO_PORT=27017
# Ollama
OLLAMA_HOST=localhost
OLLAMA_PORT=11434
OLLAMA_MODEL=nomic-embed-text
# RAG Service
RAG_SERVICE_URL=http://localhost:8081
# Mistral (opcional - para títulos descriptivos de tópicos)
# MISTRAL_API_KEY=tu_api_key_aqui
4. Arrancar MongoDB en local
Con Docker (más sencillo):
docker run -d --name mongo-local -p 27017:27017 mongo:7
O con Docker Compose (stack completo):
docker compose up -d mongo ollama rag_service
Arrancar el servidor de desarrollo
# Desde la raíz del proyecto
python -m math_service
El servidor arrancará en http://localhost:8083 con recarga automática activada.
Verificar que está funcionando:
curl http://localhost:8083/health
Con uvicorn directamente
uvicorn math_service.api:app --reload --host 0.0.0.0 --port 8083
Ejecutar tests
Tests unitarios
# Ejecutar todos los tests del servicio
pytest math_service/tests/ -v
# Solo tests unitarios
pytest math_service/tests/ -m unit -v
# Con cobertura
pytest math_service/tests/ --cov=math_service --cov-report=term-missing
# Un archivo concreto
pytest math_service/tests/test_clustering.py -v
Tests de integración (requieren servicios arrancados)
pytest tests/ -m integration -k "math" -v
Ver informe HTML de cobertura
pytest math_service/tests/ --cov=math_service --cov-report=html
open htmlcov/index.html
Estructura de tests
math_service/tests/
├── __init__.py
├── test_api_faqs.py # Tests de endpoints /faqs con TestClient
├── test_api_topics.py # Tests de endpoints /topics con TestClient
├── test_clustering.py # Tests unitarios de clustering.py y fcm.py
└── test_faq.py # Tests de FAQService (con MongoDB mockeado)
Patrón de fixtures
Los tests usan mongomock para no necesitar MongoDB real:
import mongomock
import pytest
from fastapi.testclient import TestClient
from math_service.api import app
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def mock_mongo():
with mongomock.patch(servers=(("localhost", 27017),)):
yield
Ejemplo de test de endpoint
def test_generate_faqs_no_questions(client, mock_mongo):
response = client.post("/faqs/generate", json={"subject": "asignatura_sin_preguntas"})
assert response.status_code == 500
assert "No questions found" in response.json()["detail"]
Ejemplo de test unitario de clustering
import numpy as np
from math_service.services.clustering import get_optimal_k, get_closest_to_centroid
def test_get_optimal_k_returns_valid_k():
X = np.random.rand(50, 10)
k = get_optimal_k(X, max_k=8)
assert 2 <= k <= 8
def test_get_closest_to_centroid():
X = np.array([[1, 0], [1.1, 0.1], [5, 5], [4.9, 5.1]])
labels = np.array([0, 0, 1, 1])
centroids = np.array([[1.05, 0.05], [4.95, 5.05]])
indices = get_closest_to_centroid(X, labels, centroids)
assert len(indices) == 2
Añadir un nuevo endpoint
1. Definir modelos Pydantic
En math_service/models/__init__.py:
class MiNuevoRequest(BaseModel):
subject: str
parametro: int = 10
class MiNuevoResponse(BaseModel):
status: str
resultado: list[str]
2. Implementar la lógica de negocio
En math_service/services/mi_servicio.py:
class MiServicio:
def __init__(self):
self.client = MongoClient(settings.get_mongo_uri())
self.db = self.client[settings.db_name]
def close(self):
self.client.close()
def procesar(self, subject: str, parametro: int) -> dict:
# Lógica aquí
return {"status": "success", "resultado": []}
3. Crear el router
En math_service/routes/mi_router.py o añadir al router existente:
from fastapi import APIRouter
from math_service.models import MiNuevoRequest, MiNuevoResponse
from math_service.services.mi_servicio import MiServicio
router = APIRouter(prefix="/mi-recurso", tags=["mi-recurso"])
@router.post("/accion", response_model=MiNuevoResponse)
def mi_accion(request: MiNuevoRequest) -> MiNuevoResponse:
service = MiServicio()
try:
result = service.procesar(request.subject, request.parametro)
return MiNuevoResponse(**result)
finally:
service.close()
4. Registrar el router en api.py
from math_service.routes.mi_router import router as mi_router
app.include_router(mi_router)
5. Escribir tests
# math_service/tests/test_api_mi_recurso.py
def test_mi_accion(client, mock_mongo):
response = client.post("/mi-recurso/accion", json={"subject": "iv"})
assert response.status_code == 200
Linting y formateo
El proyecto usa ruff, black e isort (configurados en pyproject.toml):
# Verificar errores de estilo
ruff check math_service/
# Formatear código
black math_service/
# Ordenar imports
isort math_service/
# Todo a la vez (con pre-commit)
pre-commit run --all-files
Depuración
Logs estructurados
El servicio emite logs JSON. Para desarrollo, puede ser más legible desactivarlos:
LOG_LEVEL=DEBUG python -m math_service
Depurar requests HTTP a Ollama/RAG
Usar httpx con logging habilitado:
import logging
logging.getLogger("httpx").setLevel(logging.DEBUG)
Inspeccionar MongoDB directamente
# Con mongosh
mongosh "mongodb://localhost:27017/tfg_chatbot"
# Listar FAQs
db.faqs.find().pretty()
# Listar extracciones de tópicos
db.topic_results.find({}, {topics: 1, created_at: 1}).pretty()
Probar algoritmos en modo interactivo
# python o ipython desde la raíz del proyecto
from math_investigation.clustering.fcm import FuzzyCMeans
import numpy as np
X = np.random.rand(100, 50)
fcm = FuzzyCMeans(n_clusters=5, random_state=42)
fcm.fit(X)
print(fcm.labels_)
Comandos útiles
# Arrancar el servicio
python -m math_service
# Ejecutar todos los tests con verbose
pytest math_service/tests/ -v
# Generar FAQs para una asignatura de prueba
curl -X POST http://localhost:8083/faqs/generate \
-H "Content-Type: application/json" \
-d '{"subject": "iv", "min_cluster_size": 2}'
# Extraer tópicos
curl -X POST http://localhost:8083/topics/extract \
-H "Content-Type: application/json" \
-d '{"subject": "iv"}'
# Listar FAQs generadas
curl http://localhost:8083/faqs/iv | python3 -m json.tool
# Health check
curl http://localhost:8083/health | python3 -m json.tool
# Ver métricas Prometheus
curl http://localhost:8083/metrics
Integración con el stack completo
Para probar el Math Service integrado con todos los microservicios:
# Arrancar todos los servicios
docker compose up -d
# El Math Service estará en
curl http://localhost:8083/health
Ver deployment.md para detalles de despliegue.