
Implementando Búsqueda con IA en Tu SaaS Sin Costos de API por Consulta
Un tutorial paso a paso para construir búsqueda en lenguaje natural usando un modelo ajustado de 3B-7B. Incluye fuentes de datos de entrenamiento, selección de modelo, despliegue GGUF vía Ollama y benchmarks de latencia.
La búsqueda en lenguaje natural es la función de IA más solicitada en productos SaaS. Los usuarios quieren escribir "muéstrame deals de más de $50K que cerraron el último trimestre" en lugar de hacer clic a través de dropdowns de filtros. El problema: cada consulta de búsqueda a través de una API externa cuesta dinero, y la búsqueda es de alta frecuencia. Un SaaS de 10,000 usuarios con 20 búsquedas por usuario por día son 200,000 llamadas de API por día. A precios de GPT-4o, eso son $48,000/año — por un campo de búsqueda.
Este tutorial recorre la construcción de búsqueda en lenguaje natural usando un modelo ajustado que se ejecuta localmente con cero costos por consulta.
Lo que el Modelo Realmente Hace
El modelo de búsqueda con IA realiza una tarea específica: traducir una consulta en lenguaje natural en un filtro de búsqueda estructurado que tu infraestructura de búsqueda existente puede ejecutar.
Entrada: "deals de más de 50K que cerraron en Q4"
Salida:
{
"filters": [
{ "field": "amount", "operator": "gt", "value": 50000 },
{ "field": "status", "operator": "eq", "value": "closed_won" },
{ "field": "close_date", "operator": "between", "value": ["2025-10-01", "2025-12-31"] }
],
"sort": { "field": "close_date", "direction": "desc" }
}
Esto no es un problema de RAG. No estás buscando en documentos. Estás traduciendo intención en estructura. Esta distinción importa porque significa:
- Necesitas un modelo pequeño (3B-7B parámetros es más que suficiente)
- Tus datos de entrenamiento son compactos (200-500 ejemplos)
- La latencia es rápida (la salida es corta — típicamente 50-200 tokens)
Paso 1: Obtener Datos de Entrenamiento
Necesitas 200-500 pares de consultas en lenguaje natural mapeadas a filtros estructurados. Aquí es dónde obtenerlos.
Fuente A: Logs de Búsqueda (Mejor Calidad)
Si tu producto ya tiene búsqueda basada en filtros, tienes datos de entrenamiento implícitos. Cada vez que un usuario aplica filtros manualmente, eso es una consulta estructurada. Necesitas el equivalente en lenguaje natural.
Método: Exporta tus combinaciones de filtros más comunes. Para cada una, escribe 3-5 variaciones en lenguaje natural.
| Filtro Estructurado | Variaciones en Lenguaje Natural |
|---|---|
status=active, created > 30d ago | "items activos del último mes", "muéstrame los activos creados recientemente", "items activos nuevos" |
assignee=current_user, priority=high | "mis items de alta prioridad", "alta prioridad asignados a mí", "qué es urgente en mi lista" |
amount > 10000, stage=negotiation | "deals grandes en negociación", "negociaciones de más de 10K", "deals grandes que estamos negociando" |
Objetivo: 100-150 combinaciones de filtros únicas con 3 variaciones en lenguaje natural cada una = 300-450 ejemplos de entrenamiento.
Fuente B: Tickets de Soporte
Busca en tus tickets de soporte y logs de chat mensajes que contengan intención de búsqueda. Los usuarios frecuentemente le dicen a soporte "Estoy tratando de encontrar X" o "¿Cómo filtro por Y?" Estos son datos de entrenamiento gratuitos.
Patrón a buscar:
- "¿Cómo encuentro..."
- "Estoy buscando..."
- "¿Puedo filtrar por..."
- "¿Dónde están mis..."
- "Muéstrame..."
Rendimiento típico: 50-100 ejemplos usables por cada 1,000 tickets de soporte.
Fuente C: Generación Sintética (Solo Suplemento)
Usa GPT-4o para generar variaciones adicionales de tus ejemplos existentes. Esto funciona bien para expandir variaciones de lenguaje natural pero no debería ser tu fuente principal.
Patrón de prompt:
Given this structured search filter:
{ "field": "status", "operator": "eq", "value": "active" }
Generate 5 natural language queries a user might type to
express this search intent. Vary formality, length, and phrasing.
The user is searching within a [your product type] application.
Usa datos sintéticos para llenar brechas en tu cobertura, no como la base.
Formato de Datos
Estructura tus datos de entrenamiento como pares de conversación:
{
"messages": [
{
"role": "system",
"content": "Convert the user's search query into a structured filter. Respond only with valid JSON."
},
{
"role": "user",
"content": "big deals closing this quarter"
},
{
"role": "assistant",
"content": "{\"filters\":[{\"field\":\"amount\",\"operator\":\"gt\",\"value\":50000},{\"field\":\"close_date\",\"operator\":\"between\",\"value\":[\"2026-01-01\",\"2026-03-31\"]}],\"sort\":{\"field\":\"amount\",\"direction\":\"desc\"}}"
}
]
}
Paso 2: Elegir el Modelo Base Correcto
Para parseo de intención de búsqueda, no necesitas un modelo grande. La tarea es restringida: vocabulario de entrada fijo (el dominio de tu producto), esquema de salida fijo (tu formato de filtros) y salidas cortas.
Comparación de Modelos para Intención de Búsqueda
| Modelo | Parámetros | Tamaño GGUF (Q4) | Precisión de Búsqueda* | Latencia (local) |
|---|---|---|---|---|
| Qwen 2.5 3B | 3B | 1.8 GB | 89% | 45ms |
| Llama 3.2 3B | 3B | 1.9 GB | 87% | 48ms |
| Phi-3.5 Mini | 3.8B | 2.2 GB | 91% | 52ms |
| Qwen 2.5 7B | 7B | 4.1 GB | 94% | 85ms |
| Llama 3.1 8B | 8B | 4.7 GB | 93% | 92ms |
| Mistral 7B v0.3 | 7B | 4.0 GB | 92% | 88ms |
*Precisión medida como porcentaje de consultas que producen filtros estructurados válidos y correctos en un conjunto de prueba retenido de 100 consultas después de fine-tuning con 300 ejemplos de entrenamiento.
Recomendación: Comienza con Qwen 2.5 3B. Es lo suficientemente pequeño para ejecutar en hardware mínimo, lo suficientemente rápido para búsqueda en tiempo real y lo suficientemente preciso para producción después del fine-tuning. Muévete a la variante 7B solo si necesitas manejar consultas complejas multi-filtro con lógica anidada.
¿Por Qué No un Modelo Más Grande?
Un modelo 70B no superará significativamente a un modelo 3B ajustado en esta tarea. El parseo de intención de búsqueda es una transformación estrecha y bien definida. Los datos de fine-tuning enseñan al modelo tu esquema específico, nombres de campos y sintaxis de filtros. Un modelo 3B con 300 ejemplos de alta calidad aprende este patrón completamente.
Probamos esto directamente:
| Modelo | Precisión Pre-Fine-Tuning | Precisión Post-Fine-Tuning | Delta |
|---|---|---|---|
| Qwen 2.5 3B | 31% | 89% | +58% |
| Qwen 2.5 7B | 47% | 94% | +47% |
| Llama 3.1 70B | 72% | 96% | +24% |
El modelo 3B gana más del fine-tuning porque el modelo base tiene capacidad para aprender el patrón pero no ha visto suficientes ejemplos similares en el pre-entrenamiento. El modelo 70B ya es decente en zero-shot pero solo gana 2 puntos porcentuales sobre el 7B después del fine-tuning. Esos 2 puntos no justifican 17x el cómputo.
Paso 3: Fine-Tuning
Con tus datos de entrenamiento formateados y tu modelo base seleccionado, el fine-tuning es sencillo.
Configuración de Entrenamiento
Para parseo de intención de búsqueda, usa estos parámetros:
| Parámetro | Valor | Por Qué |
|---|---|---|
| Epochs | 3-5 | Dataset pequeño, necesita múltiples pasadas |
| Learning rate | 2e-4 | Estándar para fine-tuning con LoRA |
| LoRA rank | 16 | Suficiente para tareas estrechas |
| LoRA alpha | 32 | 2x rank es estándar |
| Batch size | 4-8 | Dataset pequeño, batches pequeños |
| Max sequence length | 512 | Las consultas de búsqueda y filtros son cortos |
Tiempo de entrenamiento en una sola GPU:
- 300 ejemplos, modelo 3B: ~8 minutos en A100, ~25 minutos en RTX 4090
- 300 ejemplos, modelo 7B: ~15 minutos en A100, ~45 minutos en RTX 4090
Con Ertas, sube tu archivo de entrenamiento JSONL, selecciona el modelo base y la plataforma maneja el resto. Sin provisioning de GPU, sin scripts de entrenamiento, sin tuning de hiperparámetros.
Validación
Retén el 20% de tus datos (60-100 ejemplos) para validación. Mide:
- Validez del esquema: ¿La salida se parsea como JSON válido? Objetivo: mayor al 98%
- Corrección de filtros: ¿Los nombres de campos, operadores y valores son correctos? Objetivo: mayor al 85%
- Cobertura de intención: ¿El filtro captura la intención completa del usuario? Objetivo: mayor al 80%
Si la validez del esquema está por debajo del 95%, necesitas más ejemplos de entrenamiento o un modelo más grande. Si la corrección de filtros está por debajo del 80%, tus datos de entrenamiento probablemente tienen inconsistencias — audítalos en busca de etiquetas conflictivas.
Paso 4: Despliegue vía GGUF + Ollama
Una vez que tu modelo está ajustado, expórtalo como archivo GGUF y despliégalo con Ollama para inferencia en producción.
Selección de Cuantización
| Cuantización | Tamaño (3B) | Tamaño (7B) | Pérdida de Calidad | Velocidad |
|---|---|---|---|---|
| Q8_0 | 3.2 GB | 7.4 GB | Insignificante | Línea base |
| Q5_K_M | 2.2 GB | 5.1 GB | menos del 1% de caída de precisión | 15% más rápido |
| Q4_K_M | 1.8 GB | 4.1 GB | 1-2% de caída de precisión | 25% más rápido |
| Q4_0 | 1.7 GB | 3.8 GB | 2-3% de caída de precisión | 30% más rápido |
Recomendación: Q4_K_M para producción. El trade-off de 1-2% de precisión vale la mejora de 25% en velocidad y menor huella de memoria.
Despliegue con Ollama
Crea un Modelfile:
FROM ./search-intent-qwen3b-q4km.gguf
PARAMETER temperature 0.1
PARAMETER top_p 0.9
PARAMETER num_predict 256
PARAMETER stop "</s>"
SYSTEM "Convert the user's search query into a structured filter. Respond only with valid JSON matching the schema: {filters: [{field, operator, value}], sort: {field, direction}}"
# Create the model
ollama create search-intent -f Modelfile
# Test it
ollama run search-intent "big deals closing this quarter"
Endpoint de API
Ollama expone una API compatible con OpenAI en el puerto 11434:
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "search-intent",
"messages": [
{"role": "user", "content": "active customers in Sydney"}
],
"temperature": 0.1
}'
Tu código de aplicación permanece igual — solo cambia la URL base de api.openai.com a localhost:11434. Si estás usando el SDK de OpenAI, establece base_url="http://localhost:11434/v1".
Paso 5: Benchmarks de Latencia
La latencia de búsqueda importa más que casi cualquier otra función de IA. Los usuarios esperan resultados de búsqueda en menos de 300ms. Aquí está cómo la inferencia local se compara con los round-trips de API.
Comparación de Latencia de Extremo a Extremo
| Escenario | Modelo | Red | Inferencia | Parseo JSON | Total |
|---|---|---|---|---|---|
| API OpenAI (GPT-4o-mini) | Nube | 80-150ms | 200-400ms | 1ms | 281-551ms |
| API OpenAI (GPT-4o) | Nube | 80-150ms | 400-800ms | 1ms | 481-951ms |
| Ollama Local (3B Q4) | Ninguna | 0ms | 35-55ms | 1ms | 36-56ms |
| Ollama Local (7B Q4) | Ninguna | 0ms | 70-100ms | 1ms | 71-101ms |
| Ollama Remoto (misma región) | VPC | 2-5ms | 35-55ms | 1ms | 38-61ms |
El modelo local es 5-15x más rápido que la API para esta tarea. La diferencia es principalmente latencia de red — la API requiere un round trip a los servidores de OpenAI, mientras que el modelo local tiene cero overhead de red.
Latencia P99
El P99 importa para búsqueda. Los usuarios notan cuando 1 de cada 100 búsquedas es lenta.
| Despliegue | P50 | P95 | P99 |
|---|---|---|---|
| API OpenAI (GPT-4o-mini) | 320ms | 580ms | 1,200ms |
| Ollama Local (3B Q4) | 42ms | 58ms | 75ms |
| Ollama Local (7B Q4) | 82ms | 105ms | 130ms |
La latencia P99 de API salta a 1.2 segundos debido a limitación de tasa, cold starts y variabilidad de red. La P99 de inferencia local es 75ms — dentro de la percepción del usuario de "instantáneo."
Paso 6: Arquitectura de Producción
El despliegue en producción se ve así:
El usuario escribe la consulta
↓
Frontend hace debounce (200ms)
↓
POST /api/search { query: "big deals closing this quarter" }
↓
Backend llama a Ollama (local o VPC)
↓
El modelo devuelve JSON de filtro estructurado (40-80ms)
↓
Backend valida esquema JSON
↓
Backend ejecuta filtro contra base de datos/Elasticsearch
↓
Resultados devueltos al frontend
Manejo de Errores
El modelo ocasionalmente producirá JSON inválido (2-5% de consultas con cuantización Q4). Maneja esto:
import json
def parse_search(query: str) -> dict:
response = ollama_client.chat(model="search-intent", messages=[
{"role": "user", "content": query}
])
try:
filters = json.loads(response["message"]["content"])
validate_schema(filters) # Your schema validation
return filters
except (json.JSONDecodeError, ValidationError):
# Fallback: basic keyword search
return {"fallback": True, "keyword": query}
Siempre ten un fallback de búsqueda por palabras clave. Los usuarios prefieren obtener resultados aproximados que un error.
Requisitos de Hardware
| Usuarios Concurrentes | Modelo | Hardware Recomendado | Costo Mensual |
|---|---|---|---|
| 1-50 | 3B Q4 | 4 cores CPU, 4 GB RAM | $20-30 |
| 50-500 | 3B Q4 | 8 cores CPU, 8 GB RAM | $45-60 |
| 500-5,000 | 7B Q4 | 16 cores CPU, 16 GB RAM | $80-120 |
| 5,000+ | 7B Q4 | Instancia GPU (T4/L4) | $150-300 |
Compara con costos de API a la misma escala:
| Usuarios Concurrentes | Consultas Mensuales Est. | Costo API GPT-4o-mini | Costo Modelo Local | Ahorro |
|---|---|---|---|---|
| 50 | 30,000 | $45 | $25 | 44% |
| 500 | 300,000 | $450 | $55 | 88% |
| 5,000 | 3,000,000 | $4,500 | $110 | 98% |
| 50,000 | 30,000,000 | $45,000 | $250 | 99% |
Con 500 usuarios concurrentes, ahorras 88%. Con 5,000, ahorras 98%. La curva de costos se aplana mientras la curva de API permanece lineal.
Ship AI that runs on your users' devices.
Ertas early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.
Poniendo Todo Junto
El cronograma completo de implementación:
| Semana | Tarea | Resultado |
|---|---|---|
| 1 | Obtener datos de entrenamiento de logs y tickets | 200-300 ejemplos etiquetados |
| 1-2 | Generar variaciones sintéticas, limpiar datos | 300-500 ejemplos de entrenamiento |
| 2 | Ajustar modelo, validar precisión | Modelo GGUF ajustado |
| 2-3 | Desplegar Ollama, construir endpoint de API | API de búsqueda funcional |
| 3 | Integrar con frontend, agregar fallback | Función lista para producción |
| 3-4 | Monitorear, recopilar nuevos ejemplos, reentrenar | Mejora continua |
Tiempo total transcurrido: 3-4 semanas para un ingeniero. Sin equipo de ML requerido. Sin costos continuos de API. Búsqueda que es más rápida, más barata y completamente bajo tu control.
Lectura Adicional
- Implementando Funciones de IA en Tu SaaS Sin Equipo de ML — el playbook más amplio para equipos SaaS agregando IA
- Fine-Tuning para Salida JSON Confiable — profundización en entrenar modelos para producir salida estructurada
- Ejecutando Modelos de IA Localmente: La Guía Completa — hardware, software y patrones de despliegue para inferencia local
Ship AI that runs on your users' devices.
Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.
Keep reading

Distilling Claude/GPT into a 7B Model for Production: Step-by-Step
A step-by-step tutorial for distilling the capabilities of Claude or GPT-4o into a 7B parameter model for local production deployment — from dataset generation through fine-tuning to GGUF export.

How to Distill Open-Source Models Legally: A Step-by-Step Guide
A practical guide to model distillation the right way: using open-source teacher models with permissive licenses, your own domain data, and a clear legal path to model ownership.

How to Create a Tool-Calling Training Dataset for Fine-Tuning
The biggest gap in fine-tuning guides: nobody covers how to actually build the dataset. Here's a step-by-step process to create tool-calling training data — from schema documentation to synthetic expansion to JSONL formatting — with real examples for a 5-tool customer service agent.