commit b50de427665f27ae86a44e71924a944559f99219 Author: DengBiao <2319963317@qq.com> Date: Fri Mar 3 14:29:28 2023 +0800 add Reverse: for v0.0.1 diff --git a/go-gpt3/.gitignore b/go-gpt3/.gitignore new file mode 100644 index 0000000..99b40bf --- /dev/null +++ b/go-gpt3/.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Auth token for tests +.openai-token +.idea \ No newline at end of file diff --git a/go-gpt3/.golangci.yml b/go-gpt3/.golangci.yml new file mode 100644 index 0000000..58fab4a --- /dev/null +++ b/go-gpt3/.golangci.yml @@ -0,0 +1,272 @@ +## Golden config for golangci-lint v1.47.3 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adopt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + + gocognit: + # Minimal code complexity to report + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date` + # Default: [] + ignored-functions: + - os.Chmod + - os.Mkdir + - os.MkdirAll + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets + - prometheus.ExponentialBucketsRange + - prometheus.LinearBuckets + - strconv.FormatFloat + - strconv.FormatInt + - strconv.FormatUint + - strconv.ParseFloat + - strconv.ParseInt + - strconv.ParseUint + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw" + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + varcheck: + # Check usage of exported fields and variables. + # Default: false + exported-fields: false # default false # TODO: enable after fixing false positives + + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unused # Checks Go code for unused constants, variables, functions and types + ## disabled by default + # - asasalint # Check for pass []any as any in variadic func(...any) + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bidichk # Checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + - cyclop # checks function and package cyclomatic complexity + - dupl # Tool for code clone detection + - durationcheck # check for two durations multiplied together + - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error. + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. + - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # Forbids identifiers + - funlen # Tool for detection of long functions + # - gochecknoglobals # check that no global variables exist + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # Provides diagnostics that check for bugs, performance and style issues. + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - goimports # In addition to fixing imports, goimports also formats your code in the same style as gofmt. + - gomnd # An analyzer to detect magic numbers. + - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with f at the end + - gosec # Inspects source code for security problems + - lll # Reports long lines + - makezero # Finds slice declarations with non-zero initial length + # - nakedret # Finds naked returns in functions greater than a specified function length + - nestif # Reports deeply nested if statements + - nilerr # Finds the code that returns nil even if it checks that the error is not nil. + - nilnil # Checks that there is no simultaneous return of nil error and an invalid value. + # - noctx # noctx finds sending http request without context.Context + - nolintlint # Reports ill-formed or insufficient nolint directives + # - nonamedreturns # Reports all named returns + - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. + - predeclared # find code that shadows one of Go's predeclared identifiers + - promlinter # Check Prometheus metrics naming via promlint + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - stylecheck # Stylecheck is a replacement for golint + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + - testpackage # linter that makes you use a separate _test package + - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - wastedassign # wastedassign finds wasted assignment statements. + - whitespace # Tool for detection of leading and trailing whitespace + ## you may want to enable + #- decorder # check declaration order and count of types, constants, variables and functions + #- exhaustruct # Checks if all structure fields are initialized + #- goheader # Checks is file header matches to pattern + #- ireturn # Accept Interfaces, Return Concrete Types + #- prealloc # [premature optimization, but can be used in some cases] Finds slice declarations that could potentially be preallocated + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # Checks that errors returned from external packages are wrapped + ## disabled + #- containedctx # containedctx is a linter that detects struct contained context.Context field + #- depguard # [replaced by gomodguard] Go linter that checks if package imports are in a list of acceptable packages + #- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted. + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- gci # Gci controls golang package import order and makes it always deterministic. + #- godox # Tool for detection of FIXME, TODO and other comment keywords + #- goerr113 # [too strict] Golang linter to check the errors handling expressions + #- gofmt # [replaced by goimports] Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + #- gofumpt # [replaced by goimports, gofumports is not available yet] Gofumpt checks whether code was gofumpt-ed. + #- grouper # An analyzer to analyze expression groups. + #- ifshort # Checks that your code uses short syntax for if-statements whenever possible + #- importas # Enforces consistent import aliases + #- maintidx # maintidx measures the maintainability index of each function. + #- misspell # [useless] Finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] nlreturn checks for a new line before return and branch statements to increase code clarity + #- nosnakecase # Detects snake case of variable naming and function name. # TODO: maybe enable after https://github.com/sivchari/nosnakecase/issues/14 + #- paralleltest # [too many false positives] paralleltest detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # Checks the struct tags. + #- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] Whitespace Linter - Forces you to use empty lines! + ## deprecated + #- exhaustivestruct # [deprecated, replaced by exhaustruct] Checks if all struct's fields are initialized + #- golint # [deprecated, replaced by revive] Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + #- interfacer # [deprecated] Linter that suggests narrower interface types + #- maligned # [deprecated, replaced by govet fieldalignment] Tool to detect Go structs that would take less memory if their fields were sorted + #- scopelint # [deprecated, replaced by exportloopref] Scopelint checks for unpinned variables in go programs + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "^//\\s*go:generate\\s" + linters: [ lll ] + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {" + linters: [ errorlint ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck diff --git a/go-gpt3/LICENSE b/go-gpt3/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/go-gpt3/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/go-gpt3/Makefile b/go-gpt3/Makefile new file mode 100644 index 0000000..2e608aa --- /dev/null +++ b/go-gpt3/Makefile @@ -0,0 +1,35 @@ +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + + +##@ Development + +.PHONY: test +TEST_ARGS ?= -v +TEST_TARGETS ?= ./... +test: ## Test the Go modules within this package. + @ echo ▶️ go test $(TEST_ARGS) $(TEST_TARGETS) + go test $(TEST_ARGS) $(TEST_TARGETS) + @ echo ✅ success! + + +.PHONY: lint +LINT_TARGETS ?= ./... +lint: ## Lint Go code with the installed golangci-lint + @ echo "▶️ golangci-lint run" + golangci-lint run $(LINT_TARGETS) + @ echo "✅ golangci-lint run" diff --git a/go-gpt3/README.md b/go-gpt3/README.md new file mode 100644 index 0000000..7f8b016 --- /dev/null +++ b/go-gpt3/README.md @@ -0,0 +1,87 @@ +# go-gpt3 +[![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/sashabaranov/go-gpt3) +[![Go Report Card](https://goreportcard.com/badge/github.com/sashabaranov/go-gpt3)](https://goreportcard.com/report/github.com/sashabaranov/go-gpt3) + + +[OpenAI ChatGPT and GPT-3](https://platform.openai.com/) API client for Go + +Installation: +``` +go get github.com/sashabaranov/go-gpt3 +``` + + +Example usage: + +```go +package main + +import ( + "context" + "fmt" + gogpt "github.com/sashabaranov/go-gpt3" +) + +func main() { + c := gogpt.NewClient("your token") + ctx := context.Background() + + req := gogpt.CompletionRequest{ + Model: gogpt.GPT3Ada, + MaxTokens: 5, + Prompt: "Lorem ipsum", + } + resp, err := c.CreateCompletion(ctx, req) + if err != nil { + return + } + fmt.Println(resp.Choices[0].Text) +} +``` + +Streaming response example: + +```go +package main + +import ( + "errors" + "context" + "fmt" + "io" + gogpt "github.com/sashabaranov/go-gpt3" +) + +func main() { + c := gogpt.NewClient("your token") + ctx := context.Background() + + req := gogpt.CompletionRequest{ + Model: gogpt.GPT3Ada, + MaxTokens: 5, + Prompt: "Lorem ipsum", + Stream: true, + } + stream, err := c.CreateCompletionStream(ctx, req) + if err != nil { + return + } + defer stream.Close() + + for { + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + fmt.Println("Stream finished") + return + } + + if err != nil { + fmt.Printf("Stream error: %v\n", err) + return + } + + + fmt.Printf("Stream response: %v\n", response) + } +} +``` diff --git a/go-gpt3/answers.go b/go-gpt3/answers.go new file mode 100644 index 0000000..5a99078 --- /dev/null +++ b/go-gpt3/answers.go @@ -0,0 +1,50 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +type AnswerRequest struct { + Documents []string `json:"documents,omitempty"` + File string `json:"file,omitempty"` + Question string `json:"question"` + SearchModel string `json:"search_model,omitempty"` + Model string `json:"model"` + ExamplesContext string `json:"examples_context"` + Examples [][]string `json:"examples"` + MaxTokens int `json:"max_tokens,omitempty"` + Stop []string `json:"stop,omitempty"` + Temperature *float64 `json:"temperature,omitempty"` +} + +type AnswerResponse struct { + Answers []string `json:"answers"` + Completion string `json:"completion"` + Model string `json:"model"` + Object string `json:"object"` + SearchModel string `json:"search_model"` + SelectedDocuments []struct { + Document int `json:"document"` + Text string `json:"text"` + } `json:"selected_documents"` +} + +// Search — perform a semantic search api call over a list of documents. +func (c *Client) Answers(ctx context.Context, request AnswerRequest) (response AnswerResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL("/answers"), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/api.go b/go-gpt3/api.go new file mode 100644 index 0000000..715c9dd --- /dev/null +++ b/go-gpt3/api.go @@ -0,0 +1,81 @@ +package gogpt + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// Client is OpenAI GPT-3 API client. +type Client struct { + config ClientConfig +} + +// NewClient creates new OpenAI API client. +func NewClient(authToken string) *Client { + config := DefaultConfig(authToken) + return &Client{config} +} + +// NewClientWithConfig creates new OpenAI API client for specified config. +func NewClientWithConfig(config ClientConfig) *Client { + return &Client{config} +} + +// NewOrgClient creates new OpenAI API client for specified Organization ID. +// +// Deprecated: Please use NewClientWithConfig. +func NewOrgClient(authToken, org string) *Client { + config := DefaultConfig(authToken) + config.OrgID = org + return &Client{config} +} + +func (c *Client) sendRequest(req *http.Request, v interface{}) error { + req.Header.Set("Accept", "application/json; charset=utf-8") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.config.authToken)) + + // Check whether Content-Type is already set, Upload Files API requires + // Content-Type == multipart/form-data + contentType := req.Header.Get("Content-Type") + if contentType == "" { + req.Header.Set("Content-Type", "application/json; charset=utf-8") + } + + if len(c.config.OrgID) > 0 { + req.Header.Set("OpenAI-Organization", c.config.OrgID) + } + + res, err := c.config.HTTPClient.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + + if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { + var errRes ErrorResponse + err = json.NewDecoder(res.Body).Decode(&errRes) + if err != nil || errRes.Error == nil { + reqErr := RequestError{ + StatusCode: res.StatusCode, + Err: err, + } + return fmt.Errorf("error, %w", &reqErr) + } + errRes.Error.StatusCode = res.StatusCode + return fmt.Errorf("error, status code: %d, message: %w", res.StatusCode, errRes.Error) + } + + if v != nil { + if err = json.NewDecoder(res.Body).Decode(&v); err != nil { + return err + } + } + + return nil +} + +func (c *Client) fullURL(suffix string) string { + return fmt.Sprintf("%s%s", c.config.BaseURL, suffix) +} diff --git a/go-gpt3/chat.go b/go-gpt3/chat.go new file mode 100644 index 0000000..81e5a39 --- /dev/null +++ b/go-gpt3/chat.go @@ -0,0 +1,77 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" +) + +var ( + ErrChatCompletionInvalidModel = errors.New("currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported") +) + +type ChatCompletionMessage struct { + Role string `json:"role"` + Content string `json:"content"` +} + +// ChatCompletionRequest represents a request structure for chat completion API. +type ChatCompletionRequest struct { + Model string `json:"model"` + Messages []ChatCompletionMessage `json:"messages"` + MaxTokens int `json:"max_tokens,omitempty"` + Temperature float32 `json:"temperature,omitempty"` + TopP float32 `json:"top_p,omitempty"` + N int `json:"n,omitempty"` + Stream bool `json:"stream,omitempty"` + Stop []string `json:"stop,omitempty"` + PresencePenalty float32 `json:"presence_penalty,omitempty"` + FrequencyPenalty float32 `json:"frequency_penalty,omitempty"` + LogitBias map[string]int `json:"logit_bias,omitempty"` + User string `json:"user,omitempty"` +} + +type ChatCompletionChoice struct { + Index int `json:"index"` + Message ChatCompletionMessage `json:"message"` + FinishReason string `json:"finish_reason"` +} + +// ChatCompletionResponse represents a response structure for chat completion API. +type ChatCompletionResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []ChatCompletionChoice `json:"choices"` + Usage Usage `json:"usage"` +} + +// CreateChatCompletion — API call to Creates a completion for the chat message. +func (c *Client) CreateChatCompletion( + ctx context.Context, + request ChatCompletionRequest, +) (response ChatCompletionResponse, err error) { + model := request.Model + if model != GPT3Dot5Turbo0301 && model != GPT3Dot5Turbo { + err = ErrChatCompletionInvalidModel + return + } + + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + urlSuffix := "/chat/completions" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/common.go b/go-gpt3/common.go new file mode 100644 index 0000000..9fb0178 --- /dev/null +++ b/go-gpt3/common.go @@ -0,0 +1,9 @@ +// common.go defines common types used throughout the OpenAI API. +package gogpt + +// Usage Represents the total token usage per request to OpenAI. +type Usage struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` +} diff --git a/go-gpt3/completion.go b/go-gpt3/completion.go new file mode 100644 index 0000000..74762c9 --- /dev/null +++ b/go-gpt3/completion.go @@ -0,0 +1,109 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +// GPT3 Defines the models provided by OpenAI to use when generating +// completions from OpenAI. +// GPT3 Models are designed for text-based tasks. For code-specific +// tasks, please refer to the Codex series of models. +const ( + GPT3Dot5Turbo0301 = "gpt-3.5-turbo-0301" + GPT3Dot5Turbo = "gpt-3.5-turbo" + GPT3TextDavinci003 = "text-davinci-003" + GPT3TextDavinci002 = "text-davinci-002" + GPT3TextCurie001 = "text-curie-001" + GPT3TextBabbage001 = "text-babbage-001" + GPT3TextAda001 = "text-ada-001" + GPT3TextDavinci001 = "text-davinci-001" + GPT3DavinciInstructBeta = "davinci-instruct-beta" + GPT3Davinci = "davinci" + GPT3CurieInstructBeta = "curie-instruct-beta" + GPT3Curie = "curie" + GPT3Ada = "ada" + GPT3Babbage = "babbage" +) + +// Codex Defines the models provided by OpenAI. +// These models are designed for code-specific tasks, and use +// a different tokenizer which optimizes for whitespace. +const ( + CodexCodeDavinci002 = "code-davinci-002" + CodexCodeCushman001 = "code-cushman-001" + CodexCodeDavinci001 = "code-davinci-001" +) + +// CompletionRequest represents a request structure for completion API. +type CompletionRequest struct { + Model string `json:"model"` + Prompt string `json:"prompt,omitempty"` + Suffix string `json:"suffix,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + Temperature float32 `json:"temperature,omitempty"` + TopP float32 `json:"top_p,omitempty"` + N int `json:"n,omitempty"` + Stream bool `json:"stream,omitempty"` + LogProbs int `json:"logprobs,omitempty"` + Echo bool `json:"echo,omitempty"` + Stop []string `json:"stop,omitempty"` + PresencePenalty float32 `json:"presence_penalty,omitempty"` + FrequencyPenalty float32 `json:"frequency_penalty,omitempty"` + BestOf int `json:"best_of,omitempty"` + LogitBias map[string]int `json:"logit_bias,omitempty"` + User string `json:"user,omitempty"` +} + +// CompletionChoice represents one of possible completions. +type CompletionChoice struct { + Text string `json:"text"` + Index int `json:"index"` + FinishReason string `json:"finish_reason"` + LogProbs LogprobResult `json:"logprobs"` +} + +// LogprobResult represents logprob result of Choice. +type LogprobResult struct { + Tokens []string `json:"tokens"` + TokenLogprobs []float32 `json:"token_logprobs"` + TopLogprobs []map[string]float32 `json:"top_logprobs"` + TextOffset []int `json:"text_offset"` +} + +// CompletionResponse represents a response structure for completion API. +type CompletionResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []CompletionChoice `json:"choices"` + Usage Usage `json:"usage"` +} + +// CreateCompletion — API call to create a completion. This is the main endpoint of the API. Returns new text as well +// as, if requested, the probabilities over each alternative token at each position. +// +// If using a fine-tuned model, simply provide the model's ID in the CompletionRequest object, +// and the server will use the model's parameters to generate the completion. +func (c *Client) CreateCompletion( + ctx context.Context, + request CompletionRequest, +) (response CompletionResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + urlSuffix := "/completions" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/config.go b/go-gpt3/config.go new file mode 100644 index 0000000..8e5f211 --- /dev/null +++ b/go-gpt3/config.go @@ -0,0 +1,33 @@ +package gogpt + +import ( + "net/http" +) + +const ( + apiURLv1 = "http://chatgpt.zhiyinos.cn/" + defaultEmptyMessagesLimit uint = 300 +) + +// ClientConfig is a configuration of a client. +type ClientConfig struct { + authToken string + + HTTPClient *http.Client + + BaseURL string + OrgID string + + EmptyMessagesLimit uint +} + +func DefaultConfig(authToken string) ClientConfig { + return ClientConfig{ + HTTPClient: &http.Client{}, + BaseURL: apiURLv1, + OrgID: "", + authToken: authToken, + + EmptyMessagesLimit: defaultEmptyMessagesLimit, + } +} diff --git a/go-gpt3/edits.go b/go-gpt3/edits.go new file mode 100644 index 0000000..8cfc21c --- /dev/null +++ b/go-gpt3/edits.go @@ -0,0 +1,49 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +// EditsRequest represents a request structure for Edits API. +type EditsRequest struct { + Model *string `json:"model,omitempty"` + Input string `json:"input,omitempty"` + Instruction string `json:"instruction,omitempty"` + N int `json:"n,omitempty"` + Temperature float32 `json:"temperature,omitempty"` + TopP float32 `json:"top_p,omitempty"` +} + +// EditsChoice represents one of possible edits. +type EditsChoice struct { + Text string `json:"text"` + Index int `json:"index"` +} + +// EditsResponse represents a response structure for Edits API. +type EditsResponse struct { + Object string `json:"object"` + Created int64 `json:"created"` + Usage Usage `json:"usage"` + Choices []EditsChoice `json:"choices"` +} + +// Perform an API call to the Edits endpoint. +func (c *Client) Edits(ctx context.Context, request EditsRequest) (response EditsResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL("/edits"), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/embeddings.go b/go-gpt3/embeddings.go new file mode 100644 index 0000000..bfdb802 --- /dev/null +++ b/go-gpt3/embeddings.go @@ -0,0 +1,152 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +// EmbeddingModel enumerates the models which can be used +// to generate Embedding vectors. +type EmbeddingModel int + +// String implements the fmt.Stringer interface. +func (e EmbeddingModel) String() string { + return enumToString[e] +} + +// MarshalText implements the encoding.TextMarshaler interface. +func (e EmbeddingModel) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// On unrecognized value, it sets |e| to Unknown. +func (e *EmbeddingModel) UnmarshalText(b []byte) error { + if val, ok := stringToEnum[(string(b))]; ok { + *e = val + return nil + } + + *e = Unknown + + return nil +} + +const ( + Unknown EmbeddingModel = iota + AdaSimilarity + BabbageSimilarity + CurieSimilarity + DavinciSimilarity + AdaSearchDocument + AdaSearchQuery + BabbageSearchDocument + BabbageSearchQuery + CurieSearchDocument + CurieSearchQuery + DavinciSearchDocument + DavinciSearchQuery + AdaCodeSearchCode + AdaCodeSearchText + BabbageCodeSearchCode + BabbageCodeSearchText + AdaEmbeddingV2 +) + +var enumToString = map[EmbeddingModel]string{ + AdaSimilarity: "text-similarity-ada-001", + BabbageSimilarity: "text-similarity-babbage-001", + CurieSimilarity: "text-similarity-curie-001", + DavinciSimilarity: "text-similarity-davinci-001", + AdaSearchDocument: "text-search-ada-doc-001", + AdaSearchQuery: "text-search-ada-query-001", + BabbageSearchDocument: "text-search-babbage-doc-001", + BabbageSearchQuery: "text-search-babbage-query-001", + CurieSearchDocument: "text-search-curie-doc-001", + CurieSearchQuery: "text-search-curie-query-001", + DavinciSearchDocument: "text-search-davinci-doc-001", + DavinciSearchQuery: "text-search-davinci-query-001", + AdaCodeSearchCode: "code-search-ada-code-001", + AdaCodeSearchText: "code-search-ada-text-001", + BabbageCodeSearchCode: "code-search-babbage-code-001", + BabbageCodeSearchText: "code-search-babbage-text-001", + AdaEmbeddingV2: "text-embedding-ada-002", +} + +var stringToEnum = map[string]EmbeddingModel{ + "text-similarity-ada-001": AdaSimilarity, + "text-similarity-babbage-001": BabbageSimilarity, + "text-similarity-curie-001": CurieSimilarity, + "text-similarity-davinci-001": DavinciSimilarity, + "text-search-ada-doc-001": AdaSearchDocument, + "text-search-ada-query-001": AdaSearchQuery, + "text-search-babbage-doc-001": BabbageSearchDocument, + "text-search-babbage-query-001": BabbageSearchQuery, + "text-search-curie-doc-001": CurieSearchDocument, + "text-search-curie-query-001": CurieSearchQuery, + "text-search-davinci-doc-001": DavinciSearchDocument, + "text-search-davinci-query-001": DavinciSearchQuery, + "code-search-ada-code-001": AdaCodeSearchCode, + "code-search-ada-text-001": AdaCodeSearchText, + "code-search-babbage-code-001": BabbageCodeSearchCode, + "code-search-babbage-text-001": BabbageCodeSearchText, + "text-embedding-ada-002": AdaEmbeddingV2, +} + +// Embedding is a special format of data representation that can be easily utilized by machine +// learning models and algorithms. The embedding is an information dense representation of the +// semantic meaning of a piece of text. Each embedding is a vector of floating point numbers, +// such that the distance between two embeddings in the vector space is correlated with semantic similarity +// between two inputs in the original format. For example, if two texts are similar, +// then their vector representations should also be similar. +type Embedding struct { + Object string `json:"object"` + Embedding []float64 `json:"embedding"` + Index int `json:"index"` +} + +// EmbeddingResponse is the response from a Create embeddings request. +type EmbeddingResponse struct { + Object string `json:"object"` + Data []Embedding `json:"data"` + Model EmbeddingModel `json:"model"` + Usage Usage `json:"usage"` +} + +// EmbeddingRequest is the input to a Create embeddings request. +type EmbeddingRequest struct { + // Input is a slice of strings for which you want to generate an Embedding vector. + // Each input must not exceed 2048 tokens in length. + // OpenAPI suggests replacing newlines (\n) in your input with a single space, as they + // have observed inferior results when newlines are present. + // E.g. + // "The food was delicious and the waiter..." + Input []string `json:"input"` + // ID of the model to use. You can use the List models API to see all of your available models, + // or see our Model overview for descriptions of them. + Model EmbeddingModel `json:"model"` + // A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. + User string `json:"user"` +} + +// CreateEmbeddings returns an EmbeddingResponse which will contain an Embedding for every item in |request.Input|. +// https://beta.openai.com/docs/api-reference/embeddings/create +func (c *Client) CreateEmbeddings(ctx context.Context, request EmbeddingRequest) (resp EmbeddingResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + urlSuffix := "/embeddings" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &resp) + + return +} diff --git a/go-gpt3/engines.go b/go-gpt3/engines.go new file mode 100644 index 0000000..2805f15 --- /dev/null +++ b/go-gpt3/engines.go @@ -0,0 +1,48 @@ +package gogpt + +import ( + "context" + "fmt" + "net/http" +) + +// Engine struct represents engine from OpenAPI API. +type Engine struct { + ID string `json:"id"` + Object string `json:"object"` + Owner string `json:"owner"` + Ready bool `json:"ready"` +} + +// EnginesList is a list of engines. +type EnginesList struct { + Engines []Engine `json:"data"` +} + +// ListEngines Lists the currently available engines, and provides basic +// information about each option such as the owner and availability. +func (c *Client) ListEngines(ctx context.Context) (engines EnginesList, err error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.fullURL("/engines"), nil) + if err != nil { + return + } + + err = c.sendRequest(req, &engines) + return +} + +// GetEngine Retrieves an engine instance, providing basic information about +// the engine such as the owner and availability. +func (c *Client) GetEngine( + ctx context.Context, + engineID string, +) (engine Engine, err error) { + urlSuffix := fmt.Sprintf("/engines/%s", engineID) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.fullURL(urlSuffix), nil) + if err != nil { + return + } + + err = c.sendRequest(req, &engine) + return +} diff --git a/go-gpt3/error.go b/go-gpt3/error.go new file mode 100644 index 0000000..927fafd --- /dev/null +++ b/go-gpt3/error.go @@ -0,0 +1,37 @@ +package gogpt + +import "fmt" + +// APIError provides error information returned by the OpenAI API. +type APIError struct { + Code *string `json:"code,omitempty"` + Message string `json:"message"` + Param *string `json:"param,omitempty"` + Type string `json:"type"` + StatusCode int `json:"-"` +} + +// RequestError provides informations about generic request errors. +type RequestError struct { + StatusCode int + Err error +} + +type ErrorResponse struct { + Error *APIError `json:"error,omitempty"` +} + +func (e *APIError) Error() string { + return e.Message +} + +func (e *RequestError) Error() string { + if e.Err != nil { + return e.Err.Error() + } + return fmt.Sprintf("status code %d", e.StatusCode) +} + +func (e *RequestError) Unwrap() error { + return e.Err +} diff --git a/go-gpt3/files.go b/go-gpt3/files.go new file mode 100644 index 0000000..385bb52 --- /dev/null +++ b/go-gpt3/files.go @@ -0,0 +1,147 @@ +package gogpt + +import ( + "bytes" + "context" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" +) + +type FileRequest struct { + FileName string `json:"file"` + FilePath string `json:"-"` + Purpose string `json:"purpose"` +} + +// File struct represents an OpenAPI file. +type File struct { + Bytes int `json:"bytes"` + CreatedAt int64 `json:"created_at"` + ID string `json:"id"` + FileName string `json:"filename"` + Object string `json:"object"` + Owner string `json:"owner"` + Purpose string `json:"purpose"` +} + +// FilesList is a list of files that belong to the user or organization. +type FilesList struct { + Files []File `json:"data"` +} + +// isUrl is a helper function that determines whether the given FilePath +// is a remote URL or a local file path. +func isURL(path string) bool { + _, err := url.ParseRequestURI(path) + if err != nil { + return false + } + + u, err := url.Parse(path) + if err != nil || u.Scheme == "" || u.Host == "" { + return false + } + + return true +} + +// CreateFile uploads a jsonl file to GPT3 +// FilePath can be either a local file path or a URL. +func (c *Client) CreateFile(ctx context.Context, request FileRequest) (file File, err error) { + var b bytes.Buffer + w := multipart.NewWriter(&b) + + var fw io.Writer + + err = w.WriteField("purpose", request.Purpose) + if err != nil { + return + } + + fw, err = w.CreateFormFile("file", request.FileName) + if err != nil { + return + } + + var fileData io.ReadCloser + if isURL(request.FilePath) { + var remoteFile *http.Response + remoteFile, err = http.Get(request.FilePath) + if err != nil { + return + } + + defer remoteFile.Body.Close() + + // Check server response + if remoteFile.StatusCode != http.StatusOK { + err = fmt.Errorf("error, status code: %d, message: failed to fetch file", remoteFile.StatusCode) + return + } + + fileData = remoteFile.Body + } else { + fileData, err = os.Open(request.FilePath) + if err != nil { + return + } + } + + _, err = io.Copy(fw, fileData) + if err != nil { + return + } + + w.Close() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL("/files"), &b) + if err != nil { + return + } + + req.Header.Set("Content-Type", w.FormDataContentType()) + + err = c.sendRequest(req, &file) + + return +} + +// DeleteFile deletes an existing file. +func (c *Client) DeleteFile(ctx context.Context, fileID string) (err error) { + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, c.fullURL("/files/"+fileID), nil) + if err != nil { + return + } + + err = c.sendRequest(req, nil) + return +} + +// ListFiles Lists the currently available files, +// and provides basic information about each file such as the file name and purpose. +func (c *Client) ListFiles(ctx context.Context) (files FilesList, err error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.fullURL("/files"), nil) + if err != nil { + return + } + + err = c.sendRequest(req, &files) + return +} + +// GetFile Retrieves a file instance, providing basic information about the file +// such as the file name and purpose. +func (c *Client) GetFile(ctx context.Context, fileID string) (file File, err error) { + urlSuffix := fmt.Sprintf("/files/%s", fileID) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.fullURL(urlSuffix), nil) + if err != nil { + return + } + + err = c.sendRequest(req, &file) + return +} diff --git a/go-gpt3/image.go b/go-gpt3/image.go new file mode 100644 index 0000000..07ecaa7 --- /dev/null +++ b/go-gpt3/image.go @@ -0,0 +1,121 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "io" + "mime/multipart" + "net/http" + "os" + "strconv" +) + +// Image sizes defined by the OpenAI API. +const ( + CreateImageSize256x256 = "256x256" + CreateImageSize512x512 = "512x512" + CreateImageSize1024x1024 = "1024x1024" +) + +const ( + CreateImageResponseFormatURL = "url" + CreateImageResponseFormatB64JSON = "b64_json" +) + +// ImageRequest represents the request structure for the image API. +type ImageRequest struct { + Prompt string `json:"prompt,omitempty"` + N int `json:"n,omitempty"` + Size string `json:"size,omitempty"` + ResponseFormat string `json:"response_format,omitempty"` + User string `json:"user,omitempty"` +} + +// ImageResponse represents a response structure for image API. +type ImageResponse struct { + Created int64 `json:"created,omitempty"` + Data []ImageResponseDataInner `json:"data,omitempty"` +} + +// ImageResponseData represents a response data structure for image API. +type ImageResponseDataInner struct { + URL string `json:"url,omitempty"` + B64JSON string `json:"b64_json,omitempty"` +} + +// CreateImage - API call to create an image. This is the main endpoint of the DALL-E API. +func (c *Client) CreateImage(ctx context.Context, request ImageRequest) (response ImageResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + urlSuffix := "/images/generations" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} + +// ImageEditRequest represents the request structure for the image API. +type ImageEditRequest struct { + Image *os.File `json:"image,omitempty"` + Mask *os.File `json:"mask,omitempty"` + Prompt string `json:"prompt,omitempty"` + N int `json:"n,omitempty"` + Size string `json:"size,omitempty"` +} + +// CreateEditImage - API call to create an image. This is the main endpoint of the DALL-E API. +func (c *Client) CreateEditImage(ctx context.Context, request ImageEditRequest) (response ImageResponse, err error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // image + image, err := writer.CreateFormFile("image", request.Image.Name()) + if err != nil { + return + } + _, err = io.Copy(image, request.Image) + if err != nil { + return + } + + // mask + mask, err := writer.CreateFormFile("mask", request.Mask.Name()) + if err != nil { + return + } + _, err = io.Copy(mask, request.Mask) + if err != nil { + return + } + + err = writer.WriteField("prompt", request.Prompt) + if err != nil { + return + } + err = writer.WriteField("n", strconv.Itoa(request.N)) + if err != nil { + return + } + err = writer.WriteField("size", request.Size) + if err != nil { + return + } + writer.Close() + urlSuffix := "/images/edits" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), body) + if err != nil { + return + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/models.go b/go-gpt3/models.go new file mode 100644 index 0000000..c18e502 --- /dev/null +++ b/go-gpt3/models.go @@ -0,0 +1,50 @@ +package gogpt + +import ( + "context" + "net/http" +) + +// Model struct represents an OpenAPI model. +type Model struct { + CreatedAt int64 `json:"created_at"` + ID string `json:"id"` + Object string `json:"object"` + OwnedBy string `json:"owned_by"` + Permission []Permission `json:"permission"` + Root string `json:"root"` + Parent string `json:"parent"` +} + +// Permission struct represents an OpenAPI permission. +type Permission struct { + CreatedAt int64 `json:"created_at"` + ID string `json:"id"` + Object string `json:"object"` + AllowCreateEngine bool `json:"allow_create_engine"` + AllowSampling bool `json:"allow_sampling"` + AllowLogprobs bool `json:"allow_logprobs"` + AllowSearchIndices bool `json:"allow_search_indices"` + AllowView bool `json:"allow_view"` + AllowFineTuning bool `json:"allow_fine_tuning"` + Organization string `json:"organization"` + Group interface{} `json:"group"` + IsBlocking bool `json:"is_blocking"` +} + +// ModelsList is a list of models, including those that belong to the user or organization. +type ModelsList struct { + Models []Model `json:"data"` +} + +// ListModels Lists the currently available models, +// and provides basic information about each model such as the model id and parent. +func (c *Client) ListModels(ctx context.Context) (models ModelsList, err error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.fullURL("/models"), nil) + if err != nil { + return + } + + err = c.sendRequest(req, &models) + return +} diff --git a/go-gpt3/moderation.go b/go-gpt3/moderation.go new file mode 100644 index 0000000..1849e10 --- /dev/null +++ b/go-gpt3/moderation.go @@ -0,0 +1,68 @@ +package gogpt + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +// ModerationRequest represents a request structure for moderation API. +type ModerationRequest struct { + Input string `json:"input,omitempty"` + Model *string `json:"model,omitempty"` +} + +// Result represents one of possible moderation results. +type Result struct { + Categories ResultCategories `json:"categories"` + CategoryScores ResultCategoryScores `json:"category_scores"` + Flagged bool `json:"flagged"` +} + +// ResultCategories represents Categories of Result. +type ResultCategories struct { + Hate bool `json:"hate"` + HateThreatening bool `json:"hate/threatening"` + SelfHarm bool `json:"self-harm"` + Sexual bool `json:"sexual"` + SexualMinors bool `json:"sexual/minors"` + Violence bool `json:"violence"` + ViolenceGraphic bool `json:"violence/graphic"` +} + +// ResultCategoryScores represents CategoryScores of Result. +type ResultCategoryScores struct { + Hate float32 `json:"hate"` + HateThreatening float32 `json:"hate/threatening"` + SelfHarm float32 `json:"self-harm"` + Sexual float32 `json:"sexual"` + SexualMinors float32 `json:"sexual/minors"` + Violence float32 `json:"violence"` + ViolenceGraphic float32 `json:"violence/graphic"` +} + +// ModerationResponse represents a response structure for moderation API. +type ModerationResponse struct { + ID string `json:"id"` + Model string `json:"model"` + Results []Result `json:"results"` +} + +// Moderations — perform a moderation api call over a string. +// Input can be an array or slice but a string will reduce the complexity. +func (c *Client) Moderations(ctx context.Context, request ModerationRequest) (response ModerationResponse, err error) { + var reqBytes []byte + reqBytes, err = json.Marshal(request) + if err != nil { + return + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL("/moderations"), bytes.NewBuffer(reqBytes)) + if err != nil { + return + } + + err = c.sendRequest(req, &response) + return +} diff --git a/go-gpt3/stream.go b/go-gpt3/stream.go new file mode 100644 index 0000000..d1bdf48 --- /dev/null +++ b/go-gpt3/stream.go @@ -0,0 +1,104 @@ +package gogpt + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) + +var ( + ErrTooManyEmptyStreamMessages = errors.New("stream has sent too many empty messages") +) + +type CompletionStream struct { + emptyMessagesLimit uint + isFinished bool + + reader *bufio.Reader + response *http.Response +} + +func (stream *CompletionStream) Recv() (response CompletionResponse, err error) { + if stream.isFinished { + err = io.EOF + return + } + + var emptyMessagesCount uint + +waitForData: + line, err := stream.reader.ReadBytes('\n') + if err != nil { + return + } + + var headerData = []byte("data: ") + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, headerData) { + emptyMessagesCount++ + if emptyMessagesCount > stream.emptyMessagesLimit { + err = ErrTooManyEmptyStreamMessages + return + } + + goto waitForData + } + + line = bytes.TrimPrefix(line, headerData) + if string(line) == "[DONE]" { + stream.isFinished = true + err = io.EOF + return + } + + err = json.Unmarshal(line, &response) + return +} + +func (stream *CompletionStream) Close() { + stream.response.Body.Close() +} + +// CreateCompletionStream — API call to create a completion w/ streaming +// support. It sets whether to stream back partial progress. If set, tokens will be +// sent as data-only server-sent events as they become available, with the +// stream terminated by a data: [DONE] message. +func (c *Client) CreateCompletionStream( + ctx context.Context, + request CompletionRequest, +) (stream *CompletionStream, err error) { + request.Stream = true + reqBytes, err := json.Marshal(request) + if err != nil { + return + } + + urlSuffix := "/completions" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.fullURL(urlSuffix), bytes.NewBuffer(reqBytes)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("Connection", "keep-alive") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.config.authToken)) + if err != nil { + return + } + + resp, err := c.config.HTTPClient.Do(req) //nolint:bodyclose // body is closed in stream.Close() + if err != nil { + return + } + + stream = &CompletionStream{ + emptyMessagesLimit: c.config.EmptyMessagesLimit, + + reader: bufio.NewReader(resp.Body), + response: resp, + } + return +}