Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): support function call api #369

Merged
merged 2 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ const (
ChatMessageRoleSystem = "system"
ChatMessageRoleUser = "user"
ChatMessageRoleAssistant = "assistant"
ChatMessageRoleFunction = "function"
)
Ccheers marked this conversation as resolved.
Show resolved Hide resolved

const chatCompletionsSuffix = "/chat/completions"

var (
ErrChatCompletionInvalidModel = errors.New("this model is not supported with this method, please use CreateCompletion client method instead") //nolint:lll
ErrChatCompletionStreamNotSupported = errors.New("streaming is not supported with this method, please use CreateChatCompletionStream") //nolint:lll
Expand All @@ -27,6 +30,14 @@ type ChatCompletionMessage struct {
// - https://github.com/openai/openai-python/blob/main/chatml.md
// - https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
Name string `json:"name,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name is documented now, this document should be updated.


FunctionCall *FunctionCall `json:"function_call,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FunctionCall should be added to ChatCompletionStreamChoiceDelta too.

}

type FunctionCall struct {
Name string `json:"name,omitempty"`
// call function with arguments in JSON format
Arguments string `json:"arguments,omitempty"`
}

// ChatCompletionRequest represents a request structure for chat completion API.
Expand All @@ -43,12 +54,70 @@ type ChatCompletionRequest struct {
FrequencyPenalty float32 `json:"frequency_penalty,omitempty"`
LogitBias map[string]int `json:"logit_bias,omitempty"`
User string `json:"user,omitempty"`
Functions []*FunctionDefine `json:"functions,omitempty"`
Copy link
Contributor

@j178 j178 Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to make Functions a slice of pointer? It makes constructing request much harder. Why not []FunctionDefine?

FunctionCall string `json:"function_call,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this -- this is crucial functionality because you can use it to force the model to use a particular function, but only as an object.

}

type FunctionDefine struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
// it's required in function call
Parameters *FunctionParams `json:"parameters"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why pointer?

}

type FunctionParams struct {
// the Type must be JSONSchemaTypeObject
Type JSONSchemaType `json:"type"`
Properties map[string]*JSONSchemaDefine `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
}

type JSONSchemaType string

const (
JSONSchemaTypeObject JSONSchemaType = "object"
JSONSchemaTypeNumber JSONSchemaType = "number"
JSONSchemaTypeString JSONSchemaType = "string"
JSONSchemaTypeArray JSONSchemaType = "array"
JSONSchemaTypeNull JSONSchemaType = "null"
JSONSchemaTypeBoolean JSONSchemaType = "boolean"
)

// JSONSchemaDefine is a struct for JSON Schema.
type JSONSchemaDefine struct {
// Type is a type of JSON Schema.
Type JSONSchemaType `json:"type,omitempty"`
// Description is a description of JSON Schema.
Description string `json:"description,omitempty"`
// Enum is a enum of JSON Schema. It used if Type is JSONSchemaTypeString.
Enum []string `json:"enum,omitempty"`
// Properties is a properties of JSON Schema. It used if Type is JSONSchemaTypeObject.
Properties map[string]*JSONSchemaDefine `json:"properties,omitempty"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to those pointed out by @j178 , this is also an unnecessary use of pointers.

// Required is a required of JSON Schema. It used if Type is JSONSchemaTypeObject.
Required []string `json:"required,omitempty"`
}

type FinishReason string

const (
FinishReasonStop FinishReason = "stop"
FinishReasonLength FinishReason = "length"
FinishReasonFunctionCall FinishReason = "function_call"
FinishReasonContentFilter FinishReason = "content_filter"
FinishReasonNull FinishReason = "null"
)

type ChatCompletionChoice struct {
Index int `json:"index"`
Message ChatCompletionMessage `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Message ChatCompletionMessage `json:"message"`
// FinishReason
// stop: API returned complete message,
// or a message terminated by one of the stop sequences provided via the stop parameter
// length: Incomplete model output due to max_tokens parameter or token limit
// function_call: The model decided to call a function
// content_filter: Omitted content due to a flag from our content filters
// null: API response still in progress or incomplete
FinishReason FinishReason `json:"finish_reason"`
}

// ChatCompletionResponse represents a response structure for chat completion API.
Expand All @@ -71,7 +140,7 @@ func (c *Client) CreateChatCompletion(
return
}

urlSuffix := "/chat/completions"
urlSuffix := chatCompletionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrChatCompletionInvalidModel
return
Expand Down
2 changes: 1 addition & 1 deletion chat_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (c *Client) CreateChatCompletionStream(
ctx context.Context,
request ChatCompletionRequest,
) (stream *ChatCompletionStream, err error) {
urlSuffix := "/chat/completions"
urlSuffix := chatCompletionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrChatCompletionInvalidModel
return
Expand Down
2 changes: 1 addition & 1 deletion completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var disabledModelsForEndpoints = map[string]map[string]bool{
GPT432K0314: true,
GPT432K0613: true,
},
"/chat/completions": {
chatCompletionsSuffix: {
CodexCodeDavinci002: true,
CodexCodeCushman001: true,
CodexCodeDavinci001: true,
Expand Down