Guía de TypeScript

OpenAI Decisions API en TypeScript

OpenAI no ha publicado un SDK para su Decisions API — no hay ningún client.decisions.create que copiar. Esta página muestra TypeScript funcional y totalmente tipado para el endpoint de este sitio — una alternativa llamable a la OpenAI Decisions API que sirve decisions-1 y sigue el mismo patrón de decisión restringida.

Actualizado

Define el contrato

Tipa la petición una vez y reutilízala en todas partes. model es una unión de literales, así que el id fijado nunca se convierte en un typo. questions es un record indexado por tus ids, cada uno de tipo noul, choice o score.

Tipa las respuestas como una unión discriminada por type — eso es lo que hace seguro el manejo de la respuesta: una respuesta noul tiene noul, una choice tiene choice y probabilities — al estrechar por type obtienes los campos correctos.

TypeScript

interface DecisionQuestion {
  type: 'noul' | 'choice' | 'score'
  instructions: string
  criteria?: Record<string, string> | string[]
}

interface DecisionRequest {
  model: 'decisions-1' | 'decisions-latest'
  state: string | Record<string, unknown> | unknown[]
  questions: Record<string, DecisionQuestion>
}

interface DecisionAnswers {
  [questionId: string]:
    | { type: 'noul'; noul: number }
    | { type: 'choice'; choice: string; probabilities: Record<string, number>; confidence: number }
    | { type: 'score'; score: number; legend: string[]; probabilities: Record<string, number>; confidence: number }
}

Haz la llamada

Un solo POST con una clave Bearer. satisfies DecisionRequest comprueba el cuerpo en tiempo de compilación; AbortSignal.timeout evita que llamadas colgadas atasquen el pipeline. Estrecha answers por el campo type antes de leerlas.

TypeScript

const res = await fetch('https://decisions-api.net/api/v1/decisions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.DECISIONS_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(request satisfies DecisionRequest),
  signal: AbortSignal.timeout(30000),
})
const { answers } = (await res.json()) as { answers: DecisionAnswers }

const team = answers.team
if (team.type === 'choice' && team.confidence >= 0.7) {
  routeTo(team.choice)
}

Timeouts y reintentos

429 y 502 merecen un reintento corto con backoff — las llamadas fallidas no se cobran. 402 significa saldo vacío: recarga, no reintentes. 422 es un error de validación; el mensaje nombra el campo, así que corrige el cuerpo en lugar de reintentar.

TypeScript

async function decide(body: DecisionRequest, attempts = 3): Promise<DecisionAnswers> {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch('https://decisions-api.net/api/v1/decisions', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.DECISIONS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(30000),
    })
    if (res.status === 429 || res.status === 502) {
      await new Promise(r => setTimeout(r, 2 ** i * 1000))
      continue
    }
    if (res.status === 402) throw new Error('out of credits')
    if (!res.ok) throw new Error(`decision call failed: ${res.status}`)
    return (await res.json() as { answers: DecisionAnswers }).answers
  }
  throw new Error('decision call failed')
}

Envuelve la llamada en un helper

Quien llama debería ver una función que toma texto y devuelve una etiqueta — no detalles HTTP. Pon el timeout, la comprobación de 402 y el bucle de reintentos dentro de decide() una sola vez y cada punto de llamada queda limpio.

TypeScript

// Keep the call behind one typed function — timeouts, 402s,
// and retries live inside it, not at every call site.
export async function routeTicket(text: string): Promise<string> {
  const answers = await decide({
    model: 'decisions-1',
    state: text,
    questions: {
      team: {
        type: 'choice',
        instructions: 'Which team should own this ticket?',
        criteria: {
          payments: 'Checkout or billing.',
          frontend: 'Rendering or browser behavior.',
          account: 'Login or permissions.',
        },
      },
    },
  })
  const team = answers.team
  return team.type === 'choice' && team.confidence >= 0.7 ? team.choice : 'triage'
}

Preguntas frecuentes

¿Hay un ejemplo oficial del SDK de OpenAI para la Decisions API?

No. OpenAI no ha publicado métodos de SDK ni un esquema de petición para su Decisions API — cualquier cosa con client.decisions.create es inventada. Esta página usa HTTP plano.

¿Necesito un generador de código para los tipos?

No — las interfaces de esta página cubren todo el contrato. Cópialas a tu proyecto; son lo bastante pequeñas para auditar y versionables con tu código.

¿Cómo envío contexto estructurado?

state acepta un objeto JSON o un array, no solo una cadena — pasa dicts directamente en el cuerpo json y el modelo los lee como contexto.

Ejecuta una decisión desde el navegador

Sin configuración — ejecuta una llamada real en el playground con 1 crédito gratis para visitantes nuevos.