Skip to content

Commit 4d20a5b

Browse files
authored
Merge pull request #21 from otiai10/develop
Support function_call
2 parents 4fe5772 + da0673f commit 4d20a5b

File tree

8 files changed

+175
-34
lines changed

8 files changed

+175
-34
lines changed

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import (
3030

3131
func main() {
3232
client := openaigo.NewClient(os.Getenv("OPENAI_API_KEY"))
33-
request := openaigo.ChatCompletionRequestBody{
33+
request := openaigo.ChatRequest{
3434
Model: "gpt-3.5-turbo",
35-
Messages: []openaigo.ChatMessage{
35+
Messages: []openaigo.Message{
3636
{Role: "user", Content: "Hello!"},
3737
},
3838
}
@@ -51,6 +51,8 @@ cd openaigo
5151
OPENAI_API_KEY=YourAPIKey go run ./testapp/main.go
5252
```
5353

54+
See [test app](https://github.com/otiai10/openaigo/blob/main/testapp/main.go) as a working example.
55+
5456
# API Keys?
5557

5658
Visit https://beta.openai.com/account/api-keys and you can create your own API key to get started [for free](https://openai.com/api/pricing/).
@@ -62,8 +64,9 @@ Visit https://beta.openai.com/account/api-keys and you can create your own API k
6264
- [x] [Retrieve model](https://beta.openai.com/docs/api-reference/models/retrieve)
6365
- Text Completions
6466
- [x] [Create completion](https://beta.openai.com/docs/api-reference/completions/create)
65-
- **Chat Completions** <- NEW!
67+
- **Chat Completions**
6668
- [x] [Create Chat Completions](https://platform.openai.com/docs/api-reference/chat/create)
69+
- [x] [with function_call](https://openai.com/blog/function-calling-and-other-api-updates) <- New
6770
- Edits
6871
- [x] [Create edits](https://beta.openai.com/docs/api-reference/edits/create)
6972
- Images
@@ -91,11 +94,36 @@ Visit https://beta.openai.com/account/api-keys and you can create your own API k
9194
- ~~[List engines](https://beta.openai.com/docs/api-reference/engines/list)~~
9295
- ~~[Retrieve engine](https://beta.openai.com/docs/api-reference/engines/retrieve)~~
9396

97+
# Need `function_call`?
98+
99+
```go
100+
request := openaigo.ChatRequest{
101+
Messages: []openaigo.Message{
102+
{Role: "user", Content: "How's the weather today in Tokyo?"},
103+
},
104+
Functions: []openaigo.Function{
105+
{
106+
Name: "get_weather",
107+
Parameters: openaigo.Parameters{
108+
Type: "object",
109+
Properties: map[string]map[string]any{
110+
"location": {"type": "string"},
111+
"date": {"type": "string", "description": "ISO 8601 date string"},
112+
},
113+
Required: []string{"location"},
114+
},
115+
}
116+
},
117+
}
118+
```
119+
120+
See [test app](https://github.com/otiai10/openaigo/blob/main/testapp/main.go) as a working example.
121+
94122
# Need `stream`?
95123

96124
```go
97125
client := openaigo.NewClient(OPENAI_API_KEY)
98-
request := openaigo.ChatCompletionRequestBody{
126+
request := openaigo.ChatRequest{
99127
Stream: true,
100128
StreamCallback: func(res ChatCompletionResponse, done bool, err error) {
101129
// Do what you want!

chat.go

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type ChatCompletionRequestBody struct {
1515
// In the example above, the user’s final question of “Where was it played?” only makes sense in the context of the prior messages about the World Series of 2020.
1616
// Because the models have no memory of past requests, all relevant information must be supplied via the conversation.
1717
// If a conversation cannot fit within the model’s token limit, it will need to be shortened in some way.
18-
Messages []ChatMessage `json:"messages"`
18+
Messages []Message `json:"messages"`
1919

2020
// Temperature: What sampling temperature to use, between 0 and 2.
2121
// Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
@@ -78,14 +78,36 @@ type ChatCompletionRequestBody struct {
7878
// User: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Learn more.
7979
// https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids
8080
User string `json:"user,omitempty"`
81+
82+
// Functions: A list of functions which GPT is allowed to request to call.
83+
Functions []Function `json:"functions,omitempty"`
84+
85+
// FunctionCall: You ain't need it. Default is "auto".
86+
FunctionCall string `json:"function_call,omitempty"`
87+
}
88+
89+
type Function struct {
90+
Name string `json:"name,omitempty"`
91+
Description string `json:"description,omitempty"`
92+
Parameters Parameters `json:"parameters,omitempty"`
93+
}
94+
95+
type Parameters struct {
96+
Type string `json:"type,omitempty"` // Must be "object"
97+
Properties map[string]map[string]any `json:"properties,omitempty"`
98+
Required []string `json:"required,omitempty"`
8199
}
82100

83-
// ChatMessage: An element of messages parameter.
101+
// ChatRequest is just an alias of ChatCompletionRequestBody.
102+
type ChatRequest ChatCompletionRequestBody
103+
104+
// Message: An element of messages parameter.
84105
// The main input is the messages parameter. Messages must be an array of message objects,
85106
// where each object has a role (either “system”, “user”, or “assistant”)
86107
// and content (the content of the message).
87108
// Conversations can be as short as 1 message or fill many pages.
88-
type ChatMessage struct {
109+
// See https://platform.openai.com/docs/api-reference/chat/create#chat/create-messages
110+
type Message struct {
89111

90112
// Role: Either of "system", "user", "assistant".
91113
// Typically, a conversation is formatted with a system message first, followed by alternating user and assistant messages.
@@ -96,19 +118,40 @@ type ChatMessage struct {
96118

97119
// Content: A content of the message.
98120
Content string `json:"content"`
121+
122+
// FunctionCall requested by ChatGPT.
123+
// Only appears in a response from ChatGPT in which ChatGPT wants to call a function.
124+
FunctionCall *FunctionCall `json:"function_call,omitempty"`
125+
126+
// Name of the function called, to tell this message is a result of function_call.
127+
// Only appears in a request from us when the previous message is "function_call" requested by ChatGPT.
128+
Name string `json:"name,omitempty"`
99129
}
100130

131+
type FunctionCall struct {
132+
Name string `json:"name,omitempty"`
133+
ArgumentsRaw string `json:"arguments,omitempty"`
134+
// Arguments map[string]any `json:"arguments,omitempty"`
135+
}
136+
137+
// func Arg[T any](fc FunctionCall, name string) (res T) {
138+
// if fc.Arguments == nil || fc.Arguments[name] == nil {
139+
// return
140+
// }
141+
// return fc.Arguments[name].(T)
142+
// }
143+
101144
type ChatCompletionResponse struct {
102-
ID string `json:"id"`
103-
Object string `json:"object"`
104-
Created int64 `json:"created"`
105-
Choices []ChatChoice `json:"choices"`
106-
Usage Usage `json:"usage"`
145+
ID string `json:"id"`
146+
Object string `json:"object"`
147+
Created int64 `json:"created"`
148+
Choices []Choice `json:"choices"`
149+
Usage Usage `json:"usage"`
107150
}
108151

109-
type ChatChoice struct {
110-
Index int `json:"index"`
111-
Message ChatMessage `json:"message"`
112-
FinishReason string `json:"finish_reason"`
113-
Delta ChatMessage `json:"delta"` // Only appears in stream response
152+
type Choice struct {
153+
Index int `json:"index"`
154+
Message Message `json:"message"`
155+
FinishReason string `json:"finish_reason"`
156+
Delta Message `json:"delta"` // Only appears in stream response
114157
}

chat_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,34 @@ func TestClient_ChatCompletion_Stream(t *testing.T) {
3434
Expect(t, res).TypeOf("openaigo.ChatCompletionResponse")
3535
wg.Wait()
3636
}
37+
38+
func TestClient_ChatCompletion_FunctionCall(t *testing.T) {
39+
client := NewClient("")
40+
client.BaseURL = mockserver.URL
41+
res, err := client.Chat(nil, ChatRequest{
42+
Model: GPT3_5Turbo,
43+
Messages: []Message{
44+
{
45+
Role: "user", Content: "Hello, I'm John.",
46+
},
47+
},
48+
Functions: []Function{
49+
{
50+
Name: "test_method",
51+
Parameters: Parameters{
52+
Type: "object",
53+
Properties: map[string]map[string]any{
54+
"arg_0": {
55+
"type": "string",
56+
"description": "This is a test",
57+
},
58+
},
59+
Required: []string{"arg_0"},
60+
},
61+
},
62+
},
63+
FunctionCall: "auto",
64+
})
65+
Expect(t, err).ToBe(nil)
66+
Expect(t, res).TypeOf("openaigo.ChatCompletionResponse")
67+
}

completion.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ type CompletionRequestBody struct {
8989
}
9090

9191
type CompletionResponse struct {
92-
ID string `json:"id"`
93-
Object ObjectType `json:"object"`
94-
Created int64 `json:"created"`
95-
Model string `json:"model"`
96-
Choices []Choice `json:"choices"`
92+
ID string `json:"id"`
93+
Object ObjectType `json:"object"`
94+
Created int64 `json:"created"`
95+
Model string `json:"model"`
96+
Choices []CompletionChoice `json:"choices"`
9797
Usage Usage
9898
}

edits.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ type EditCreateRequestBody struct {
1010
}
1111

1212
type EditCreateResponse struct {
13-
Object ObjectType `json:"object"`
14-
Created int64 `json:"created"`
15-
Choices []Choice `json:"choices"`
16-
Usage Usage `json:"usage"`
13+
Object ObjectType `json:"object"`
14+
Created int64 `json:"created"`
15+
Choices []CompletionChoice `json:"choices"`
16+
Usage Usage `json:"usage"`
1717
}

endpoints.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,8 @@ func (client *Client) DeleteFineTuneModel(ctx context.Context, id string) (resp
193193

194194
// Chat, short-hand of ChatCompletion.
195195
// Creates a completion for the chat message.
196-
func (client *Client) Chat(ctx context.Context, body ChatCompletionRequestBody) (resp ChatCompletionResponse, err error) {
197-
return client.ChatCompletion(ctx, body)
196+
func (client *Client) Chat(ctx context.Context, body ChatRequest) (resp ChatCompletionResponse, err error) {
197+
return client.ChatCompletion(ctx, ChatCompletionRequestBody(body))
198198
}
199199

200200
// ChatCompletion: POST https://api.openai.com/v1/chat/completions

response.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package openaigo
22

3-
type Choice struct {
3+
type CompletionChoice struct {
44
Text string `json:"text"`
55
Index int `json:"index"`
66
LogProbs int `json:"logprobs"`

testapp/main.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ var (
7373
Name: "chat_completion",
7474
Run: func() (any, error) {
7575
client := openaigo.NewClient(OPENAI_API_KEY)
76-
request := openaigo.ChatCompletionRequestBody{
76+
request := openaigo.ChatRequest{
7777
Model: openaigo.GPT3_5Turbo,
78-
Messages: []openaigo.ChatMessage{
78+
Messages: []openaigo.Message{
7979
{Role: "user", Content: "Hello!"},
8080
},
8181
}
@@ -87,9 +87,9 @@ var (
8787
Name: "[SKIP] chat_completion_GPT4",
8888
Run: func() (any, error) {
8989
client := openaigo.NewClient(OPENAI_API_KEY)
90-
request := openaigo.ChatCompletionRequestBody{
90+
request := openaigo.ChatRequest{
9191
Model: openaigo.GPT4,
92-
Messages: []openaigo.ChatMessage{
92+
Messages: []openaigo.Message{
9393
{Role: "user", Content: "Who are you?"},
9494
},
9595
}
@@ -114,7 +114,7 @@ var (
114114
request := openaigo.ChatCompletionRequestBody{
115115
Model: openaigo.GPT3_5Turbo_0613,
116116
StreamCallback: calback,
117-
Messages: []openaigo.ChatMessage{
117+
Messages: []openaigo.Message{
118118
{
119119
Role: "user",
120120
Content: fmt.Sprintf("What are the historical events happend on %s", time.Now().Format("01/02"))},
@@ -135,6 +135,45 @@ var (
135135
}
136136
},
137137
},
138+
139+
// Test case using "function_call"
140+
{
141+
Name: "function_call",
142+
Run: func() (any, error) {
143+
conversation := []openaigo.Message{
144+
{Role: "user", Content: "What's the weather in Tokyo today?"},
145+
}
146+
client := openaigo.NewClient(OPENAI_API_KEY)
147+
request := openaigo.ChatRequest{
148+
Model: openaigo.GPT3_5Turbo_0613,
149+
Messages: conversation,
150+
Functions: []openaigo.Function{
151+
{
152+
Name: "get_weather",
153+
Description: "A function to get weather information",
154+
Parameters: openaigo.Parameters{
155+
Type: "object",
156+
Properties: map[string]map[string]any{
157+
"location": {"type": "string"},
158+
"date": {"type": "string", "description": "ISO 8601 date string"},
159+
},
160+
Required: []string{"location"},
161+
},
162+
},
163+
},
164+
}
165+
res0, err := client.Chat(nil, request)
166+
conversation = append(conversation, res0.Choices[0].Message)
167+
conversation = append(conversation, openaigo.Message{
168+
Role: "function",
169+
Name: "get_weather",
170+
Content: "20%:thunderstorm,70%:sandstorm,10%:snowy",
171+
})
172+
request.Messages = conversation
173+
res, err := client.Chat(nil, request)
174+
return res, err
175+
},
176+
},
138177
}
139178

140179
list bool

0 commit comments

Comments
 (0)