openai.go (3825B)
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 if req.Body != nil { 95 req.Header.Set("Content-Type", "application/json") 96 } 97 return c.Do(req) 98 } 99 100 type completeResponse struct { 101 Choices []struct { 102 Message Message 103 } 104 } 105 106 func (c *Client) Complete(chat *Chat) (*Message, error) { 107 b, err := json.Marshal(chat) 108 if err != nil { 109 return nil, fmt.Errorf("encode messages: %w", err) 110 } 111 u := c.BaseURL + "/v1/chat/completions" 112 req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(b)) 113 if err != nil { 114 return nil, err 115 } 116 resp, err := c.do(req) 117 if err != nil { 118 return nil, err 119 } 120 defer resp.Body.Close() 121 if resp.StatusCode == http.StatusUnauthorized { 122 return nil, fmt.Errorf("unauthorised") 123 } else if resp.StatusCode >= 400 && resp.StatusCode <= 499 { 124 var aerr apiError 125 if err := json.NewDecoder(resp.Body).Decode(&aerr); err != nil { 126 return nil, fmt.Errorf(resp.Status) 127 } 128 return nil, aerr 129 } else if resp.StatusCode >= 500 { 130 return nil, fmt.Errorf(resp.Status) 131 } 132 133 var cresp completeResponse 134 if err := json.NewDecoder(resp.Body).Decode(&cresp); err != nil { 135 return nil, fmt.Errorf("decode response: %w", err) 136 } 137 if len(cresp.Choices) == 0 { 138 return nil, fmt.Errorf("no completions in response") 139 } 140 return &cresp.Choices[0].Message, nil 141 } 142 143 func (c *Client) Models() ([]Model, error) { 144 u := c.BaseURL + "/v1/models" 145 req, err := http.NewRequest(http.MethodGet, u, nil) 146 if err != nil { 147 return nil, err 148 } 149 resp, err := c.do(req) 150 if err != nil { 151 return nil, err 152 } 153 defer resp.Body.Close() 154 if resp.StatusCode != http.StatusOK { 155 var aerr apiError 156 if err := json.NewDecoder(resp.Body).Decode(&aerr); err != nil { 157 return nil, fmt.Errorf(resp.Status) 158 } 159 return nil, aerr 160 } 161 v := struct { 162 Data []Model 163 }{} 164 if err := json.NewDecoder(resp.Body).Decode(&v); err != nil { 165 return nil, fmt.Errorf("decode response: %w", err) 166 } 167 return v.Data, nil 168 }