001/* 002 * Copyright (c) 2026 Singular 003 * SPDX-License-Identifier: MIT 004 */ 005 006package ai.singlr.openai; 007 008import ai.singlr.core.common.Strings; 009 010/** 011 * Supported OpenAI model identifiers. 012 * 013 * <p>Each enum constant maps to a specific model available through the Responses API. 014 */ 015public enum OpenAIModelId { 016 // maxOutputTokens reflects the documented per-model output ceiling at time of writing — 017 // operators can override per-call via ModelConfig.Builder.withMaxOutputTokens. Reasoning models 018 // (o3, o4-mini) carry higher caps because their output includes reasoning tokens. 019 // 020 // supportsXhighEffort: gpt-5.4 and gpt-5.5 model pages explicitly document `xhigh` as an 021 // accepted value for `reasoning.effort`. The mini/nano variants share their parent's reasoning 022 // family but OpenAI has not published the per-variant value matrix; left false (conservative 023 // clamp to "high") until OpenAI documents otherwise. o-series reasoning models (o3, o4-mini) 024 // pre-date the xhigh tier and are documented to accept low/medium/high only. 025 GPT_5_5("gpt-5.5", 1_050_000, 128_000, true), 026 GPT_5_4("gpt-5.4", 1_050_000, 128_000, true), 027 GPT_5_4_MINI("gpt-5.4-mini", 400_000, 128_000, false), 028 GPT_5_4_NANO("gpt-5.4-nano", 400_000, 128_000, false), 029 GPT_4_1("gpt-4.1", 1_000_000, 32_000, false), 030 GPT_4_1_MINI("gpt-4.1-mini", 1_000_000, 32_000, false), 031 GPT_4_1_NANO("gpt-4.1-nano", 1_000_000, 16_000, false), 032 GPT_4O("gpt-4o", 128_000, 16_384, false), 033 GPT_4O_MINI("gpt-4o-mini", 128_000, 16_384, false), 034 O3("o3", 200_000, 100_000, false), 035 O4_MINI("o4-mini", 200_000, 100_000, false); 036 037 private final String id; 038 private final int contextWindow; 039 private final int maxOutputTokens; 040 private final boolean supportsXhighEffort; 041 042 OpenAIModelId(String id, int contextWindow, int maxOutputTokens, boolean supportsXhighEffort) { 043 this.id = id; 044 this.contextWindow = contextWindow; 045 this.maxOutputTokens = maxOutputTokens; 046 this.supportsXhighEffort = supportsXhighEffort; 047 } 048 049 /** 050 * Whether this model accepts the {@code "xhigh"} value on the {@code reasoning.effort} parameter 051 * of the Responses API. Confirmed against the gpt-5.4 and gpt-5.5 model pages on OpenAI's 052 * developer docs (2026-05-24). Models without confirmed support clamp {@link 053 * ai.singlr.core.model.ThinkingLevel#XHIGH} and {@link ai.singlr.core.model.ThinkingLevel#MAX} to 054 * {@code "high"} so requests stay valid; flip this flag to {@code true} when OpenAI publishes 055 * wider per-model support. 056 * 057 * @return true when {@code reasoning.effort=xhigh} is a valid request value for this model 058 */ 059 public boolean supportsXhighEffort() { 060 return supportsXhighEffort; 061 } 062 063 /** 064 * Returns the API model identifier string. 065 * 066 * @return the model ID used in API requests 067 */ 068 public String id() { 069 return id; 070 } 071 072 /** 073 * Returns the context window size in tokens. 074 * 075 * @return the context window size 076 */ 077 public int contextWindow() { 078 return contextWindow; 079 } 080 081 /** 082 * Returns the maximum output tokens this model can generate in a single response. Used as the 083 * fallback when {@code ModelConfig.maxOutputTokens()} is unset. 084 * 085 * @return the per-model output ceiling 086 */ 087 public int maxOutputTokens() { 088 return maxOutputTokens; 089 } 090 091 /** 092 * Finds an OpenAIModelId by its string identifier. 093 * 094 * @param id the model identifier string 095 * @return the matching OpenAIModelId, or null if not found 096 */ 097 public static OpenAIModelId fromId(String id) { 098 if (Strings.isBlank(id)) { 099 return null; 100 } 101 for (var model : values()) { 102 if (model.id.equals(id)) { 103 return model; 104 } 105 } 106 return null; 107 } 108 109 /** 110 * Checks if the given model ID is supported. 111 * 112 * @param id the model identifier string 113 * @return true if the model is supported 114 */ 115 public static boolean isSupported(String id) { 116 return fromId(id) != null; 117 } 118}