package llm import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) type Client struct { endpoint string apiKey string model string httpClient *http.Client } func NewClient(endpoint, apiKey, model string) *Client { return &Client{ endpoint: strings.TrimRight(endpoint, "/"), apiKey: apiKey, model: model, httpClient: &http.Client{Timeout: 60 * time.Second}, } } type ChatMessage struct { Role string `json:"role"` Content string `json:"content"` } type chatRequest struct { Model string `json:"model"` Messages []ChatMessage `json:"messages"` ResponseFormat *responseFmt `json:"response_format,omitempty"` Temperature float64 `json:"temperature,omitempty"` } type responseFmt struct { Type string `json:"type"` } type chatResponse struct { Choices []struct { Message ChatMessage `json:"message"` } `json:"choices"` Error *struct { Message string `json:"message"` Type string `json:"type"` } `json:"error,omitempty"` } func (c *Client) Chat(ctx context.Context, systemPrompt, userPrompt string) (string, error) { return c.ChatJSON(ctx, systemPrompt, userPrompt, false) } func (c *Client) ChatJSON(ctx context.Context, systemPrompt, userPrompt string, jsonMode bool) (string, error) { messages := []ChatMessage{ {Role: "system", Content: systemPrompt}, {Role: "user", Content: userPrompt}, } req := chatRequest{ Model: c.model, Messages: messages, } if jsonMode { req.ResponseFormat = &responseFmt{Type: "json_object"} } body, err := json.Marshal(req) if err != nil { return "", fmt.Errorf("marshal request: %w", err) } url := c.endpoint + "/v1/chat/completions" httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return "", fmt.Errorf("create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") if c.apiKey != "" { httpReq.Header.Set("Authorization", "Bearer "+c.apiKey) } resp, err := c.httpClient.Do(httpReq) if err != nil { return "", fmt.Errorf("http do: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read response: %w", err) } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("llm api error %d: %s", resp.StatusCode, string(respBody)) } var chatResp chatResponse if err := json.Unmarshal(respBody, &chatResp); err != nil { return "", fmt.Errorf("unmarshal response: %w", err) } if chatResp.Error != nil { return "", fmt.Errorf("llm error: %s", chatResp.Error.Message) } if len(chatResp.Choices) == 0 { return "", fmt.Errorf("no choices in response") } return chatResp.Choices[0].Message.Content, nil }