1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
// Package chatgpt is a simple binding to the ChatGPT API.
package chatgpt
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"within.website/x/web"
)
type Request struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Functions []Function `json:"functions,omitempty"`
Seed *int `json:"seed,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
}
type Function struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters Param `json:"parameters"`
}
type Param struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
Enum []string `json:"enum,omitempty"`
Properties Properties `json:"properties"`
Required []string `json:"required,omitempty"`
}
type Properties map[string]Param
func (p Properties) MarshalJSON() ([]byte, error) {
if len(p) == 0 {
return []byte("{}"), nil
}
return json.Marshal(map[string]Param(p))
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
FunctionCall *Funcall `json:"function_call,omitempty"`
}
type Funcall struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
func (m Message) ProxyFormat() string {
return fmt.Sprintf("%s\\ %s", strings.Title(m.Role), m.Content)
}
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
type Choice struct {
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
type Response struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Usage Usage `json:"usage"`
Choices []Choice `json:"choices"`
}
type Client struct {
httpCli *http.Client
apiKey string
baseURL string
}
func (c Client) WithBaseURL(baseURL string) Client {
c.baseURL = baseURL
return c
}
func NewClient(apiKey string) Client {
return Client{
httpCli: &http.Client{},
apiKey: apiKey,
baseURL: "https://api.openai.com",
}
}
func (c Client) Complete(ctx context.Context, r Request) (*Response, error) {
if r.Model == "" {
r.Model = "gpt-3.5-turbo"
}
data, err := json.Marshal(r)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/v1/chat/completions", bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("chatgpt: [unexpected] can't make request???: %w", err)
}
req.Header.Add("Authorization", "Bearer "+c.apiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := c.httpCli.Do(req)
if err != nil {
return nil, fmt.Errorf("chatgpt: can't reach API: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, web.NewError(http.StatusOK, resp)
}
defer resp.Body.Close()
var result Response
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("chatgpt: can't decode result: %w", err)
}
return &result, nil
}
|