StackPractices
beginner Por Mathias Paulenko

Chatbot con OpenAI Assistants API: Build, Coste y Deploy

Cómo crear un chatbot de IA usando la OpenAI Assistants API con function calling y file search.

Temas: ai

Visión General

La OpenAI Assistants API permite crear chatbots con estado sin que tú escribas el historial de la conversación, la recuperación de archivos ni el bucle de ejecución de herramientas. Definas un asistente una vez, luego creas un thread para cada conversación y dejas que la API maneje el orden de mensajes, las llamadas a herramientas integradas y el function calling.

Aviso de deprecación: OpenAI deprecó la Assistants API en agosto de 2025. Cerrará el 26 de agosto de 2026. OpenAI recomienda la Responses API o el Agents SDK para proyectos nuevos. Usa esta receta para mantener integraciones existentes o comparar enfoques; no inicies chatbots nuevos en producción con la Assistants API sin un plan de migración. Recursos relacionados: Generar Imágenes Programáticamente con Modelos de IA y Análisis de Sentimiento con Python y NLTK. Ver también Patrón LLM Fallback.

Cuándo Usar

Usa la OpenAI Assistants API cuando:

  • Necesitas un chatbot con memoria persistente entre turnos y sesiones. Para una alternativa sin estado, consulta Prompt Engineering.
  • Quieres file_search o code_interpreter integrados sin montar un pipeline RAG aparte. Para un RAG a medida, consulta RAG Pipeline.
  • Quieres que el asistente llame funciones de tu backend para obtener datos, reservar citas o activar flujos de trabajo. Para más patrones de agentes, consulta AI Agents Tool Use.
  • Ya estás en el ecosistema OpenAI y aceptas una API gestionada, con proveedor único y una ruta de migración explícita.

No la uses cuando:

  • Necesitas respuestas de baja latencia en tiempo real. Los run de Assistants son asíncronos y normalmente requieren polling o streaming.
  • Quieres evitar el lock-in con OpenAI. Revisa LangChain Agents o las variantes de modelos locales más abajo.
  • Construyes un proyecto nuevo desde mediados de 2025 en adelante. Prefiere la Responses API.

Solución

Los siguientes ejemplos construyen el mismo bot de soporte en tres lenguajes. El bot puede buscar en archivos subidos y llamar a la función get_order_status.

Python

import json
import time
from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY")

assistant = client.beta.assistants.create(
    name="Support Bot",
    instructions="""You are a support agent. Answer questions from the user's knowledge base.
If you need order data, call get_order_status. Use only the data provided.""",
    model="gpt-4o-mini",
    tools=[
        {"type": "file_search"},
        {
            "type": "function",
            "function": {
                "name": "get_order_status",
                "description": "Get the status of a customer order",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "order_id": {"type": "string"}
                    },
                    "required": ["order_id"]
                }
            }
        }
    ],
    tool_resources={
        "file_search": {"vector_store_ids": ["vs_..."]}
    }
)

thread = client.beta.threads.create()
client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="What is the status of order ORD-9981?"
)

run = client.beta.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id
)

while run.status in ("queued", "in_progress", "requires_action"):
    time.sleep(1)
    run = client.beta.threads.runs.retrieve(
        thread_id=thread.id,
        run_id=run.id
    )

    if run.status == "requires_action":
        outputs = []
        for tool_call in run.required_action.submit_tool_outputs.tool_calls:
            if tool_call.function.name == "get_order_status":
                args = json.loads(tool_call.function.arguments)
                result = f"Order {args['order_id']} is shipped and arriving tomorrow."
                outputs.append({"tool_call_id": tool_call.id, "output": result})

        client.beta.threads.runs.submit_tool_outputs(
            thread_id=thread.id,
            run_id=run.id,
            tool_outputs=outputs
        )

messages = client.beta.threads.messages.list(
    thread_id=thread.id,
    order="desc",
    limit=1
)
print(messages.data[0].content[0].text.value)

JavaScript

import OpenAI from 'openai';

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function runChatbot() {
  const assistant = await client.beta.assistants.create({
    name: 'Support Bot',
    instructions: 'You are a support agent. Answer from the knowledge base. If you need order data, call get_order_status.',
    model: 'gpt-4o-mini',
    tools: [
      { type: 'file_search' },
      {
        type: 'function',
        function: {
          name: 'get_order_status',
          description: 'Get the status of a customer order',
          parameters: {
            type: 'object',
            properties: { order_id: { type: 'string' } },
            required: ['order_id']
          }
        }
      }
    ],
    tool_resources: {
      file_search: { vector_store_ids: ['vs_...'] }
    }
  });

  const thread = await client.beta.threads.create();
  await client.beta.threads.messages.create(thread.id, {
    role: 'user',
    content: 'What is the status of order ORD-9981?'
  });

  let run = await client.beta.threads.runs.create(thread.id, {
    assistant_id: assistant.id
  });

  while (['queued', 'in_progress', 'requires_action'].includes(run.status)) {
    await new Promise(r => setTimeout(r, 1000));
    run = await client.beta.threads.runs.retrieve(run.id, { thread_id: thread.id });

    if (run.status === 'requires_action') {
      const outputs = run.required_action.submit_tool_outputs.tool_calls.map(tc => {
        if (tc.function.name === 'get_order_status') {
          const args = JSON.parse(tc.function.arguments);
          return {
            tool_call_id: tc.id,
            output: `Order ${args.order_id} is shipped and arriving tomorrow.`
          };
        }
        return { tool_call_id: tc.id, output: '{}' };
      });

      run = await client.beta.threads.runs.submitToolOutputs(run.id, {
        thread_id: thread.id,
        tool_outputs: outputs
      });
    }
  }

  const messages = await client.beta.threads.messages.list(thread.id, {
    limit: 1,
    order: 'desc'
  });
  console.log(messages.data[0].content[0].text.value);
}

runChatbot().catch(console.error);

Java

import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.FunctionDefinition;
import com.openai.models.FunctionParameters;
import com.openai.models.beta.assistants.AssistantCreateParams;
import com.openai.models.beta.assistants.FileSearchTool;
import com.openai.models.beta.assistants.FunctionTool;

public class SupportAssistant {
    public static void main(String[] args) {
        OpenAIClient client = OpenAIOkHttpClient.fromEnv();

        FunctionDefinition getOrderStatus = FunctionDefinition.builder()
            .name("get_order_status")
            .description("Get the status of a customer order")
            .parameters(FunctionParameters.builder()
                .putAllAdditionalProperties(Map.of(
                    "type", JsonValue.from("object"),
                    "properties", JsonValue.from(Map.of(
                        "order_id", Map.of("type", "string")
                    )),
                    "required", JsonValue.from(List.of("order_id"))
                ))
                .build())
            .build();

        AssistantCreateParams params = AssistantCreateParams.builder()
            .name("Support Bot")
            .instructions("You are a support agent. Answer from the knowledge base. " +
                          "If you need order data, call get_order_status.")
            .model("gpt-4o-mini")
            .addTool(FileSearchTool.builder().build())
            .addTool(FunctionTool.builder().function(getOrderStatus).build())
            .toolResources(AssistantCreateParams.ToolResources.builder()
                .fileSearch(AssistantCreateParams.ToolResources.FileSearch.builder()
                    .vectorStoreIds(List.of("vs_..."))
                    .build())
                .build())
            .build();

        client.beta().assistants().create(params);
        // Thread, message, run, and tool-output submission follow the same pattern
        // using ThreadCreateParams, MessageCreateParams, RunCreateParams, etc.
    }
}

Explicación

La Assistants API abstrae tres piezas de complejidad:

  • Asistente: una configuración persistente de modelo, instrucciones, además de herramientas. Lo creas una vez y lo reutilizas para cada conversación.
  • Thread: un contenedor de conversación. OpenAI almacena el historial de mensajes, por lo que no necesitas una base de datos para el log de chat.
  • Run: una pasada de ejecución. El modelo decide si responde directamente, llama una función o invoca una herramienta integrada. Tu código ejecuta la función y envía el resultado.

Cuando run.status es requires_action, el run se pausa hasta que se envíen los tool outputs solicitados. Después de llamar submit_tool_outputs (Python) o submitToolOutputs (Node), el run se reanuda y el asistente produce un mensaje final.

flowchart diagram: Crear thread

Compromisos

  • Conveniencia vs. control: Assistants gestiona estado y llamadas a herramientas, pero pierdes control sobre la construcción exacta del prompt y el uso de la ventana de contexto.
  • Latencia: cada run es asíncrono. Requiere polling o streaming, lo que añade idas y vueltas respecto a una sola llamada a Chat Completions.
  • Costo: pagas tokens de entrada/salida más el uso de herramientas. file_search y code_interpreter añaden overhead.
  • Lock-in y deprecación: la API es específica de OpenAI y está deprecada. Los proyectos nuevos deberían evaluar la Responses API.

Variantes

TecnologíaEnfoqueNotas
OpenAI Assistants APIThreads con estado + herramientas integradasÚtil para integraciones gestionadas existentes; deprecada para proyectos nuevos
OpenAI Chat Completions APISin estado, historial manualMenor latencia, más control, pero tú gestionas contexto y herramientas
OpenAI Responses APIConversaciones unificadas y bucle de herramientasCamino recomendado por OpenAI para nuevos agentes
Azure OpenAI AssistantsMisma API, cumplimiento empresarialÚtil para redes privadas y requisitos regionales de datos
LangChain AgentsAbstracción a nivel de frameworkCambiar modelos, añadir herramientas propias, pero más boilerplate
Functionary / LLMs localesFunction calling autoalojadoPrivacidad, sin costos de API, pero requiere GPU

Lo que Funciona

  1. Almacena los IDs de thread en tu base de datos, claveados por usuario, para que puedan retomar conversaciones.
  2. Usa file_search y code_interpreter a través de tool_resources, no con el file_ids heredado a nivel superior.
  3. Valida y sanitiza cada argumento de función antes de ejecutarlo.
  4. Define instructions estrictas para limitar el tono, el alcance y cuándo llamar funciones.
  5. Monitorea el uso de tokens por run; file_search y code_interpreter aumentan el costo rápidamente.

Errores Comunes

  1. Filtrar IDs de thread — trátalos como tokens de sesión; limita su acceso a usuarios autenticados.
  2. Ignorar requires_action — los run se quedan pausados para siempre si no envías los tool outputs.
  3. Abusar de file_search — adjuntar vector stores muy grandes aumenta latencia y costo.
  4. No manejar fallos de run — revisa run.status por failed, expired o cancelled.
  5. Asumir respuestas en tiempo real — los run son asíncronos; se requiere polling o streaming.

Solución de Problemas

  • El asistente no llama a la función: ajusta la description de la función y asegúrate de que la intención del usuario sea clara en las instructions.
  • El run se queda in_progress mucho tiempo: implementa un timeout y un mensaje de fallback; no hagas polling infinito.
  • Las salidas del modelo son inconsistentes: usa temperature 0 para tareas deterministas y fija una versión de modelo.
  • Prompt injection filtra contexto: mantén la entrada del usuario separada de las instructions y valida los argumentos de las herramientas.
  • Costos altos de tokens: cachea resultados de búsqueda, resume contexto largo y usa modelos más pequeños para tareas simples.
  • File search devuelve chunks irrelevantes: ajusta tamaño de chunk, además de overlap y filtros de metadata.

Lectura Adicional

Notas de Producción

  • Fija una versión de modelo como gpt-4o-2024-08-06 en lugar de alias, para evitar cambios de comportamiento silenciosos.
  • Suscríbete al changelog de OpenAI para seguir hitos de deprecación de Assistants y paridad de funciones en Responses.
  • Despliega de forma gradual con canary o blue-green para atrapar regresiones en llamadas a herramientas.
  • Configura alertas para tasa de errores, latencia p99 y tasa de fallos de run antes de activar en producción.
  • Documenta el rollback en tu runbook y pruébalo en staging al menos una vez por trimestre.
  • Revisa logs estructurados con correlation IDs para trazar una request desde tu backend hasta el run de OpenAI.

Puntos Clave

  • La Assistants API evita gestionar el estado de la conversación y las llamadas a herramientas, pero está deprecada y tiene fecha de cierre.
  • Usa file_search, code_interpreter y function a través de la estructura v2 actual de tool_resources.
  • Valida cada argumento de función, limita los thread a usuarios y maneja siempre requires_action.
  • Para chatbots nuevos en 2026, evalúa la Responses API o el Agents SDK antes de comprometerte con Assistants.

Errores Comunes en Producción

  • Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
  • Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
  • Codificar valores fijos como el ID del asistente o la versión del modelo en lugar de usar configuración por entorno.
  • Olvidar logging y monitoreo en cada paso del ciclo de run.
  • Desplegar sin plan de rollback ni estrategia de backup probada.
  • Asumir que el ejemplo mínimo escalará sin agregar reintentos, circuit breakers o rate limiting.
  • No documentar qué versión de modelo y configuración de herramientas se usan en producción.
  • Dejar la receta sin cambios cuando evolucionan dependencias, escala o fechas de deprecación.

Preguntas frecuentes

¿Cuál es la diferencia entre Assistants y Chat Completions?

Assistants gestiona el estado del thread, herramientas integradas como file_search y code_interpreter, y el ciclo de function calling. Chat Completions es sin estado: enviás el array completo de mensajes cada vez y gestionás historial, ejecución de herramientas y manejo de archivos vos mismo.

¿Puedo usar mi propio LLM con la Assistants API?

No. La Assistants API solo funciona con modelos de OpenAI. Si necesitás un modelo personalizado, usá LangChain agents o construí una abstracción similar sobre Chat Completions con tu propio backend.

¿Qué cuesta correr esto?

Pagás tokens de entrada y salida del modelo, más el uso de cualquier herramienta. file_search y code_interpreter añaden costo por consulta o sesión. Monitoreá siempre el uso en el dashboard de OpenAI — es fácil quemar tokens con file search si no estás prestando atención.

¿Cuál es la mejor forma de manejar errores de function call?

Capturá la excepción en tu función y devolvé un objeto JSON con campos error y message a la Assistants API vía submit_tool_outputs. El asistente lee el error y puede reintentar, pedir aclaración al usuario o intentar otro enfoque. Sanitizá el mensaje para evitar filtrar detalles internos y establecé un máximo de reintentos para prevenir bucles infinitos.

¿Cuándo debería streamear respuestas desde la Assistants API?

Pasá stream: true al crear un run si necesitás output incremental. La API retorna Server-Sent Events con eventos thread.run.step.delta que contienen texto incremental. Parseá el stream SSE y reenviá los chunks al cliente. Manejá thread.run.completed para señalar el final del stream.

¿Qué pasa con el rate limiting para mi chatbot?

Rastreá requests por usuario con una ventana deslizante en Redis. Definí límites según tu plan, retorná HTTP 429 con header Retry-After cuando se alcance el límite e implementá backoff exponencial ante 429 de OpenAI. Encolá picos de tráfico para procesarlos asincrónicamente.

¿Se puede testear una integración de Assistants API sin pegarle a la API?

Sí. Mockeá el cliente de OpenAI con vi.mock() o unittest.mock.patch. Testeá function calling devolviendo tool outputs predefinidos y aserciones de que el asistente los recibe. Para tests end-to-end, usá un asistente aparte con un modelo más barato como gpt-4o-mini, y grabá respuestas con VCR.py o Polly.js para reproducirlas en CI.

¿Qué pasa cuando una conversación excede la ventana de contexto?

La Assistants API trunca mensajes antiguos automáticamente. Para preservar contexto importante, resumí periódicamente la conversación y almacená el resumen. Para chats con mucho conocimiento, guardá hechos clave en un vector store y recuperálos con file_search en lugar de depender del historial completo del thread.

¿Cómo mantengo tenants aislados con la Assistants API?

Creá un asistente por tenant o incluí el contexto del tenant en las instructions. Scopeá los IDs de thread por tenant y validá que un usuario solo acceda a sus propios threads. Nunca compartas vector stores de file_search entre tenants — es una filtración de datos esperando a pasar.

¿Cuál es mi fallback cuando la API de OpenAI cae?

Implementá un circuit breaker que se abra tras un umbral de fallos. Cuando esté abierto, devolvé una respuesta cacheada o un mensaje de "servicio temporalmente no disponible". Encolá mensajes del usuario con BullMQ o Celery y procesalos cuando la API se recupere. Para caminos críticos, configurá un proveedor de modelos fallback.