Руководство TypeScript

OpenAI Decisions API на TypeScript

OpenAI не публиковала SDK для своей Decisions API — нечего копировать, client.decisions.create не существует. На этой странице — рабочий, полностью типизированный TypeScript для эндпоинта этого сайта: вызываемой альтернативы OpenAI Decisions API, которая обслуживает decisions-1 и следует тому же паттерну ограниченных решений.

Обновлено

Определите контракт

Типизируйте запрос один раз и используйте везде. model — литеральное объединение, зафиксированный ID не превратится в опечатку. questions — record по вашим ID, каждый типа noul, choice или score.

Типизируйте answers как дискриминированное объединение по type — именно это делает обработку ответа безопасной: у ответа noul есть noul, у choice есть choice и probabilities — сужение по type даёт правильные поля.

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 }
}

Выполните вызов

Один POST с Bearer-ключом. satisfies DecisionRequest проверяет тело на этапе компиляции; AbortSignal.timeout не даёт зависшим вызовам остановить конвейер. Сужайте answers по полю type перед чтением.

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)
}

Таймауты и ретраи

429 и 502 стоят короткого ретрая с backoff — неудачные вызовы не оплачиваются. 402 — пустой баланс: пополняйте, а не ретрайте. 422 — ошибка валидации; сообщение называет поле, так что чините тело, а не повторяйте.

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')
}

Оберните вызов в хелпер

Вызывающий код должен видеть функцию «текст на вход — метка на выход», а не детали HTTP. Таймаут, проверку 402 и цикл ретраев один раз поместите внутрь decide() — и все места вызова останутся чистыми.

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'
}

Частые вопросы

Есть ли официальный пример SDK OpenAI для Decisions API?

Нет. OpenAI не опубликовала ни методов SDK, ни схемы запроса для Decisions API — любой код с client.decisions.create выдуман. Эта страница использует чистый HTTP.

Нужен ли генератор кода для типов?

Нет — интерфейсы на этой странице покрывают весь контракт. Скопируйте их в проект; они достаточно малы для аудита и версионируются вместе с кодом.

Как отправить структурированный контекст?

state принимает JSON-объект или массив, а не только строку — передавайте dict прямо в json-теле, модель прочитает его как контекст.

Запустите решение из браузера

Без настройки — выполните настоящий вызов в песочнице с 1 бесплатным кредитом для новых посетителей.