Repository: GoogleCloudPlatform/kubectl-ai
File: gollm/openai.go (Go)

Code before:
	// Check configuration
	configModel := viper.GetString("model")
	if configModel != "" {
		klog.V(1).Infof("Using model from config: %s", configModel)
		return configModel
	}

	// Default model as fallback
	klog.V(2).Info("No model specified, defaulting to gpt-4.1")
	return "gpt-4.1"
}

// NewOpenAIClient creates a new client for interacting with OpenAI.
// It reads the API key and optional endpoint from viper configuration.
// For backward compatibility, it also supports OPENAI_API_KEY and
// OPENAI_ENDPOINT environment variables through viper bindings.
func NewOpenAIClient(ctx context.Context) (*OpenAIClient, error) {
	// Get API key from viper configuration
	// This will also check the OPENAI_API_KEY env var due to the binding in init()
	apiKey := viper.GetString("openai-api-key")
	if apiKey == "" {
		return nil, errors.New("OpenAI API key not found. Set via OPENAI_API_KEY, KUBECTL_AI_OPENAI_API_KEY env var, or openai-api-key in config")
	}

	// Set options for client creation
	options := []option.RequestOption{option.WithAPIKey(apiKey)}

	// Check for custom endpoint or API base URL
	// Endpoint takes precedence over API base if both are defined
	baseURL := viper.GetString("openai-endpoint")
	if baseURL == "" {
		baseURL = viper.GetString("openai-api-base")
	}

	if baseURL != "" {
		klog.Infof("Using custom OpenAI base URL: %s", baseURL)
		options = append(options, option.WithBaseURL(baseURL))
	}

	return &OpenAIClient{
		client: openai.NewClient(options...),
	}, nil
}

// Close cleans up any resources used by the client.
func (c *OpenAIClient) Close() error {
	// No specific cleanup needed for the OpenAI client currently.
	return nil
}

// StartChat starts a new chat session.

Review comment (performance):
My bad, I should have stated this after we merged the cobra integration, but I am reconsidering the decision to integrate viper in `kubectl-ai`. Sorry to kill all the excitement here :(

Input to the CLI programs are like APIs. Once introduced, we have to maintain them forever. I am finding that viper is expanding that surface area too quickly (for ex. all these environment variable for all the flags...) which may become problem for us later.

I am open to introducing multiple ways to configure the program but slowly overtime based on the need.

I am finding viper is introducing too many dependency and complexity so soon in the project with very little value.

@tuannvm I hope you understand. If needed, happy to connect over chat (or call).
  

Code after:

	// Default model as fallback
	klog.V(2).Info("No model specified, defaulting to gpt-4.1")
	return "gpt-4.1"
}

// NewOpenAIClient creates a new client for interacting with OpenAI.
func NewOpenAIClient(ctx context.Context) (*OpenAIClient, error) {
	// Get API key from loaded env var
	apiKey := openAIAPIKey
	if apiKey == "" {
		return nil, errors.New("OpenAI API key not found. Set via OPENAI_API_KEY env var")
	}

	// Set options for client creation
	options := []option.RequestOption{option.WithAPIKey(apiKey)}

	// Check for custom endpoint or API base URL
	baseURL := openAIEndpoint
	if baseURL == "" {
		baseURL = openAIAPIBase
	}

	if baseURL != "" {
		klog.Infof("Using custom OpenAI base URL: %s", baseURL)
		options = append(options, option.WithBaseURL(baseURL))
	}

	return &OpenAIClient{
		client: openai.NewClient(options...),
	}, nil
}

// Close cleans up any resources used by the client.
func (c *OpenAIClient) Close() error {
	// No specific cleanup needed for the OpenAI client currently.
	return nil
}

// StartChat starts a new chat session.
func (c *OpenAIClient) StartChat(systemPrompt, model string) Chat {
	// Get the model to use for this chat
	selectedModel := getOpenAIModel(model)

	klog.V(1).Infof("Starting new OpenAI chat session with model: %s", selectedModel)

	// Initialize history with system prompt if provided
	history := []openai.ChatCompletionMessageParamUnion{}
	if systemPrompt != "" {
		history = append(history, openai.SystemMessage(systemPrompt))
	}