Cybersecurity-Projects/PROJECTS/intermediate/security-news-scraper/internal/ai/openai.go

79 lines
1.7 KiB
Go

// ©AngelaMos | 2026
// openai.go
package ai
import (
"context"
"fmt"
"net/http"
"strings"
)
const (
openAIChatPath = "/chat/completions"
roleSystem = "system"
roleUser = "user"
headerAuth = "Authorization"
bearerPrefix = "Bearer "
)
type openAICompat struct {
name string
http *http.Client
baseURL string
model string
apiKey string
}
func newOpenAICompat(name string, client *http.Client, baseURL, model, apiKey string) *openAICompat {
return &openAICompat{
name: name,
http: client,
baseURL: strings.TrimRight(baseURL, "/"),
model: model,
apiKey: apiKey,
}
}
func (c *openAICompat) Name() string { return c.name }
type openAIMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type openAIRequest struct {
Model string `json:"model"`
Messages []openAIMessage `json:"messages"`
}
type openAIResponse struct {
Choices []struct {
Message openAIMessage `json:"message"`
} `json:"choices"`
}
func (c *openAICompat) Generate(ctx context.Context, req IdeationRequest) (IdeationResult, error) {
system, user := buildPrompt(req)
body := openAIRequest{
Model: c.model,
Messages: []openAIMessage{
{Role: roleSystem, Content: system},
{Role: roleUser, Content: user},
},
}
header := http.Header{}
if c.apiKey != "" {
header.Set(headerAuth, bearerPrefix+c.apiKey)
}
var resp openAIResponse
if err := postJSON(ctx, c.http, c.baseURL+openAIChatPath, header, body, &resp); err != nil {
return IdeationResult{}, err
}
if len(resp.Choices) == 0 {
return IdeationResult{}, fmt.Errorf("ai: %s returned no choices", c.name)
}
return parseResult(resp.Choices[0].Message.Content)
}