package llm import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" ) // Client calls an OpenAI-compatible chat completion API. type Client struct { BaseURL string APIKey string Model string Temperature float64 HTTP *http.Client } // NewClient creates a new LLM client. func NewClient(baseURL, apiKey, model string) *Client { return &Client{ BaseURL: strings.TrimRight(baseURL, "/"), APIKey: apiKey, Model: model, HTTP: &http.Client{}, } } // WithTemperature sets the temperature for LLM requests. // A value of 0 (the zero value) means the field is omitted from the request, // causing the server to use its default temperature. // If not set (zero value), the server default is used. func (c *Client) WithTemperature(t float64) *Client { c.Temperature = t return c } // Message represents a chat message. type Message struct { Role string `json:"role"` Content string `json:"content"` } // ChatRequest is the request payload. type ChatRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` Temperature float64 `json:"temperature,omitempty"` } // ChatResponse is the response from the API. type ChatResponse struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` } // Complete sends a chat completion request and returns the assistant's response content. func (c *Client) Complete(messages []Message) (string, error) { reqBody := ChatRequest{ Model: c.Model, Temperature: c.Temperature, Messages: messages, } data, err := json.Marshal(reqBody) if err != nil { return "", fmt.Errorf("marshal request: %w", err) } url := c.BaseURL + "/chat/completions" req, err := http.NewRequest("POST", url, bytes.NewReader(data)) if err != nil { return "", fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.APIKey) req.Header.Set("Content-Type", "application/json") resp, err := c.HTTP.Do(req) if err != nil { return "", fmt.Errorf("LLM request: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("LLM API error (status %d): %s", resp.StatusCode, string(body)) } body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read response: %w", err) } var chatResp ChatResponse if err := json.Unmarshal(body, &chatResp); err != nil { return "", fmt.Errorf("parse response: %w", err) } if len(chatResp.Choices) == 0 { return "", fmt.Errorf("no choices in LLM response") } return chatResp.Choices[0].Message.Content, nil }