@@ -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 |
@@ -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 |
@@ -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. |
@@ -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<target>\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" |
@@ -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) | |||
} | |||
} | |||
``` |
@@ -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 | |||
} |
@@ -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) | |||
} |
@@ -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 | |||
} |
@@ -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"` | |||
} |
@@ -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 | |||
} |
@@ -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, | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 | |||
} |