Para las preguntas exactas de Tienda Luna —stock A-100, listar, ficha A-100— el modelo es innecesario: el parser determinista ya entiende los comandos. El adaptador opcional de modelo.py enseña otra posibilidad: un modelo puede seleccionar la herramienta de lectura consultar_catalogo, mientras catalogo_core.py valida, lee y redacta los hechos.
Objetivo: justificar cuándo el modelo sobra y observar, con un transporte falso, cómo se conserva la fuente local de verdad.
Qué necesita el lector
Necesitas el kit y comprender el contrato del taller 2. No necesitas instalar una librería ni una clave para esta lección. El modelo puede ser útil si las personas preguntan con variantes lingüísticas («¿queda algo del teclado?») o si necesitas mapear lenguaje natural a una consulta conocida. No hace falta para consultar los números guardados ni ejecutar las pruebas offline. Añadirlo tampoco convierte este kit en un agente autónomo.
El adaptador real solo crea transporte cuando están disponibles OPENAI_MODEL y OPENAI_API_KEY. Eso puede implicar red y cargos según tu cuenta. No pongas claves en el MDX, Git, URLs ni historial.
Ejercicio: comparar comandos y lenguaje libre
python3 -c 'from catalogo_core import respond; print(respond("¿qué hay?")["text"])'
# No hay coincidencias literales para las palabras «qué» y «hay»:
# No encontré coincidencias para «¿qué hay?» en este catálogo demo.
python3 -c 'from catalogo_core import respond; print(respond("listar")["text"])'
# «listar» sí es un comando exacto y enumera los seis artículos.
La primera consulta no demuestra comprensión de una pregunta humana: el núcleo busca literalmente todos los términos y devuelve not_found. Usa listar cuando quieres el comando que el contrato conoce. La salida sigue siendo local. --llm en Telegram/Kapso es opcional y no cambia la autoridad del núcleo. Si el modelo no está configurado, el adaptador vuelve al núcleo determinista.
unset OPENAI_MODEL OPENAI_API_KEY
python3 - <<'PY'
import modelo
r = modelo.respond_with_model("stock A-100")
assert r["status"] == "ok" and r["items"] == ["A-100"]
assert any(step.get("reason") == "model_not_configured" for step in r["trace"])
print("Fallback local OK: sin proveedor, stock A-100 sigue siendo determinista")
PY
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest test_modelo.ModelAdapterTests.test_request_schema_is_strict_and_no_network_is_needed -v
Transporte falso: qué valida de verdad
Las pruebas del kit inyectan un transporte falso que devuelve respuestas JSON preescritas. Sirve para verificar el contrato de la integración: una sola tool permitida, argumentos estrictos, límites y fallback. No demuestra inteligencia del modelo, calidad de lenguaje ni disponibilidad de OpenAI. Tampoco es una prueba de llamada real.
La secuencia conceptual es:
- El adaptador recibe la pregunta.
- El modelo (si lo autorizas) propone
consultar_catalogoconquery. - El núcleo ejecuta
respondde forma local. - Solo los resultados validados por el núcleo llegan a la respuesta.
Un texto libre del modelo que diga «hay 999 unidades» no sustituye el resultado de la herramienta. Si la llamada es inválida o falla, la respuesta local conserva el contrato.
Resultado observable
Puedes justificar cuándo el modelo sobra, cuándo podría ayudar con variantes de idioma y qué verifica un transporte falso. La ruta real de proveedor, sus costes, latencia y disponibilidad no se probaron aquí. No uses esta lección para afirmar que hay una sincronización o una respuesta de producción.
Error habitual y recuperación
Si la traza muestra model_not_configured, estás usando el fallback local, no un modelo. Para este ejercicio es lo esperado: no lo «arregles» poniendo una clave en el archivo. Si usas una pregunta libre y obtienes not_found, prueba listar o ficha A-100; distingue el límite del parser de un fallo de red. Una prueba con transporte falso que pasa no cambia esos límites.