La API de embeddings no genera texto: convierte el texto en una secuencia de números. La búsqueda semántica, RAG, el agrupamiento, la deduplicación y las recomendaciones funcionan con esa secuencia. La integración en sí no es difícil; lo complicado son los detalles de dimensiones, lotes, normalización e indexación. Si se manejan mal, la calidad de la recuperación se deteriora sin que se note de inmediato.

Este artículo usa la interfaz compatible con OpenAI como ejemplo y explica la solicitud y la respuesta, las llamadas en Python y curl, la estrategia de lotes, el almacenamiento de vectores, el flujo de RAG, los errores habituales y la lista de verificación para migrar.

Nota: OpenAI, PostgreSQL y las demás marcas mencionadas son marcas de sus respectivos titulares. Este artículo solo tiene fines técnicos y no implica afiliación con esos proveedores; las interfaces, los modelos y los precios se rigen por la documentación oficial y por las páginas de este sitio.

1. Interfaz y autenticación

La interfaz de embeddings compatible con OpenAI es:

POST https://ai.zhongshangqihang.com/v1/embeddings
Authorization: Bearer sk-tu-clave
Content-Type: application/json

La Base URL debe terminar en /v1; no añadas /embeddings por tu cuenta antes de pasarla al SDK. El SDK agrega la ruta automáticamente según el método, y una ruta duplicada produce un 404.

El cuerpo de la solicitud solo necesita dos campos como mínimo:

{
  "model": "text-embedding-3-small",
  "input": ["Quién paga el costo de envío de una devolución", "Quién asume la logística de una devolución"]
}

input puede ser una sola cadena o un arreglo de cadenas. El arreglo devuelve varios vectores en orden y resulta adecuado para el procesamiento por lotes. Las cadenas vacías suelen rechazarse, así que deben filtrarse en la etapa de limpieza.

2. Campos de solicitud y respuesta

Campo Ubicación Descripción
model Solicitud Obligatorio; el nombre del modelo se rige por la documentación de la plataforma y por precios de modelos
input Solicitud Cadena o arreglo de cadenas; cada elemento no debe superar el límite del modelo
dimensions Solicitud Solo lo admiten algunos modelos; sirve para acortar la dimensión de salida
encoding_format Solicitud Normalmente float; también puede usarse base64 para reducir el tamaño de transmisión
data[].embedding Respuesta Arreglo del vector, en el mismo orden que la entrada
data[].index Respuesta Índice de la entrada dentro del arreglo
usage Respuesta prompt_tokens y total_tokens, útiles para la conciliación

El campo index de la respuesta es importante. Cuando falla un lote y se reintenta, o cuando se combinan solicitudes concurrentes, no supongas que el orden de retorno siempre coincide con la entrada; alinear por index es más seguro.

3. Llamada en Python y similitud

Después de instalar el SDK, el ejemplo mínimo es:

import os
from math import sqrt
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ZSQH_API_KEY"],
    base_url="https://ai.zhongshangqihang.com/v1",
)

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input=["Quién paga el costo de envío de una devolución", "Quién asume la logística de una devolución"],
)
a, b = resp.data[0].embedding, resp.data[1].embedding
cos = sum(x * y for x, y in zip(a, b)) / (
    sqrt(sum(x * x for x in a)) * sqrt(sum(y * y for y in b))
)
print(len(a), round(cos, 4), resp.usage.total_tokens)

Este código valida tres cosas a la vez: que la clave funciona, que la dimensión es la esperada y que la similitud entre dos textos parecidos es razonable. En un proyecto real conviene usar NumPy o las funciones de distancia de la base de datos vectorial; no ejecutes Python puro sobre lotes grandes.

4. Dimensiones, normalización y coherencia del modelo

Las dimensiones de los modelos de embeddings de OpenAI más habituales son:

  • text-embedding-3-small: 1536 dimensiones.
  • text-embedding-3-large: 3072 dimensiones.
  • text-embedding-ada-002: 1536 dimensiones.

El parámetro dimensions permite acortar la salida de la familia text-embedding-3, por ejemplo de 3072 a 1024, para ahorrar almacenamiento y ancho de banda. El recorte pierde parte de la información; para decidir si vale la pena, usa tu propio conjunto de evaluación y no te guíes solo por el número de dimensiones.

La documentación de OpenAI indica que sus embeddings están normalizados a longitud 1, por lo que la similitud del coseno puede calcularse con el producto punto. Pero si usas un modelo propio, un modelo de terceros o truncas dimensiones manualmente, debes volver a aplicar la normalización L2. Lo más seguro es usar la misma lógica de normalización para la escritura y para la consulta, y dejarla documentada en el código.

Hay además una restricción estricta: todos los vectores de la base vectorial deben provenir del mismo modelo, la misma dimensión y el mismo método de normalización. Después de cambiar de modelo, los vectores antiguos no pueden mezclarse con los nuevos, porque la similitud deja de ser comparable.

5. Cómo dividir las solicitudes por lotes

El procesamiento por lotes reduce la sobrecarga HTTP, pero no conviene enviar solicitudes cada vez más grandes. La documentación actual de OpenAI establece como límites que cada entrada no supere aproximadamente 8K tokens y que el total de entradas de una solicitud no supere 300,000 tokens; los límites concretos se rigen por la documentación del proveedor.

En la práctica, divide por presupuesto de tokens y no por una cantidad fija de elementos:

  1. Limpia primero el texto: elimina espacios sobrantes, duplicados y párrafos demasiado largos.
  2. Estima los tokens de cada elemento con un tokenizador y divide antes los textos que excedan el límite.
  3. Agrupa según un presupuesto total de tokens, por ejemplo unas decenas de miles por lote, dejando margen para reintentos.
  4. Usa retroceso exponencial para los errores 429 y 5xx; no reintentes a ciegas los errores 4xx de parámetros.
  5. Registra por lote el modelo, la dimensión, los tokens y el tiempo de ejecución para facilitar la conciliación y la reproducción.

Los reintentos deben ser idempotentes. Puedes usar «hash del texto + nombre del modelo + dimensión» como clave de caché para no pagar dos veces por el mismo texto. Si un lote falla, confirma primero qué index ya se procesaron y completa solo los que faltan.

6. Almacenamiento en la base vectorial

Con pgvector de PostgreSQL, la estructura de la tabla puede simplificarse así:

CREATE TABLE chunks (
  id bigserial PRIMARY KEY,
  content text NOT NULL,
  source text,
  embedding vector(1536)
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);

La consulta usa la distancia del coseno:

SELECT id, content
FROM chunks
ORDER BY embedding <=> $1
LIMIT 5;

Además del vector, conviene almacenar al menos el texto original, la fuente, el inquilino o permiso al que pertenece, la posición del fragmento, el nombre del modelo y la dimensión. Sin esos metadatos, la respuesta no puede citar la fuente ni aplicar filtros de permisos. En producción también hay que prever el tipo de índice, los parámetros de recuperación, las copias de seguridad y las actualizaciones incrementales; no esperes a que crezca el volumen de datos.

7. Orden del flujo de RAG

Un flujo de RAG que funciona suele seguir estos pasos:

  1. Analizar y limpiar los documentos, eliminando encabezados, pies de página y contenido repetido.
  2. Dividir por títulos o párrafos, conservando un pequeño solapamiento entre fragmentos.
  3. Llamar a embeddings para cada fragmento y escribirlos en la base vectorial por lotes.
  4. Cuando el usuario hace una pregunta, vectorizar la pregunta con el mismo modelo.
  5. Recuperar el Top-K por similitud del coseno y filtrar después por permisos y fuente.
  6. Si hace falta, añadir búsqueda por palabras clave para una recuperación híbrida, o un modelo de reordenamiento.
  7. Entregar los resultados recuperados y la pregunta al modelo de chat, pidiéndole que responda con citas.

No hay una respuesta única para el tamaño de los fragmentos. Un punto de partida habitual en documentos en chino es de 300 a 800 caracteres, y luego se ajusta según la calidad de las respuestas. Si el fragmento es demasiado grande, la recuperación pierde precisión; si es demasiado pequeño, el contexto queda incompleto. En lugar de discutir parámetros, crea un conjunto pequeño de evaluación con preguntas fijas y respuestas de referencia, y ejecútalo cada vez que cambies el flujo.

8. Errores habituales y diagnóstico

  • 401 de autenticación: clave incorrecta, eliminada, sin Bearer o variable de entorno vacía.
  • 404 Not Found: falta /v1 en la Base URL o la ruta se concatenó dos veces.
  • model not found: el nombre del modelo es incorrecto o la cuenta no tiene permiso para usarlo.
  • 400 por entrada demasiado larga: un elemento supera el límite del modelo; divídelo antes de enviarlo.
  • 400 por entrada vacía: hay una cadena vacía en el arreglo; fíltrala en la limpieza.
  • 429: se superó el límite de frecuencia o de concurrencia; reduce la concurrencia y aplica retroceso exponencial.
  • Dimensiones inconsistentes: se usaron modelos distintos o valores distintos de dimensions al escribir y al consultar.
  • Similitudes todas en 0 o anormalmente cercanas: revisa la normalización, si tomaste el campo equivocado y si se escribió un vector cero en la base.

Al diagnosticar, imprime primero el nombre del modelo, la cantidad de entradas, la longitud de cada una y la dimensión devuelta. La mayoría de los problemas no es «mala calidad del modelo», sino alguna inconsistencia entre la solicitud y el almacenamiento.

9. Migración y lista de verificación antes de producción

Al migrar desde la API del proveedor u otro gateway a un gateway compatible con OpenAI, normalmente solo cambian dos cosas: base_url y api_key. Pero los embeddings tienen un costo adicional: si cambia el modelo o la dimensión, hay que reconstruir los vectores históricos.

Antes de salir a producción, verifica al menos:

  • Si la nueva plataforma admite el nombre de modelo y la dimensión que usas.
  • Si es necesario revectorizar toda la base y si la ventana de reconstrucción y el costo son aceptables.
  • Si el lado de consulta y el de escritura cambian al mismo tiempo, para evitar mezclar vectores nuevos y antiguos.
  • Si existe un plan de despliegue gradual que permita desviar parte del tráfico y comparar la recuperación.
  • Si el monitoreo cubre la tasa de error, la latencia, el consumo de tokens y la tasa de resultados vacíos.
  • Si hay una ruta de reversión y si las claves y los índices anteriores se conservan un tiempo tras el cambio.

Si solo cambias la Base URL y mantienes el modelo y la dimensión, la migración es mucho más sencilla; si también cambias el modelo, trátalo como un proyecto de reconstrucción de datos y planifica el calendario. Los modelos y precios concretos se rigen por precios de modelos.

Preguntas frecuentes

¿Cuál es la diferencia entre la interfaz de embeddings y Chat Completions?

La de embeddings devuelve un vector de longitud fija para recuperación, agrupamiento y cálculo de similitud; Chat Completions devuelve texto generado. La autenticación suele ser la misma, pero el cuerpo de la solicitud y la estructura de la respuesta son completamente distintos.

¿Cuántos textos se pueden enviar en una sola solicitud?

La interfaz acepta un arreglo de cadenas, pero el límite real es el total de tokens y la longitud de cada elemento. La documentación actual de OpenAI indica un máximo de 300,000 tokens por solicitud y alrededor de 8K tokens por elemento. En la práctica, divide por presupuesto de tokens; no operes justo en el límite.

¿Hay que normalizar los vectores manualmente?

Si la plataforma indica explícitamente que los vectores ya están normalizados, la similitud del coseno puede calcularse con el producto punto. Con modelos propios, truncamiento manual de dimensiones o procedencia incierta, aplica la misma normalización L2 tanto al escribir como al consultar.

Si cambio el modelo de embeddings, ¿sirven los vectores antiguos?

No pueden mezclarse directamente. Los espacios vectoriales de modelos distintos no son comparables, y compartir la dimensión no implica alineación semántica. Tras cambiar de modelo hay que reconstruir el índice y registrar el nombre del modelo y la dimensión en los metadatos.

¿Hay que segmentar el texto chino antes de enviarlo?

Normalmente no. Los modelos de embeddings modernos procesan el texto original directamente, y la segmentación puede perder contexto. Lo que hay que gestionar es la limpieza, la división en fragmentos, la deduplicación y el control de longitud.

Próximos pasos

Si quieres probar primero una solicitud, crea una clave con cuota baja desde la página de registro y adapta el ejemplo de curl o Python con tus propios textos. Para elegir el modelo definitivo, consulta precios de modelos y compara la recuperación con un pequeño lote de textos reales de tu negocio. Los precios y los modelos cambian; se rigen por lo que muestre el sitio en cada momento.

Lecturas relacionadas: