Saltar al contenido principal
Utiliza este patrón cuando quieras que una respuesta divida el trabajo en tareas más pequeñas. Este patrón implementa un bucle sincrónico de delegación de respuestas hijas. Una respuesta principal llama a una herramienta de función que tu aplicación resuelve ejecutando otra solicitud de Responses. Uno o varios resultados hijos se devuelven al padre como function_call_output, y el padre continúa la generación. Una respuesta hija a veces se llama subagente. En esta guía, es simplemente otra solicitud de Responses que realiza una tarea enfocada. Respuesta principal -> function_call spawn_subagent -> tu aplicación ejecuta una o más respuestas hijas -> resultados hijos devueltos como function_call_output -> la respuesta principal reanuda la generación

Cómo funciona el bucle

En este patrón la respuesta principal se pausa cada vez que llama a spawn_subagent. Tu aplicación ejecuta las tareas delegadas y luego reanuda la respuesta principal con los resultados.
  1. Crea una respuesta principal con una herramienta de función spawn_subagent.
  2. Cuando el modelo llama a la herramienta, analiza los argumentos de cada llamada a la herramienta.
  3. Ejecuta una o más solicitudes Responses hijas para realizar las tareas delegadas.
  4. Espera a que todas las respuestas hijas finalicen.
  5. Devuelve cada resultado hijo como function_call_output usando el call_id correspondiente.
  6. Reanuda la respuesta principal con previous_response_id.
  7. Repite hasta que el padre produzca un message normal de asistente.

Define una herramienta para delegación

Mantén la herramienta enfocada. Pasa solo los campos que la respuesta hija necesita.
{
  "type": "function",
  "name": "spawn_subagent",
  "description": "Delegar una tarea enfocada a una respuesta hija y devolver el resultado.",
  "strict": true,
  "parameters": {
    "type": "object",
    "properties": {
      "task": {
        "type": "string",
        "description": "La tarea para la respuesta hija."
      },
      "instructions": {
        "type": "string",
        "description": "Instrucciones opcionales para la respuesta hija."
      },
      "model": {
        "type": "string",
        "description": "Modelo opcional para la respuesta hija."
      }
    },
    "required": ["task"],
    "additionalProperties": false
  }
}
Usa tool_choice: "auto" cuando el modelo deba decidir cuándo delegar. Usa tool_choice: "required" cuando cada turno deba pasar por una herramienta.

Receta SDK TypeScript

Este ejemplo utiliza sdk.llm.responses.create tanto para las respuestas principales como para las hijas. Permite que el padre emita múltiples llamadas a spawn_subagent en un solo turno. Tu aplicación ejecuta esas respuestas hijas en paralelo, espera a que todas finalicen y luego reanuda el padre una sola vez con todos los resultados de las herramientas. Mantén cada resultado hijo pequeño para que el padre pueda usarlo en el siguiente turno sin consumir demasiado contexto.
import { SDK } from "@meetkai/mka1";

const sdk = new SDK({
  bearerAuth: process.env.MKA1_API_KEY!,
});

const spawnSubagentTool = {
  type: "function" as const,
  name: "spawn_subagent",
  description: "Delegar una tarea enfocada a una respuesta hija y devolver el resultado.",
  strict: true,
  parameters: {
    type: "object",
    properties: {
      task: { type: "string" },
      instructions: { type: "string" },
      model: { type: "string" },
    },
    required: ["task"],
    additionalProperties: false,
  },
};

type SpawnSubagentArgs = {
  task: string;
  instructions?: string;
  model?: string;
};

async function runChildResponse(args: SpawnSubagentArgs) {
  const child = await sdk.llm.responses.create({
    model: args.model ?? "gpt-5",
    instructions:
      args.instructions ??
      "Eres un asistente especialista. Completa la tarea y devuelve solo el resultado.",
    input: args.task,
    store: true,
  });

  return {
    response_id: child.id,
    output_text: child.outputText,
  };
}

export async function runDelegatingAgent(input: string) {
  let response = await sdk.llm.responses.create({
    model: "gpt-5",
    instructions:
      "Eres un orquestador. Usa spawn_subagent para tareas laterales enfocadas. Puedes llamarlo varias veces en un turno cuando las tareas puedan paralelizarse. Después de que todos los resultados de herramientas regresen, responde directamente al usuario.",
    input,
    tools: [spawnSubagentTool],
    tool_choice: "auto",
    parallel_tool_calls: true,
    max_tool_calls: 8,
    store: true,
  });

  while (true) {
    const toolCalls = response.output.filter(
      (item): item is {
        type: "function_call";
        name: string;
        call_id: string;
        arguments: string;
      } => item.type === "function_call" && item.name === "spawn_subagent",
    );

    if (toolCalls.length === 0) {
      return response.outputText;
    }

    const toolOutputs = await Promise.all(
      toolCalls.map(async (toolCall) => {
        const args = JSON.parse(toolCall.arguments) as SpawnSubagentArgs;
        const childResult = await runChildResponse(args);

        return {
          type: "function_call_output" as const,
          call_id: toolCall.call_id,
          output: JSON.stringify(childResult),
        };
      }),
    );

    response = await sdk.llm.responses.create({
      model: response.model,
      previous_response_id: response.id,
      input: toolOutputs,
      tools: [spawnSubagentTool],
      parallel_tool_calls: true,
      max_tool_calls: 8,
      store: true,
    });
  }
}

Ramificar y esperar a todas las respuestas hijas

Cuando parallel_tool_calls es true, el padre puede emitir varios elementos function_call en un solo turno. Trata ese conjunto de llamadas a herramientas como un lote. Inicia cada respuesta hija, espera a que todas finalicen y solo entonces reanuda el padre. Esto crea una barrera:
  1. La respuesta principal emite muchos elementos function_call.
  2. Tu aplicación inicia muchas respuestas hijas.
  3. Tu aplicación espera a que todas las respuestas hijas terminen.
  4. Tu aplicación envía todos los elementos function_call_output en una sola solicitud de seguimiento.
  5. La respuesta principal continúa con el conjunto completo de resultados delegados.
Si reanudas el padre antes con solo parte del lote, el modelo continúa sin los resultados faltantes. Eso generalmente hace que la orquestación sea menos predecible. El paso clave de consolidación se ve así:
const toolOutputs = await Promise.all(
  toolCalls.map(async (toolCall) => {
    const args = JSON.parse(toolCall.arguments) as SpawnSubagentArgs;
    const childResult = await runChildResponse(args);

    return {
        type: "function_call_output" as const,
        call_id: toolCall.call_id,
        output: JSON.stringify(childResult),
    };
  }),
);
Si tu aplicación actúa en nombre de un usuario final, envía el mismo valor X-On-Behalf-Of en las solicitudes padre e hijas. Si una respuesta hija puede tardar más, puedes establecer background: true en la solicitud hija y consultar con sdk.llm.responses.get hasta que finalice. Cuando ramifiques a múltiples respuestas hijas, espera hasta que todos los resultados hijos estén disponibles antes de reanudar la respuesta principal.

Solicitud Responses sin procesar para el paso de reanudación

La transferencia crítica es la solicitud de seguimiento. Devuelves cada resultado de herramienta en input y apuntas al turno principal anterior con previous_response_id.
bash
curl https://apigw.mka1.com/api/v1/llm/responses \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --header 'X-On-Behalf-Of: <end-user-id>' \
  --data '{
    "model": "gpt-5",
    "previous_response_id": "resp_parent123",
    "input": [
      {
        "type": "function_call_output",
        "call_id": "call_abc123",
        "output": "{\"response_id\":\"resp_child456\",\"output_text\":\"Investigación completa. Se recomienda un lanzamiento escalonado.\"}"
      },
      {
        "type": "function_call_output",
        "call_id": "call_def456",
        "output": "{\"response_id\":\"resp_child789\",\"output_text\":\"Borrador completo. Titular: Lanzamiento más rápido con menor riesgo.\"}"
      }
    ]
  }'
Si no actúas en nombre de un usuario final, omite X-On-Behalf-Of.

Manejo básico de errores y recuperación

En sistemas reales, este bucle fallará a veces. Los fallos comunes incluyen argumentos de herramienta mal formados, tiempos de espera en solicitudes hijas, errores 5xx aguas arriba, límites de tasa y salidas de hijos que son demasiado débiles para ser útiles. El patrón más seguro es:
  • Analiza los argumentos de la herramienta de forma defensiva.
  • Reintenta fallos transitorios de solicitudes hijas un pequeño número de veces con retroceso.
  • Devuelve una carga útil de fallo estructurada al padre en lugar de fallar todo el lote cuando un hijo falla.
  • Mantén los resultados hijos pequeños y explícitos para que el padre decida si continuar, reintentar o responder con resultados parciales.
Un enfoque simple es envolver la ejecución hija y siempre devolver un objeto de éxito o de error:
type ChildSuccess = {
  ok: true;
  response_id: string;
  output_text: string;
};

type ChildFailure = {
  ok: false;
  error_code: "invalid_arguments" | "child_request_failed";
  message: string;
};

type ChildResult = ChildSuccess | ChildFailure;

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function runChildResponseWithRecovery(rawArgs: string): Promise<ChildResult> {
  let args: SpawnSubagentArgs;

  try {
    args = JSON.parse(rawArgs) as SpawnSubagentArgs;
    if (!args.task || typeof args.task !== "string") {
      return {
        ok: false,
        error_code: "invalid_arguments",
        message: "spawn_subagent requiere una tarea de tipo string",
      };
    }
  } catch {
    return {
      ok: false,
      error_code: "invalid_arguments",
      message: "No se pudieron analizar los argumentos de spawn_subagent como JSON",
    };
  }

  for (let attempt = 0; attempt < 3; attempt += 1) {
    try {
      const child = await sdk.llm.responses.create({
        model: args.model ?? "gpt-5",
        instructions:
          args.instructions ??
          "Eres un asistente especialista. Completa la tarea y devuelve solo el resultado.",
        input: args.task,
        store: true,
      });

      return {
        ok: true,
        response_id: child.id,
        output_text: child.outputText,
      };
    } catch (error) {
      const isLastAttempt = attempt === 2;
      if (isLastAttempt) {
        return {
          ok: false,
          error_code: "child_request_failed",
          message:
            error instanceof Error ? error.message : "La respuesta hija falló",
        };
      }

      await sleep(500 * (attempt + 1));
    }
  }

  return {
    ok: false,
    error_code: "child_request_failed",
    message: "La respuesta hija falló",
  };
}
Luego, consolida los resultados en el padre incluso si algunos hijos fallan:
const toolOutputs = await Promise.all(
  toolCalls.map(async (toolCall) => {
    const childResult = await runChildResponseWithRecovery(toolCall.arguments);

    return {
      type: "function_call_output" as const,
      call_id: toolCall.call_id,
      output: JSON.stringify(childResult),
    };
  }),
);
Esto permite que el padre vea fallos parciales y continúe razonando. Por ejemplo, el padre puede decidir reintentar con una tarea más acotada, lanzar un subagente de reemplazo o responder con los resultados hijos exitosos disponibles mientras señala la ausencia. Para hijos de larga duración, también puedes combinar esto con background: true más polling. La lógica de recuperación permanece igual: espera a que el hijo alcance un estado terminal, luego devuelve al padre un objeto de éxito o de error estructurado.

Límites prácticos

  • Mantén parallel_tool_calls en true cuando el padre deba poder delegar varias respuestas hijas en un turno.
  • Establece parallel_tool_calls en false solo cuando las respuestas hijas compartan estado o deban ejecutarse en orden.
  • Configura max_tool_calls para que una respuesta principal no pueda hacer bucle indefinidamente.
  • Mantén las salidas hijas compactas para que el padre pueda incorporar el resultado sin consumir demasiado contexto.
  • Mantén store: true mientras construyes el flujo de trabajo para poder inspeccionar respuestas padre e hijas después.
  • Usa la página de elementos de entrada de Responses en la Referencia de API cuando necesites depurar los elementos exactos enviados al modelo.

Ver también

Revisa realizar investigación profunda con subagentes para una implementación concreta orientada a investigación de este patrón. Revisa generar una respuesta para el patrón base de solicitud de Responses. Usa gestionar conversaciones cuando quieras que el flujo de trabajo padre o hijo mantenga estado duradero fuera de una sola cadena de respuestas.