openai.go (3942B)
1 package openai 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "strings" 9 "time" 10 ) 11 12 type Role string 13 14 const ( 15 RoleSystem = "system" 16 RoleUser = "user" 17 RoleAssistant = "assistant" 18 ) 19 20 type Message struct { 21 Role string `json:"role"` 22 Content string `json:"content"` 23 Reasoning string `json:"reasoning,omitempty"` 24 ToolCalls []ToolCall `json:"tool_calls,omitempty"` 25 ToolCallID string `json:"tool_call_id,omitempty"` 26 } 27 28 type Function struct { 29 Name string `json:"name"` 30 Description string `json:"description"` 31 Parameters json.RawMessage `json:"parameters"` // JSON Schema for tool arguments 32 } 33 34 type Tool struct { 35 Type string `json:"type"` 36 Function Function `json:"function"` 37 } 38 39 type ToolCall struct { 40 ID string `json:"id"` 41 Type string `json:"type"` // "function" 42 Function struct { 43 Name string `json:"name"` 44 Arguments string `json:"arguments"` // JSON string; parse per tool 45 } `json:"function"` 46 } 47 48 type Chat struct { 49 Messages []Message `json:"messages"` 50 Model string `json:"model"` 51 Reasoning string `json:"reasoning_effort,omitempty"` 52 } 53 54 type Model struct { 55 ID string 56 Name string 57 Description string 58 Aliases []string 59 MaxContextLength int 60 Deprecation time.Time 61 } 62 63 type Client struct { 64 *http.Client 65 Token string 66 BaseURL string 67 } 68 69 type apiError struct { 70 Message struct { 71 Detail []struct { 72 Msg string 73 } 74 } 75 Type string 76 } 77 78 func (e apiError) Error() string { 79 messages := make([]string, len(e.Message.Detail)) 80 for i := range e.Message.Detail { 81 messages[i] = e.Message.Detail[i].Msg 82 } 83 return fmt.Sprintf("%s: %s", e.Type, strings.Join(messages, ", ")) 84 } 85 86 func (c *Client) do(req *http.Request) (*http.Response, error) { 87 if c.Client == nil { 88 c.Client = http.DefaultClient 89 } 90 if c.Token != "" { 91 req.Header.Set("Authorization", "Bearer "+c.Token) 92 } 93 // req.Header.Set("Accept", "application/json") 94 // assume we're sending JSON if not already set since most requests mandate it 95 if req.Body != nil && req.Header.Get("Content-Type") == "" { 96 req.Header.Set("Content-Type", "application/json") 97 } 98 return c.Do(req) 99 } 100 101 type completeResponse struct { 102 Choices []struct { 103 Message Message 104 } 105 } 106 107 func (c *Client) Complete(chat *Chat) (*Message, error) { 108 b, err := json.Marshal(chat) 109 if err != nil { 110 return nil, fmt.Errorf("encode messages: %w", err) 111 } 112 u := c.BaseURL + "/chat/completions" 113 req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(b)) 114 if err != nil { 115 return nil, err 116 } 117 resp, err := c.do(req) 118 if err != nil { 119 return nil, err 120 } 121 defer resp.Body.Close() 122 if resp.StatusCode == http.StatusUnauthorized { 123 return nil, fmt.Errorf("unauthorised") 124 } else if resp.StatusCode >= 400 && resp.StatusCode <= 499 { 125 var aerr apiError 126 if err := json.NewDecoder(resp.Body).Decode(&aerr); err != nil { 127 return nil, fmt.Errorf(resp.Status) 128 } 129 return nil, aerr 130 } else if resp.StatusCode >= 500 { 131 return nil, fmt.Errorf(resp.Status) 132 } 133 134 var cresp completeResponse 135 if err := json.NewDecoder(resp.Body).Decode(&cresp); err != nil { 136 return nil, fmt.Errorf("decode response: %w", err) 137 } 138 if len(cresp.Choices) == 0 { 139 return nil, fmt.Errorf("no completions in response") 140 } 141 return &cresp.Choices[0].Message, nil 142 } 143 144 func (c *Client) Models() ([]Model, error) { 145 u := c.BaseURL + "/models" 146 req, err := http.NewRequest(http.MethodGet, u, nil) 147 if err != nil { 148 return nil, err 149 } 150 resp, err := c.do(req) 151 if err != nil { 152 return nil, err 153 } 154 defer resp.Body.Close() 155 if resp.StatusCode != http.StatusOK { 156 var aerr apiError 157 if err := json.NewDecoder(resp.Body).Decode(&aerr); err != nil { 158 return nil, fmt.Errorf(resp.Status) 159 } 160 return nil, aerr 161 } 162 v := struct { 163 Data []Model 164 }{} 165 if err := json.NewDecoder(resp.Body).Decode(&v); err != nil { 166 return nil, fmt.Errorf("decode response: %w", err) 167 } 168 return v.Data, nil 169 }