001/* 002 * Copyright (c) 2026 Singular 003 * SPDX-License-Identifier: MIT 004 */ 005 006package ai.singlr.openai; 007 008import ai.singlr.core.common.Strings; 009import ai.singlr.core.model.Model; 010import ai.singlr.core.model.ModelConfig; 011import ai.singlr.core.model.ModelProvider; 012 013/** 014 * ModelProvider implementation for OpenAI's Responses API. 015 * 016 * <p>Supports canonical OpenAI models (GPT-5.4, GPT-4.1, GPT-4o, o3, o4-mini) out of the box. When 017 * {@link ModelConfig#baseUrl()} is set — pointing at Azure OpenAI, an OpenAI-compatible proxy 018 * (LiteLLM, vLLM, Ollama), or Vertex AI — any non-blank {@code modelId} is accepted. The string is 019 * used verbatim as the {@code model} field in the request body, which Azure OpenAI maps to the 020 * deployment name. Context-window and max-output-tokens metadata default to {@code 0} ("unknown") 021 * for unrecognised ids; callers can override output tokens via {@link 022 * ModelConfig.Builder#withMaxOutputTokens(Integer)}. 023 */ 024public class OpenAIProvider implements ModelProvider { 025 026 private static final String PROVIDER_NAME = "openai"; 027 028 @Override 029 public String name() { 030 return PROVIDER_NAME; 031 } 032 033 @Override 034 public Model create(String modelId, ModelConfig config) { 035 var known = OpenAIModelId.fromId(modelId); 036 if (known != null) { 037 return new OpenAIModel(known, config); 038 } 039 if (!Strings.isBlank(config.baseUrl())) { 040 return new OpenAIModel(modelId, config); 041 } 042 throw new IllegalArgumentException( 043 "Unsupported model: " 044 + modelId 045 + ". Set ModelConfig.baseUrl for custom endpoints (Azure, proxy, Vertex)."); 046 } 047 048 @Override 049 public boolean supports(String modelId) { 050 return OpenAIModelId.isSupported(modelId); 051 } 052}