Endpoints compatibles
| Endpoint | Descripción |
|---|---|
/v1/chat/completions | Solicitudes de completación de chat |
/v1/embeddings | Generación de embeddings |
/v1/images/generations | Generación de imágenes |
Ciclo de vida
Un lote pasa por estos estados:validating → in_progress → finalizing → completed
↓ ↓
failed cancelling → cancelled
| Estado | Descripción |
|---|---|
validating | El archivo de entrada está siendo verificado por errores de formato y contenido. |
failed | La validación falló — el archivo de entrada contiene errores. Consulta batch.errors para más detalles. |
in_progress | Las solicitudes están siendo procesadas. |
finalizing | Todas las solicitudes han sido procesadas y se están generando los archivos de salida. |
completed | El lote finalizó. Descarga los resultados desde output_file_id. |
cancelling | Se solicitó una cancelación. Las solicitudes en curso están finalizando. |
cancelled | El lote fue cancelado. Puede haber resultados parciales disponibles. |
expired | El lote no se completó dentro de la ventana de 24 horas. |
Paso 1 — Prepara el archivo de entrada
Crea un archivo JSONL donde cada línea sea una solicitud. Cada línea tiene cuatro campos:| Campo | Tipo | Descripción |
|---|---|---|
custom_id | string | Tu identificador para esta solicitud. Se usa para emparejar la entrada con la salida. Debe ser único dentro del archivo. |
method | string | "POST" — el único método soportado. |
url | string | La ruta del endpoint — debe coincidir con el endpoint que declares al crear el lote. |
body | object | El cuerpo de la solicitud — los mismos parámetros que enviarías al endpoint síncrono. |
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "meetkai:functionary-es-mini", "messages": [{"role": "user", "content": "Summarize the benefits of batch processing in one sentence."}], "max_tokens": 100}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "meetkai:functionary-es-mini", "messages": [{"role": "user", "content": "What is the capital of France?"}], "max_tokens": 100}}
{"custom_id": "request-3", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "meetkai:functionary-es-mini", "messages": [{"role": "user", "content": "Explain embeddings in one paragraph."}], "max_tokens": 100}}
Paso 2 — Sube el archivo de entrada
Sube el archivo JSONL usando la API de Archivos conpurpose: "batch".
mka1 llm files upload \
--file ./batch_input.jsonl \
--purpose batch \
-H 'X-On-Behalf-Of: <end-user-id>'
import { SDK } from '@meetkai/mka1';
const mka1 = new SDK({
bearerAuth: `Bearer ${YOUR_API_KEY}`,
});
const file = await mka1.llm.files.upload({
file: new File([jsonlContent], 'batch_input.jsonl', { type: 'application/jsonl' }),
purpose: 'batch',
});
console.log(file.id); // "file_abc123"
console.log(file.status); // "processed"
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: '<mka1-api-key>',
baseURL: 'https://apigw.mka1.com/api/v1/llm/',
defaultHeaders: { 'X-On-Behalf-Of': '<end-user-id>' },
});
const file = await openai.files.create({
file: new File([jsonlContent], 'batch_input.jsonl', { type: 'application/jsonl' }),
purpose: 'batch',
});
console.log(file.id); // "file_abc123"
console.log(file.status); // "processed"
using System.Text;
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
using MeetKai.MKA1.Types.Requests;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var file = await sdk.Llm.Files.UploadAsync(new UploadFileRequestBody()
{
File = new UploadFileFile()
{
FileName = "batch_input.jsonl",
Content = Encoding.UTF8.GetBytes(jsonlContent),
},
Purpose = UploadFilePurpose.Batch,
});
Console.WriteLine(file.File!.Id); // "file_abc123"
from mka1 import SDK
sdk = SDK(bearer_auth="Bearer YOUR_API_KEY")
file = sdk.llm.files.upload(
file={"file_name": "batch_input.jsonl", "content": open("batch_input.jsonl", "rb")},
purpose="batch",
)
print(file.id) # "file_abc123"
print(file.status) # "processed"
curl https://apigw.mka1.com/api/v1/llm/files \
--request POST \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>' \
--form 'file=@batch_input.jsonl;type=application/jsonl' \
--form 'purpose=batch'
Paso 3 — Crea el lote
Pasa el ID del archivo subido, el endpoint de destino y la ventana de finalización.mka1 llm batches create --body '{
"input_file_id": "file_abc123",
"endpoint": "/v1/chat/completions",
"completion_window": "24h"
}'
const batch = await mka1.llm.batches.create({
inputFileId: file.id,
endpoint: '/v1/chat/completions',
completionWindow: '24h',
});
console.log(batch.id); // "batch_abc123"
console.log(batch.status); // "validating" or "in_progress"
console.log(batch.requestCounts); // { total: 3, completed: 0, failed: 0 }
const batch = await openai.batches.create({
input_file_id: file.id,
endpoint: '/v1/chat/completions',
completion_window: '24h',
});
console.log(batch.id); // "batch_abc123"
console.log(batch.status); // "validating" or "in_progress"
console.log(batch.request_counts); // { total: 3, completed: 0, failed: 0 }
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var batch = await sdk.Llm.Batches.CreateAsync(new CreateBatchRequest()
{
InputFileId = file.File!.Id,
Endpoint = BatchEndpoint.RootV1ChatCompletions,
});
Console.WriteLine(batch.BatchObject!.Id); // "batch_abc123"
Console.WriteLine(batch.BatchObject!.Status); // "validating" or "in_progress"
Console.WriteLine(batch.BatchObject!.RequestCounts); // { Total: 3, Completed: 0, Failed: 0 }
batch = sdk.llm.batches.create(
input_file_id=file.id,
endpoint="/v1/chat/completions",
)
print(batch.id) # "batch_abc123"
print(batch.status) # "validating" or "in_progress"
print(batch.request_counts) # { total: 3, completed: 0, failed: 0 }
curl https://apigw.mka1.com/api/v1/llm/batches \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>' \
--data '{
"input_file_id": "file_abc123",
"endpoint": "/v1/chat/completions",
"completion_window": "24h"
}'
mka1 llm batches create --body '{
"input_file_id": "file_abc123",
"endpoint": "/v1/chat/completions",
"completion_window": "24h",
"metadata": {
"description": "nightly evaluation run",
"run_id": "eval-2026-03-31"
}
}'
const batch = await mka1.llm.batches.create({
inputFileId: file.id,
endpoint: '/v1/chat/completions',
completionWindow: '24h',
metadata: {
description: 'nightly evaluation run',
run_id: 'eval-2026-03-31',
},
});
const batch = await openai.batches.create({
input_file_id: file.id,
endpoint: '/v1/chat/completions',
completion_window: '24h',
metadata: {
description: 'nightly evaluation run',
run_id: 'eval-2026-03-31',
},
});
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var batch = await sdk.Llm.Batches.CreateAsync(new CreateBatchRequest()
{
InputFileId = file.File!.Id,
Endpoint = BatchEndpoint.RootV1ChatCompletions,
Metadata = new Dictionary<string, string>
{
{ "description", "nightly evaluation run" },
{ "run_id", "eval-2026-03-31" },
},
});
batch = sdk.llm.batches.create(
input_file_id=file.id,
endpoint="/v1/chat/completions",
metadata={
"description": "nightly evaluation run",
"run_id": "eval-2026-03-31",
},
)
Paso 4 — Consulta el estado del lote
Consulta el lote hasta que alcance un estado terminal.mka1 llm batches get --batch-id batch_abc123
const batch = await mka1.llm.batches.get({ batchId: 'batch_abc123' });
console.log(batch.status); // "completed"
console.log(batch.requestCounts.completed); // 3
console.log(batch.requestCounts.failed); // 0
console.log(batch.outputFileId); // "file_xyz789"
const batch = await openai.batches.retrieve('batch_abc123');
console.log(batch.status); // "completed"
console.log(batch.request_counts.completed); // 3
console.log(batch.request_counts.failed); // 0
console.log(batch.output_file_id); // "file_xyz789"
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var batch = await sdk.Llm.Batches.GetAsync("batch_abc123");
Console.WriteLine(batch.BatchObject!.Status); // "completed"
Console.WriteLine(batch.BatchObject!.RequestCounts); // { Total: 3, Completed: 3, Failed: 0 }
Console.WriteLine(batch.BatchObject!.OutputFileId); // "file_xyz789"
batch = sdk.llm.batches.get(batch_id="batch_abc123")
print(batch.status) # "completed"
print(batch.request_counts.completed) # 3
print(batch.request_counts.failed) # 0
print(batch.output_file_id) # "file_xyz789"
curl https://apigw.mka1.com/api/v1/llm/batches/batch_abc123 \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>'
# Consulta un lote hasta que alcance un estado terminal usando --jq y un bucle de shell.
BATCH_ID=batch_abc123
while :; do
STATUS=$(mka1 llm batches get --batch-id "$BATCH_ID" --jq '.status' --output-format json)
echo "status: $STATUS"
case "$STATUS" in
completed|failed|cancelled|expired) break ;;
esac
sleep 2
done
async function waitForBatch(batchId: string, timeoutMs = 120_000) {
const terminal = ['completed', 'failed', 'cancelled', 'expired'];
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const batch = await mka1.llm.batches.get({ batchId });
if (terminal.includes(batch.status)) return batch;
await new Promise((r) => setTimeout(r, 2000));
}
throw new Error(`Batch ${batchId} did not complete within ${timeoutMs}ms`);
}
const completed = await waitForBatch(batch.id);
async function waitForBatch(batchId: string, timeoutMs = 120_000) {
const terminal = ['completed', 'failed', 'cancelled', 'expired'];
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const batch = await openai.batches.retrieve(batchId);
if (terminal.includes(batch.status)) return batch;
await new Promise((r) => setTimeout(r, 2000));
}
throw new Error(`Batch ${batchId} did not complete within ${timeoutMs}ms`);
}
const completed = await waitForBatch(batch.id);
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
async Task<BatchObject> WaitForBatch(SDK sdk, string batchId, int timeoutMs = 300_000)
{
var terminal = new HashSet<BatchObjectStatus>
{
BatchObjectStatus.Completed,
BatchObjectStatus.Failed,
BatchObjectStatus.Cancelled,
BatchObjectStatus.Expired,
};
var start = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
while (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - start < timeoutMs)
{
var batch = await sdk.Llm.Batches.GetAsync(batchId);
if (terminal.Contains(batch.BatchObject!.Status))
return batch.BatchObject;
await Task.Delay(2000);
}
throw new TimeoutException($"Batch {batchId} did not complete within {timeoutMs}ms");
}
var completed = await WaitForBatch(sdk, batch.BatchObject!.Id);
Console.WriteLine(completed.Status); // BatchObjectStatus.Completed
import time
def wait_for_batch(sdk, batch_id, timeout_ms=120_000):
terminal = {"completed", "failed", "cancelled", "expired"}
start = time.time() * 1000
while (time.time() * 1000) - start < timeout_ms:
batch = sdk.llm.batches.get(batch_id=batch_id)
if batch.status in terminal:
return batch
time.sleep(2)
raise TimeoutError(f"Batch {batch_id} did not complete within {timeout_ms}ms")
completed = wait_for_batch(sdk, batch.id)
Paso 5 — Descarga los resultados
Una vez que el lote estécompleted, descarga el archivo de salida. Es un archivo JSONL donde cada línea contiene el custom_id que proporcionaste, la respuesta y cualquier error.
# Descarga el archivo de salida JSONL
mka1 llm files content \
--file-id file_xyz789 \
--output-file ./batch_output.jsonl
# Inspecciona los resultados en línea con jq
mka1 llm files content --file-id file_xyz789 \
--jq '"\(.custom_id): status=\(.response.status_code)"'
const stream = await mka1.llm.files.content({ fileId: completed.outputFileId! });
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const text = new TextDecoder().decode(Buffer.concat(chunks));
const results = text
.split('\n')
.filter((line) => line.trim())
.map((line) => JSON.parse(line));
for (const result of results) {
console.log(`${result.custom_id}: status=${result.response.status_code}`);
console.log(` body:`, result.response.body);
}
const content = await openai.files.content(completed.output_file_id!);
const text = await content.text();
const results = text
.split('\n')
.filter((line) => line.trim())
.map((line) => JSON.parse(line));
for (const result of results) {
console.log(`${result.custom_id}: status=${result.response.status_code}`);
console.log(` body:`, result.response.body);
}
using System.Text;
using MeetKai.MKA1;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var content = await sdk.Llm.Files.ContentAsync(completed.OutputFileId!);
var bytes = content.TwoHundredTextPlainBytes
?? content.TwoHundredApplicationJsonlBytes
?? content.TwoHundredApplicationJsonBytes;
var text = Encoding.UTF8.GetString(bytes!);
Console.WriteLine(text); // JSONL con una línea por solicitud
import json
content = sdk.llm.files.content(file_id=completed.output_file_id)
text = content.decode("utf-8")
results = [json.loads(line) for line in text.strip().split("\n") if line.strip()]
for result in results:
print(f"{result['custom_id']}: status={result['response']['status_code']}")
print(f" body: {result['response']['body']}")
curl https://apigw.mka1.com/api/v1/llm/files/file_xyz789/content \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>'
{
"id": "response_abc123",
"custom_id": "request-1",
"response": {
"status_code": 200,
"request_id": "req_abc123",
"body": { "...": "same shape as the synchronous endpoint response" }
},
"error": null
}
response es null y error contiene los detalles:
{
"id": "response_def456",
"custom_id": "request-2",
"response": null,
"error": {
"code": "processing_error",
"message": "The request could not be processed."
}
}
error_file_id que contiene solo las entradas fallidas.
Cancelar un lote
Cancela un lote que aún está en progreso. Las solicitudes que ya se hayan completado permanecen en la salida.mka1 llm batches cancel --batch-id batch_abc123
const cancelled = await mka1.llm.batches.cancel({ batchId: 'batch_abc123' });
console.log(cancelled.status); // "cancelling"
const cancelled = await openai.batches.cancel('batch_abc123');
console.log(cancelled.status); // "cancelling"
using MeetKai.MKA1;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var cancelled = await sdk.Llm.Batches.CancelAsync("batch_abc123");
Console.WriteLine(cancelled.BatchObject!.Status); // "cancelling"
cancelled = sdk.llm.batches.cancel(batch_id="batch_abc123")
print(cancelled.status) # "cancelling"
curl https://apigw.mka1.com/api/v1/llm/batches/batch_abc123/cancel \
--request POST \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>'
cancelling mientras terminan las solicitudes en curso, y luego a cancelled.
Listar lotes
Recupera todos los lotes de la cuenta actual, del más reciente al más antiguo. Soporta paginación.mka1 llm batches list --limit 20
const page = await mka1.llm.batches.list({ limit: 20 });
for (const batch of page.data) {
console.log(`${batch.id}: ${batch.status} (${batch.requestCounts?.completed}/${batch.requestCounts?.total})`);
}
const page = await openai.batches.list({ limit: 20 });
for (const batch of page.data) {
console.log(`${batch.id}: ${batch.status} (${batch.request_counts?.completed}/${batch.request_counts?.total})`);
}
using MeetKai.MKA1;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var page = await sdk.Llm.Batches.ListAsync(limit: 20);
foreach (var batch in page.ListBatchesResponseValue!.Data!)
{
Console.WriteLine($"{batch.Id}: {batch.Status} ({batch.RequestCounts?.Completed}/{batch.RequestCounts?.Total})");
}
page = sdk.llm.batches.list(limit=20)
for batch in page.data:
print(f"{batch.id}: {batch.status} ({batch.request_counts.completed}/{batch.request_counts.total})")
curl 'https://apigw.mka1.com/api/v1/llm/batches?limit=20' \
--header 'Authorization: Bearer <mka1-api-key>' \
--header 'X-On-Behalf-Of: <end-user-id>'
after con un ID de lote para paginar los resultados.
Ejemplo: embeddings por lote
El mismo flujo funciona para embeddings. Cambia elurl en cada línea del JSONL y el endpoint al crear el lote.
{"custom_id": "embed-1", "method": "POST", "url": "/v1/embeddings", "body": {"model": "meetkai:functionary-es-mini", "input": "The quick brown fox"}}
{"custom_id": "embed-2", "method": "POST", "url": "/v1/embeddings", "body": {"model": "meetkai:functionary-es-mini", "input": "jumps over the lazy dog"}}
# Sube el archivo JSONL de embeddings
FILE_ID=$(mka1 llm files upload \
--file ./embed_batch.jsonl \
--purpose batch \
--jq '.id' --output-format json | tr -d '"')
# Crea el lote contra el endpoint de embeddings
mka1 llm batches create --body "{
\"input_file_id\": \"$FILE_ID\",
\"endpoint\": \"/v1/embeddings\",
\"completion_window\": \"24h\"
}"
# Haz polling y descarga los resultados — ver Pasos 4 y 5
const file = await mka1.llm.files.upload({
file: new File([jsonlContent], 'embed_batch.jsonl', { type: 'application/jsonl' }),
purpose: 'batch',
});
const batch = await mka1.llm.batches.create({
inputFileId: file.id,
endpoint: '/v1/embeddings',
completionWindow: '24h',
});
const completed = await waitForBatch(batch.id);
const stream = await mka1.llm.files.content({ fileId: completed.outputFileId! });
// ... lee el stream como se muestra en el Paso 5
const file = await openai.files.create({
file: new File([jsonlContent], 'embed_batch.jsonl', { type: 'application/jsonl' }),
purpose: 'batch',
});
const batch = await openai.batches.create({
input_file_id: file.id,
endpoint: '/v1/embeddings',
completion_window: '24h',
});
const completed = await waitForBatch(batch.id);
const content = await openai.files.content(completed.output_file_id!);
const results = (await content.text()).split('\n').filter(Boolean).map(JSON.parse);
for (const r of results) {
console.log(`${r.custom_id}: ${r.response.body.data[0].embedding.length} dimensions`);
}
using System.Text;
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
using MeetKai.MKA1.Types.Requests;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var file = await sdk.Llm.Files.UploadAsync(new UploadFileRequestBody()
{
File = new UploadFileFile()
{
FileName = "embed_batch.jsonl",
Content = Encoding.UTF8.GetBytes(jsonlContent),
},
Purpose = UploadFilePurpose.Batch,
});
var batch = await sdk.Llm.Batches.CreateAsync(new CreateBatchRequest()
{
InputFileId = file.File!.Id,
Endpoint = BatchEndpoint.RootV1Embeddings,
});
var completed = await WaitForBatch(sdk, batch.BatchObject!.Id);
// Descarga y analiza los resultados como se muestra en el Paso 5
file = sdk.llm.files.upload(
file={"file_name": "embed_batch.jsonl", "content": open("embed_batch.jsonl", "rb")},
purpose="batch",
)
batch = sdk.llm.batches.create(
input_file_id=file.id,
endpoint="/v1/embeddings",
)
completed = wait_for_batch(sdk, batch.id)
# Descarga y analiza los resultados como se muestra en el Paso 5
Errores de validación
Si el archivo de entrada tiene problemas de formato, el lote pasa afailed inmediatamente.
Causas comunes:
- JSON inválido — una línea no es un JSON válido.
- Campos faltantes — falta
custom_id,method,urlobodyen una línea. - Método incorrecto —
methoddebe ser"POST". - URL no coincide — el
urlde una línea no coincide con elendpointdeclarado al crear el lote. custom_idduplicado — cadacustom_iddebe ser único dentro del archivo.
batch.errors.data para ver los mensajes de error específicos y los números de línea.
mka1 llm batches get --batch-id batch_abc123 \
--jq '.errors.data[] | "Line \(.line): [\(.code)] \(.message)"'
const batch = await mka1.llm.batches.get({ batchId: 'batch_abc123' });
if (batch.status === 'failed' && batch.errors) {
for (const err of batch.errors.data ?? []) {
console.log(`Line ${err.line}: [${err.code}] ${err.message}`);
}
}
const batch = await openai.batches.retrieve('batch_abc123');
if (batch.status === 'failed' && batch.errors) {
for (const err of batch.errors.data ?? []) {
console.log(`Line ${err.line}: [${err.code}] ${err.message}`);
}
}
using MeetKai.MKA1;
using MeetKai.MKA1.Types.Components;
var sdk = new SDK(bearerAuth: "Bearer YOUR_API_KEY");
var batch = await sdk.Llm.Batches.GetAsync("batch_abc123");
if (batch.BatchObject!.Status == BatchObjectStatus.Failed && batch.BatchObject.Errors != null)
{
foreach (var err in batch.BatchObject.Errors.Data ?? new List<BatchObjectErrorsData>())
{
Console.WriteLine($"Line {err.Line}: [{err.Code}] {err.Message}");
}
}
batch = sdk.llm.batches.get(batch_id="batch_abc123")
if batch.status == "failed" and batch.errors:
for err in batch.errors.data or []:
print(f"Line {err.line}: [{err.code}] {err.message}")
Ver también
- Generar una respuesta para el patrón síncrono de completaciones de chat.
- Archivos y almacenes vectoriales para la API de Archivos utilizada para subir entradas por lote.