Files
gosec/autofix/openai.go
T
aa2e2fb1bd feat(ai): add OpenAI and custom API provider support (#1424)
* feat(ai): add OpenAI and custom API provider support

- Expand AI provider support to include OpenAI (gpt-4o, gpt-4o-mini) and custom OpenAI-compatible APIs
- Add support for configuring AI API base URL and skipping SSL verification
- Update documentation to list all supported AI providers and clarify configuration options with examples
- Refactor AI client initialization to fallback on OpenAI-compatible API for unknown models
- Add OpenAI client implementation using openai-go library
- Update tests to validate OpenAI-compatible fallback behavior
- Add openai-go dependency to go.mod

Signed-off-by: appleboy <appleboy.tw@gmail.com>

* Fix info message after merge

Change-Id: I1cb556a42e2bd9e9b2051d6db99889c6c9f7ccdb
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>

* Fix lint warning

Change-Id: I3689b96205f494920dbbd03344e8f132a30f40b3
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>

---------

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>
Co-authored-by: Cosmin Cojocar <cosmin@cojocar.ch>
Co-authored-by: Cosmin Cojocar <ccojocar@google.com>
2025-12-11 09:53:19 +01:00

121 lines
2.6 KiB
Go

package autofix
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
)
const (
ModelGPT4o = openai.ChatModelGPT4o
ModelGPT4oMini = openai.ChatModelGPT4oMini
DefaultOpenAIBaseURL = "https://api.openai.com/v1"
)
var _ GenAIClient = (*openaiWrapper)(nil)
type OpenAIConfig struct {
Model string
APIKey string
BaseURL string
MaxTokens int
Temperature float64
SkipSSL bool
}
type openaiWrapper struct {
client openai.Client
model openai.ChatModel
maxTokens int
temperature float64
}
func NewOpenAIClient(config OpenAIConfig) (GenAIClient, error) {
var options []option.RequestOption
if config.APIKey != "" {
options = append(options, option.WithAPIKey(config.APIKey))
}
// Support custom base URL (for OpenAI-compatible APIs)
if config.BaseURL != "" {
options = append(options, option.WithBaseURL(config.BaseURL))
}
// Support skip SSL verification
if config.SkipSSL {
// Create custom HTTP client with InsecureSkipVerify
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // #nosec G402
},
},
}
options = append(options, option.WithHTTPClient(httpClient))
}
openaiModel := parseOpenAIModel(config.Model)
// Set default values
maxTokens := config.MaxTokens
if maxTokens == 0 {
maxTokens = 1024
}
temperature := config.Temperature
if temperature == 0 {
temperature = 0.7
}
return &openaiWrapper{
client: openai.NewClient(options...),
model: openaiModel,
maxTokens: maxTokens,
temperature: temperature,
}, nil
}
func (o *openaiWrapper) GenerateSolution(ctx context.Context, prompt string) (string, error) {
params := openai.ChatCompletionNewParams{
Model: o.model,
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage(prompt),
},
}
// Set optional parameters if available
// Using WithMaxTokens and WithTemperature methods if they exist in v3
resp, err := o.client.Chat.Completions.New(ctx, params)
if err != nil {
return "", fmt.Errorf("generating autofix: %w", err)
}
if resp == nil || len(resp.Choices) == 0 {
return "", errors.New("no autofix returned by openai")
}
content := resp.Choices[0].Message.Content
if content == "" {
return "", errors.New("nothing found in the first autofix returned by openai")
}
return content, nil
}
func parseOpenAIModel(model string) openai.ChatModel {
switch model {
case "gpt-4o":
return openai.ChatModelGPT4o
case "gpt-4o-mini":
return openai.ChatModelGPT4oMini
default:
return model
}
}