Showing preview only (558K chars total). Download the full file or copy to clipboard to get everything.
Repository: sashabaranov/go-openai
Branch: master
Commit: 5d7a276f4c0e
Files: 105
Total size: 529.2 KB
Directory structure:
gitextract_r1hjdmvj/
├── .codecov.yml
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── close-inactive-issues.yml
│ ├── integration-tests.yml
│ └── pr.yml
├── .gitignore
├── .golangci.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── api_integration_test.go
├── api_internal_test.go
├── assistant.go
├── assistant_test.go
├── audio.go
├── audio_api_test.go
├── audio_test.go
├── batch.go
├── batch_test.go
├── chat.go
├── chat_stream.go
├── chat_stream_test.go
├── chat_test.go
├── client.go
├── client_test.go
├── common.go
├── completion.go
├── completion_test.go
├── config.go
├── config_test.go
├── edits.go
├── edits_test.go
├── embeddings.go
├── embeddings_test.go
├── engines.go
├── engines_test.go
├── error.go
├── error_test.go
├── example_test.go
├── examples/
│ ├── README.md
│ ├── chatbot/
│ │ └── main.go
│ ├── completion/
│ │ └── main.go
│ ├── completion-with-tool/
│ │ └── main.go
│ ├── images/
│ │ └── main.go
│ └── voice-to-text/
│ └── main.go
├── files.go
├── files_api_test.go
├── files_test.go
├── fine_tunes.go
├── fine_tunes_test.go
├── fine_tuning_job.go
├── fine_tuning_job_test.go
├── go.mod
├── image.go
├── image_api_test.go
├── image_test.go
├── internal/
│ ├── error_accumulator.go
│ ├── error_accumulator_test.go
│ ├── form_builder.go
│ ├── form_builder_test.go
│ ├── marshaller.go
│ ├── marshaller_test.go
│ ├── request_builder.go
│ ├── request_builder_test.go
│ ├── test/
│ │ ├── checks/
│ │ │ ├── checks.go
│ │ │ └── checks_test.go
│ │ ├── failer.go
│ │ ├── failer_test.go
│ │ ├── helpers.go
│ │ ├── helpers_test.go
│ │ ├── server.go
│ │ └── server_test.go
│ ├── unmarshaler.go
│ └── unmarshaler_test.go
├── jsonschema/
│ ├── containsref_test.go
│ ├── json.go
│ ├── json_additional_test.go
│ ├── json_errors_test.go
│ ├── json_test.go
│ ├── validate.go
│ └── validate_test.go
├── messages.go
├── messages_test.go
├── models.go
├── models_test.go
├── moderation.go
├── moderation_test.go
├── openai_test.go
├── ratelimit.go
├── reasoning_validator.go
├── run.go
├── run_test.go
├── speech.go
├── speech_test.go
├── stream.go
├── stream_reader.go
├── stream_reader_test.go
├── stream_test.go
├── thread.go
├── thread_test.go
├── vector_store.go
└── vector_store_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .codecov.yml
================================================
coverage:
ignore:
- "examples/**"
- "internal/test/**"
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [sashabaranov, vvatanabe]
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
Your issue may already be reported!
Please search on the [issue tracker](https://github.com/sashabaranov/go-openai/issues) before creating one.
**Describe the bug**
A clear and concise description of what the bug is. If it's an API-related bug, please provide relevant endpoint(s).
**To Reproduce**
Steps to reproduce the behavior, including any relevant code snippets.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots/Logs**
If applicable, add screenshots to help explain your problem. For non-graphical issues, please provide any relevant logs or stack traces.
**Environment (please complete the following information):**
- go-openai version: [e.g. v1.12.0]
- Go version: [e.g. 1.18]
- OpenAI API version: [e.g. v1]
- OS: [e.g. Ubuntu 20.04]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
Your issue may already be reported!
Please search on the [issue tracker](https://github.com/sashabaranov/go-openai/issues) before creating one.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
A similar PR may already be submitted!
Please search among the [Pull request](https://github.com/sashabaranov/go-openai/pulls) before creating one.
If your changes introduce breaking changes, please prefix the title of your pull request with "[BREAKING_CHANGES]". This allows for clear identification of such changes in the 'What's Changed' section on the release page, making it developer-friendly.
Thanks for submitting a pull request! Please provide enough information so that others can review your pull request.
**Describe the change**
Please provide a clear and concise description of the changes you're proposing. Explain what problem it solves or what feature it adds.
**Provide OpenAI documentation link**
Provide a relevant API doc from https://platform.openai.com/docs/api-reference
**Describe your solution**
Describe how your changes address the problem or how they add the feature. This should include a brief description of your approach and any new libraries or dependencies you're using.
**Tests**
Briefly describe how you have tested these changes. If possible — please add integration tests.
**Additional context**
Add any other context or screenshots or logs about your pull request here. If the pull request relates to an open issue, please link to it.
Issue: #XXXX
================================================
FILE: .github/workflows/close-inactive-issues.yml
================================================
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
exempt-issue-labels: 'bug,enhancement'
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/integration-tests.yml
================================================
name: Integration tests
on:
push:
branches:
- master
jobs:
integration_tests:
name: Run integration tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Run integration tests
env:
OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }}
run: go test -v -tags=integration ./api_integration_test.go
================================================
FILE: .github/workflows/pr.yml
================================================
name: Sanity check
on:
- push
- pull_request
jobs:
prcheck:
name: Sanity check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Run vet
run: |
go vet -stdversion ./...
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v7
with:
version: v2.1.5
- name: Run tests
run: go test -race -covermode=atomic -coverprofile=coverage.out -v ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
================================================
FILE: .gitignore
================================================
# 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
# Generated by tests
test.mp3
================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
default: none
enable:
- asciicheck
- bidichk
- bodyclose
- contextcheck
- cyclop
- dupl
- durationcheck
- errcheck
- errname
- errorlint
- exhaustive
- forbidigo
- funlen
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- govet
- ineffassign
- lll
- makezero
- mnd
- nestif
- nilerr
- nilnil
- nolintlint
- nosprintfhostport
- predeclared
- promlinter
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- testpackage
- tparallel
- unconvert
- unparam
- unused
- usetesting
- wastedassign
- whitespace
settings:
cyclop:
max-complexity: 30
package-average: 10
errcheck:
check-type-assertions: true
funlen:
lines: 100
statements: 50
gocognit:
min-complexity: 20
gocritic:
settings:
captLocal:
paramsOnly: false
underef:
skipRecvDeref: false
gomodguard:
blocked:
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:
disable:
- fieldalignment
enable-all: true
settings:
shadow:
strict: true
mnd:
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
nakedret:
max-func-lines: 0
nolintlint:
require-explanation: true
require-specific: true
allow-no-explanation:
- funlen
- gocognit
- lll
rowserrcheck:
packages:
- github.com/jmoiron/sqlx
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- forbidigo
- mnd
- revive
path : ^examples/.*\.go$
- linters:
- lll
source: ^//\s*go:generate\s
- linters:
- godot
source: (noinspection|TODO)
- linters:
- gocritic
source: //noinspection
- linters:
- errorlint
source: ^\s+if _, ok := err\.\([^.]+\.InternalError\); ok {
- linters:
- bodyclose
- dupl
- funlen
- goconst
- gosec
- noctx
- wrapcheck
- staticcheck
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
issues:
max-same-issues: 50
formatters:
enable:
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines
## Overview
Thank you for your interest in contributing to the "Go OpenAI" project! By following this guideline, we hope to ensure that your contributions are made smoothly and efficiently. The Go OpenAI project is licensed under the [Apache 2.0 License](https://github.com/sashabaranov/go-openai/blob/master/LICENSE), and we welcome contributions through GitHub pull requests.
## Reporting Bugs
If you discover a bug, first check the [GitHub Issues page](https://github.com/sashabaranov/go-openai/issues) to see if the issue has already been reported. If you're reporting a new issue, please use the "Bug report" template and provide detailed information about the problem, including steps to reproduce it.
## Suggesting Features
If you want to suggest a new feature or improvement, first check the [GitHub Issues page](https://github.com/sashabaranov/go-openai/issues) to ensure a similar suggestion hasn't already been made. Use the "Feature request" template to provide a detailed description of your suggestion.
## Reporting Vulnerabilities
If you identify a security concern, please use the "Report a security vulnerability" template on the [GitHub Issues page](https://github.com/sashabaranov/go-openai/issues) to share the details. This report will only be viewable to repository maintainers. You will be credited if the advisory is published.
## Questions for Users
If you have questions, please utilize [StackOverflow](https://stackoverflow.com/) or the [GitHub Discussions page](https://github.com/sashabaranov/go-openai/discussions).
## Contributing Code
There might already be a similar pull requests submitted! Please search for [pull requests](https://github.com/sashabaranov/go-openai/pulls) before creating one.
### Requirements for Merging a Pull Request
The requirements to accept a pull request are as follows:
- Features not provided by the OpenAI API will not be accepted.
- The functionality of the feature must match that of the official OpenAI API.
- All pull requests should be written in Go according to common conventions, formatted with `goimports`, and free of warnings from tools like `golangci-lint`.
- Include tests and ensure all tests pass.
- Maintain test coverage without any reduction.
- All pull requests require approval from at least one Go OpenAI maintainer.
**Note:**
The merging method for pull requests in this repository is squash merge.
### Creating a Pull Request
- Fork the repository.
- Create a new branch and commit your changes.
- Push that branch to GitHub.
- Start a new Pull Request on GitHub. (Please use the pull request template to provide detailed information.)
**Note:**
If your changes introduce breaking changes, please prefix your pull request title with "[BREAKING_CHANGES]".
### Code Style
In this project, we adhere to the standard coding style of Go. Your code should maintain consistency with the rest of the codebase. To achieve this, please format your code using tools like `goimports` and resolve any syntax or style issues with `golangci-lint`.
**Run goimports:**
```
go install golang.org/x/tools/cmd/goimports@latest
```
```
goimports -w .
```
**Run golangci-lint:**
```
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
```
```
golangci-lint run --out-format=github-actions
```
### Unit Test
Please create or update tests relevant to your changes. Ensure all tests run successfully to verify that your modifications do not adversely affect other functionalities.
**Run test:**
```
go test -v ./...
```
### Integration Test
Integration tests are requested against the production version of the OpenAI API. These tests will verify that the library is properly coded against the actual behavior of the API, and will fail upon any incompatible change in the API.
**Notes:**
These tests send real network traffic to the OpenAI API and may reach rate limits. Temporary network problems may also cause the test to fail.
**Run integration test:**
```
OPENAI_TOKEN=XXX go test -v -tags=integration ./api_integration_test.go
```
If the `OPENAI_TOKEN` environment variable is not available, integration tests will be skipped.
---
We wholeheartedly welcome your active participation. Let's build an amazing project together!
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# Go OpenAI
[](https://pkg.go.dev/github.com/sashabaranov/go-openai)
[](https://goreportcard.com/report/github.com/sashabaranov/go-openai)
[](https://codecov.io/gh/sashabaranov/go-openai)
This library provides unofficial Go clients for [OpenAI API](https://platform.openai.com/). We support:
* ChatGPT 4o, o1
* GPT-3, GPT-4
* DALL·E 2, DALL·E 3, GPT Image 1
* Whisper
## Installation
```
go get github.com/sashabaranov/go-openai
```
Currently, go-openai requires Go version 1.18 or greater.
## Usage
### ChatGPT example usage:
```go
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
client := openai.NewClient("your token")
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
},
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}
fmt.Println(resp.Choices[0].Message.Content)
}
```
### Getting an OpenAI API Key:
1. Visit the OpenAI website at [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys).
2. If you don't have an account, click on "Sign Up" to create one. If you do, click "Log In".
3. Once logged in, navigate to your API key management page.
4. Click on "Create new secret key".
5. Enter a name for your new key, then click "Create secret key".
6. Your new API key will be displayed. Use this key to interact with the OpenAI API.
**Note:** Your API key is sensitive information. Do not share it with anyone.
### Other examples:
<details>
<summary>ChatGPT streaming completion</summary>
```go
package main
import (
"context"
"errors"
"fmt"
"io"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
req := openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
MaxTokens: 20,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Lorem ipsum",
},
},
Stream: true,
}
stream, err := c.CreateChatCompletionStream(ctx, req)
if err != nil {
fmt.Printf("ChatCompletionStream error: %v\n", err)
return
}
defer stream.Close()
fmt.Printf("Stream response: ")
for {
response, err := stream.Recv()
if errors.Is(err, io.EOF) {
fmt.Println("\nStream finished")
return
}
if err != nil {
fmt.Printf("\nStream error: %v\n", err)
return
}
fmt.Printf(response.Choices[0].Delta.Content)
}
}
```
</details>
<details>
<summary>GPT-3 completion</summary>
```go
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
req := openai.CompletionRequest{
Model: openai.GPT3Babbage002,
MaxTokens: 5,
Prompt: "Lorem ipsum",
}
resp, err := c.CreateCompletion(ctx, req)
if err != nil {
fmt.Printf("Completion error: %v\n", err)
return
}
fmt.Println(resp.Choices[0].Text)
}
```
</details>
<details>
<summary>GPT-3 streaming completion</summary>
```go
package main
import (
"errors"
"context"
"fmt"
"io"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
req := openai.CompletionRequest{
Model: openai.GPT3Babbage002,
MaxTokens: 5,
Prompt: "Lorem ipsum",
Stream: true,
}
stream, err := c.CreateCompletionStream(ctx, req)
if err != nil {
fmt.Printf("CompletionStream error: %v\n", err)
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)
}
}
```
</details>
<details>
<summary>Audio Speech-To-Text</summary>
```go
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
req := openai.AudioRequest{
Model: openai.Whisper1,
FilePath: "recording.mp3",
}
resp, err := c.CreateTranscription(ctx, req)
if err != nil {
fmt.Printf("Transcription error: %v\n", err)
return
}
fmt.Println(resp.Text)
}
```
</details>
<details>
<summary>Audio Captions</summary>
```go
package main
import (
"context"
"fmt"
"os"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient(os.Getenv("OPENAI_KEY"))
req := openai.AudioRequest{
Model: openai.Whisper1,
FilePath: os.Args[1],
Format: openai.AudioResponseFormatSRT,
}
resp, err := c.CreateTranscription(context.Background(), req)
if err != nil {
fmt.Printf("Transcription error: %v\n", err)
return
}
f, err := os.Create(os.Args[1] + ".srt")
if err != nil {
fmt.Printf("Could not open file: %v\n", err)
return
}
defer f.Close()
if _, err := f.WriteString(resp.Text); err != nil {
fmt.Printf("Error writing to file: %v\n", err)
return
}
}
```
</details>
<details>
<summary>DALL-E 2 image generation</summary>
```go
package main
import (
"bytes"
"context"
"encoding/base64"
"fmt"
openai "github.com/sashabaranov/go-openai"
"image/png"
"os"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
// Sample image by link
reqUrl := openai.ImageRequest{
Prompt: "Parrot on a skateboard performs a trick, cartoon style, natural light, high detail",
Size: openai.CreateImageSize256x256,
ResponseFormat: openai.CreateImageResponseFormatURL,
N: 1,
}
respUrl, err := c.CreateImage(ctx, reqUrl)
if err != nil {
fmt.Printf("Image creation error: %v\n", err)
return
}
fmt.Println(respUrl.Data[0].URL)
// Example image as base64
reqBase64 := openai.ImageRequest{
Prompt: "Portrait of a humanoid parrot in a classic costume, high detail, realistic light, unreal engine",
Size: openai.CreateImageSize256x256,
ResponseFormat: openai.CreateImageResponseFormatB64JSON,
N: 1,
}
respBase64, err := c.CreateImage(ctx, reqBase64)
if err != nil {
fmt.Printf("Image creation error: %v\n", err)
return
}
imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)
if err != nil {
fmt.Printf("Base64 decode error: %v\n", err)
return
}
r := bytes.NewReader(imgBytes)
imgData, err := png.Decode(r)
if err != nil {
fmt.Printf("PNG decode error: %v\n", err)
return
}
file, err := os.Create("example.png")
if err != nil {
fmt.Printf("File creation error: %v\n", err)
return
}
defer file.Close()
if err := png.Encode(file, imgData); err != nil {
fmt.Printf("PNG encode error: %v\n", err)
return
}
fmt.Println("The image was saved as example.png")
}
```
</details>
<details>
<summary>GPT Image 1 image generation</summary>
```go
package main
import (
"context"
"encoding/base64"
"fmt"
"os"
openai "github.com/sashabaranov/go-openai"
)
func main() {
c := openai.NewClient("your token")
ctx := context.Background()
req := openai.ImageRequest{
Prompt: "Parrot on a skateboard performing a trick. Large bold text \"SKATE MASTER\" banner at the bottom of the image. Cartoon style, natural light, high detail, 1:1 aspect ratio.",
Background: openai.CreateImageBackgroundOpaque,
Model: openai.CreateImageModelGptImage1,
Size: openai.CreateImageSize1024x1024,
N: 1,
Quality: openai.CreateImageQualityLow,
OutputCompression: 100,
OutputFormat: openai.CreateImageOutputFormatJPEG,
// Moderation: openai.CreateImageModerationLow,
// User: "",
}
resp, err := c.CreateImage(ctx, req)
if err != nil {
fmt.Printf("Image creation Image generation with GPT Image 1error: %v\n", err)
return
}
fmt.Println("Image Base64:", resp.Data[0].B64JSON)
// Decode the base64 data
imgBytes, err := base64.StdEncoding.DecodeString(resp.Data[0].B64JSON)
if err != nil {
fmt.Printf("Base64 decode error: %v\n", err)
return
}
// Write image to file
outputPath := "generated_image.jpg"
err = os.WriteFile(outputPath, imgBytes, 0644)
if err != nil {
fmt.Printf("Failed to write image file: %v\n", err)
return
}
fmt.Printf("The image was saved as %s\n", outputPath)
}
```
</details>
<details>
<summary>Configuring proxy</summary>
```go
config := openai.DefaultConfig("token")
proxyUrl, err := url.Parse("http://localhost:{port}")
if err != nil {
panic(err)
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyUrl),
}
config.HTTPClient = &http.Client{
Transport: transport,
}
c := openai.NewClientWithConfig(config)
```
See also: https://pkg.go.dev/github.com/sashabaranov/go-openai#ClientConfig
</details>
<details>
<summary>ChatGPT support context</summary>
```go
package main
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/sashabaranov/go-openai"
)
func main() {
client := openai.NewClient("your token")
messages := make([]openai.ChatCompletionMessage, 0)
reader := bufio.NewReader(os.Stdin)
fmt.Println("Conversation")
fmt.Println("---------------------")
for {
fmt.Print("-> ")
text, _ := reader.ReadString('\n')
// convert CRLF to LF
text = strings.Replace(text, "\n", "", -1)
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: text,
})
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: messages,
},
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
continue
}
content := resp.Choices[0].Message.Content
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleAssistant,
Content: content,
})
fmt.Println(content)
}
}
```
</details>
<details>
<summary>Azure OpenAI ChatGPT</summary>
```go
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
config := openai.DefaultAzureConfig("your Azure OpenAI Key", "https://your Azure OpenAI Endpoint")
// If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function
// config.AzureModelMapperFunc = func(model string) string {
// azureModelMapping := map[string]string{
// "gpt-3.5-turbo": "your gpt-3.5-turbo deployment name",
// }
// return azureModelMapping[model]
// }
client := openai.NewClientWithConfig(config)
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello Azure OpenAI!",
},
},
},
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}
fmt.Println(resp.Choices[0].Message.Content)
}
```
</details>
<details>
<summary>Embedding Semantic Similarity</summary>
```go
package main
import (
"context"
"log"
openai "github.com/sashabaranov/go-openai"
)
func main() {
client := openai.NewClient("your-token")
// Create an EmbeddingRequest for the user query
queryReq := openai.EmbeddingRequest{
Input: []string{"How many chucks would a woodchuck chuck"},
Model: openai.AdaEmbeddingV2,
}
// Create an embedding for the user query
queryResponse, err := client.CreateEmbeddings(context.Background(), queryReq)
if err != nil {
log.Fatal("Error creating query embedding:", err)
}
// Create an EmbeddingRequest for the target text
targetReq := openai.EmbeddingRequest{
Input: []string{"How many chucks would a woodchuck chuck if the woodchuck could chuck wood"},
Model: openai.AdaEmbeddingV2,
}
// Create an embedding for the target text
targetResponse, err := client.CreateEmbeddings(context.Background(), targetReq)
if err != nil {
log.Fatal("Error creating target embedding:", err)
}
// Now that we have the embeddings for the user query and the target text, we
// can calculate their similarity.
queryEmbedding := queryResponse.Data[0]
targetEmbedding := targetResponse.Data[0]
similarity, err := queryEmbedding.DotProduct(&targetEmbedding)
if err != nil {
log.Fatal("Error calculating dot product:", err)
}
log.Printf("The similarity score between the query and the target is %f", similarity)
}
```
</details>
<details>
<summary>Azure OpenAI Embeddings</summary>
```go
package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
config := openai.DefaultAzureConfig("your Azure OpenAI Key", "https://your Azure OpenAI Endpoint")
config.APIVersion = "2023-05-15" // optional update to latest API version
//If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function
//config.AzureModelMapperFunc = func(model string) string {
// azureModelMapping := map[string]string{
// "gpt-3.5-turbo":"your gpt-3.5-turbo deployment name",
// }
// return azureModelMapping[model]
//}
input := "Text to vectorize"
client := openai.NewClientWithConfig(config)
resp, err := client.CreateEmbeddings(
context.Background(),
openai.EmbeddingRequest{
Input: []string{input},
Model: openai.AdaEmbeddingV2,
})
if err != nil {
fmt.Printf("CreateEmbeddings error: %v\n", err)
return
}
vectors := resp.Data[0].Embedding // []float32 with 1536 dimensions
fmt.Println(vectors[:10], "...", vectors[len(vectors)-10:])
}
```
</details>
<details>
<summary>JSON Schema for function calling</summary>
It is now possible for chat completion to choose to call a function for more information ([see developer docs here](https://platform.openai.com/docs/guides/gpt/function-calling)).
In order to describe the type of functions that can be called, a JSON schema must be provided. Many JSON schema libraries exist and are more advanced than what we can offer in this library, however we have included a simple `jsonschema` package for those who want to use this feature without formatting their own JSON schema payload.
The developer documents give this JSON schema definition as an example:
```json
{
"name":"get_current_weather",
"description":"Get the current weather in a given location",
"parameters":{
"type":"object",
"properties":{
"location":{
"type":"string",
"description":"The city and state, e.g. San Francisco, CA"
},
"unit":{
"type":"string",
"enum":[
"celsius",
"fahrenheit"
]
}
},
"required":[
"location"
]
}
}
```
Using the `jsonschema` package, this schema could be created using structs as such:
```go
FunctionDefinition{
Name: "get_current_weather",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit"},
},
},
Required: []string{"location"},
},
}
```
The `Parameters` field of a `FunctionDefinition` can accept either of the above styles, or even a nested struct from another library (as long as it can be marshalled into JSON).
</details>
<details>
<summary>Error handling</summary>
Open-AI maintains clear documentation on how to [handle API errors](https://platform.openai.com/docs/guides/error-codes/api-errors)
example:
```
e := &openai.APIError{}
if errors.As(err, &e) {
switch e.HTTPStatusCode {
case 401:
// invalid auth or key (do not retry)
case 429:
// rate limiting or engine overload (wait and retry)
case 500:
// openai server error (retry)
default:
// unhandled
}
}
```
</details>
<details>
<summary>Fine Tune Model</summary>
```go
package main
import (
"context"
"fmt"
"github.com/sashabaranov/go-openai"
)
func main() {
client := openai.NewClient("your token")
ctx := context.Background()
// create a .jsonl file with your training data for conversational model
// {"prompt": "<prompt text>", "completion": "<ideal generated text>"}
// {"prompt": "<prompt text>", "completion": "<ideal generated text>"}
// {"prompt": "<prompt text>", "completion": "<ideal generated text>"}
// chat models are trained using the following file format:
// {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]}
// {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]}
// {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]}
// you can use openai cli tool to validate the data
// For more info - https://platform.openai.com/docs/guides/fine-tuning
file, err := client.CreateFile(ctx, openai.FileRequest{
FilePath: "training_prepared.jsonl",
Purpose: "fine-tune",
})
if err != nil {
fmt.Printf("Upload JSONL file error: %v\n", err)
return
}
// create a fine tuning job
// Streams events until the job is done (this often takes minutes, but can take hours if there are many jobs in the queue or your dataset is large)
// use below get method to know the status of your model
fineTuningJob, err := client.CreateFineTuningJob(ctx, openai.FineTuningJobRequest{
TrainingFile: file.ID,
Model: "davinci-002", // gpt-3.5-turbo-0613, babbage-002.
})
if err != nil {
fmt.Printf("Creating new fine tune model error: %v\n", err)
return
}
fineTuningJob, err = client.RetrieveFineTuningJob(ctx, fineTuningJob.ID)
if err != nil {
fmt.Printf("Getting fine tune model error: %v\n", err)
return
}
fmt.Println(fineTuningJob.FineTunedModel)
// once the status of fineTuningJob is `succeeded`, you can use your fine tune model in Completion Request or Chat Completion Request
// resp, err := client.CreateCompletion(ctx, openai.CompletionRequest{
// Model: fineTuningJob.FineTunedModel,
// Prompt: "your prompt",
// })
// if err != nil {
// fmt.Printf("Create completion error %v\n", err)
// return
// }
//
// fmt.Println(resp.Choices[0].Text)
}
```
</details>
<details>
<summary>Structured Outputs</summary>
```go
package main
import (
"context"
"fmt"
"log"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
)
func main() {
client := openai.NewClient("your token")
ctx := context.Background()
type Result struct {
Steps []struct {
Explanation string `json:"explanation"`
Output string `json:"output"`
} `json:"steps"`
FinalAnswer string `json:"final_answer"`
}
var result Result
schema, err := jsonschema.GenerateSchemaForType(result)
if err != nil {
log.Fatalf("GenerateSchemaForType error: %v", err)
}
resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "You are a helpful math tutor. Guide the user through the solution step by step.",
},
{
Role: openai.ChatMessageRoleUser,
Content: "how can I solve 8x + 7 = -23",
},
},
ResponseFormat: &openai.ChatCompletionResponseFormat{
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
Name: "math_reasoning",
Schema: schema,
Strict: true,
},
},
})
if err != nil {
log.Fatalf("CreateChatCompletion error: %v", err)
}
err = schema.Unmarshal(resp.Choices[0].Message.Content, &result)
if err != nil {
log.Fatalf("Unmarshal schema error: %v", err)
}
fmt.Println(result)
}
```
</details>
See the `examples/` folder for more.
## Frequently Asked Questions
### Why don't we get the same answer when specifying a temperature field of 0 and asking the same question?
Even when specifying a temperature field of 0, it doesn't guarantee that you'll always get the same response. Several factors come into play.
1. Go OpenAI Behavior: When you specify a temperature field of 0 in Go OpenAI, the omitempty tag causes that field to be removed from the request. Consequently, the OpenAI API applies the default value of 1.
2. Token Count for Input/Output: If there's a large number of tokens in the input and output, setting the temperature to 0 can still result in non-deterministic behavior. In particular, when using around 32k tokens, the likelihood of non-deterministic behavior becomes highest even with a temperature of 0.
Due to the factors mentioned above, different answers may be returned even for the same question.
**Workarounds:**
1. As of November 2023, use [the new `seed` parameter](https://platform.openai.com/docs/guides/text-generation/reproducible-outputs) in conjunction with the `system_fingerprint` response field, alongside Temperature management.
2. Try using `math.SmallestNonzeroFloat32`: By specifying `math.SmallestNonzeroFloat32` in the temperature field instead of 0, you can mimic the behavior of setting it to 0.
3. Limiting Token Count: By limiting the number of tokens in the input and output and especially avoiding large requests close to 32k tokens, you can reduce the risk of non-deterministic behavior.
By adopting these strategies, you can expect more consistent results.
**Related Issues:**
[omitempty option of request struct will generate incorrect request when parameter is 0.](https://github.com/sashabaranov/go-openai/issues/9)
### Does Go OpenAI provide a method to count tokens?
No, Go OpenAI does not offer a feature to count tokens, and there are no plans to provide such a feature in the future. However, if there's a way to implement a token counting feature with zero dependencies, it might be possible to merge that feature into Go OpenAI. Otherwise, it would be more appropriate to implement it in a dedicated library or repository.
For counting tokens, you might find the following links helpful:
- [Counting Tokens For Chat API Calls](https://github.com/pkoukk/tiktoken-go#counting-tokens-for-chat-api-calls)
- [How to count tokens with tiktoken](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb)
**Related Issues:**
[Is it possible to join the implementation of GPT3 Tokenizer](https://github.com/sashabaranov/go-openai/issues/62)
## Contributing
By following [Contributing Guidelines](https://github.com/sashabaranov/go-openai/blob/master/CONTRIBUTING.md), we hope to ensure that your contributions are made smoothly and efficiently.
## Thank you
We want to take a moment to express our deepest gratitude to the [contributors](https://github.com/sashabaranov/go-openai/graphs/contributors) and sponsors of this project:
- [Carson Kahn](https://carsonkahn.com) of [Spindle AI](https://spindleai.com)
To all of you: thank you. You've helped us achieve more than we ever imagined possible. Can't wait to see where we go next, together!
================================================
FILE: api_integration_test.go
================================================
//go:build integration
package openai_test
import (
"context"
"encoding/json"
"errors"
"io"
"os"
"testing"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test/checks"
"github.com/sashabaranov/go-openai/jsonschema"
)
func TestAPI(t *testing.T) {
apiToken := os.Getenv("OPENAI_TOKEN")
if apiToken == "" {
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
}
var err error
c := openai.NewClient(apiToken)
ctx := context.Background()
_, err = c.ListEngines(ctx)
checks.NoError(t, err, "ListEngines error")
_, err = c.GetEngine(ctx, openai.GPT3Davinci002)
checks.NoError(t, err, "GetEngine error")
fileRes, err := c.ListFiles(ctx)
checks.NoError(t, err, "ListFiles error")
if len(fileRes.Files) > 0 {
_, err = c.GetFile(ctx, fileRes.Files[0].ID)
checks.NoError(t, err, "GetFile error")
} // else skip
embeddingReq := openai.EmbeddingRequest{
Input: []string{
"The food was delicious and the waiter",
"Other examples of embedding request",
},
Model: openai.AdaEmbeddingV2,
}
_, err = c.CreateEmbeddings(ctx, embeddingReq)
checks.NoError(t, err, "Embedding error")
_, err = c.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
},
)
checks.NoError(t, err, "CreateChatCompletion (without name) returned error")
_, err = c.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Name: "John_Doe",
Content: "Hello!",
},
},
},
)
checks.NoError(t, err, "CreateChatCompletion (with name) returned error")
_, err = c.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "What is the weather like in Boston?",
},
},
Functions: []openai.FunctionDefinition{{
Name: "get_current_weather",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit"},
},
},
Required: []string{"location"},
},
}},
},
)
checks.NoError(t, err, "CreateChatCompletion (with functions) returned error")
}
func TestCompletionStream(t *testing.T) {
apiToken := os.Getenv("OPENAI_TOKEN")
if apiToken == "" {
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
}
c := openai.NewClient(apiToken)
ctx := context.Background()
stream, err := c.CreateCompletionStream(ctx, openai.CompletionRequest{
Prompt: "Ex falso quodlibet",
Model: openai.GPT3Babbage002,
MaxTokens: 5,
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
counter := 0
for {
_, err = stream.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
t.Errorf("Stream error: %v", err)
} else {
counter++
}
}
if counter == 0 {
t.Error("Stream did not return any responses")
}
}
func TestAPIError(t *testing.T) {
apiToken := os.Getenv("OPENAI_TOKEN")
if apiToken == "" {
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
}
var err error
c := openai.NewClient(apiToken + "_invalid")
ctx := context.Background()
_, err = c.ListEngines(ctx)
checks.HasError(t, err, "ListEngines should fail with an invalid key")
var apiErr *openai.APIError
if !errors.As(err, &apiErr) {
t.Fatalf("Error is not an APIError: %+v", err)
}
if apiErr.HTTPStatusCode != 401 {
t.Fatalf("Unexpected API error status code: %d", apiErr.HTTPStatusCode)
}
switch v := apiErr.Code.(type) {
case string:
if v != "invalid_api_key" {
t.Fatalf("Unexpected API error code: %s", v)
}
default:
t.Fatalf("Unexpected API error code type: %T", v)
}
if apiErr.Error() == "" {
t.Fatal("Empty error message occurred")
}
}
func TestChatCompletionResponseFormat_JSONSchema(t *testing.T) {
apiToken := os.Getenv("OPENAI_TOKEN")
if apiToken == "" {
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
}
var err error
c := openai.NewClient(apiToken)
ctx := context.Background()
type MyStructuredResponse struct {
PascalCase string `json:"pascal_case" required:"true" description:"PascalCase"`
CamelCase string `json:"camel_case" required:"true" description:"CamelCase"`
KebabCase string `json:"kebab_case" required:"true" description:"KebabCase"`
SnakeCase string `json:"snake_case" required:"true" description:"SnakeCase"`
}
var result MyStructuredResponse
schema, err := jsonschema.GenerateSchemaForType(result)
if err != nil {
t.Fatal("CreateChatCompletion (use json_schema response) GenerateSchemaForType error")
}
resp, err := c.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "Please enter a string, and we will convert it into the following naming conventions:" +
"1. PascalCase: Each word starts with an uppercase letter, with no spaces or separators." +
"2. CamelCase: The first word starts with a lowercase letter, " +
"and subsequent words start with an uppercase letter, with no spaces or separators." +
"3. KebabCase: All letters are lowercase, with words separated by hyphens `-`." +
"4. SnakeCase: All letters are lowercase, with words separated by underscores `_`.",
},
{
Role: openai.ChatMessageRoleUser,
Content: "Hello World",
},
},
ResponseFormat: &openai.ChatCompletionResponseFormat{
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
Name: "cases",
Schema: schema,
Strict: true,
},
},
},
)
checks.NoError(t, err, "CreateChatCompletion (use json_schema response) returned error")
if err == nil {
err = schema.Unmarshal(resp.Choices[0].Message.Content, &result)
checks.NoError(t, err, "CreateChatCompletion (use json_schema response) unmarshal error")
}
}
func TestChatCompletionStructuredOutputsFunctionCalling(t *testing.T) {
apiToken := os.Getenv("OPENAI_TOKEN")
if apiToken == "" {
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
}
var err error
c := openai.NewClient(apiToken)
ctx := context.Background()
resp, err := c.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleSystem,
Content: "Please enter a string, and we will convert it into the following naming conventions:" +
"1. PascalCase: Each word starts with an uppercase letter, with no spaces or separators." +
"2. CamelCase: The first word starts with a lowercase letter, " +
"and subsequent words start with an uppercase letter, with no spaces or separators." +
"3. KebabCase: All letters are lowercase, with words separated by hyphens `-`." +
"4. SnakeCase: All letters are lowercase, with words separated by underscores `_`.",
},
{
Role: openai.ChatMessageRoleUser,
Content: "Hello World",
},
},
Tools: []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: &openai.FunctionDefinition{
Name: "display_cases",
Strict: true,
Parameters: &jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"PascalCase": {
Type: jsonschema.String,
},
"CamelCase": {
Type: jsonschema.String,
},
"KebabCase": {
Type: jsonschema.String,
},
"SnakeCase": {
Type: jsonschema.String,
},
},
Required: []string{"PascalCase", "CamelCase", "KebabCase", "SnakeCase"},
AdditionalProperties: false,
},
},
},
},
ToolChoice: openai.ToolChoice{
Type: openai.ToolTypeFunction,
Function: openai.ToolFunction{
Name: "display_cases",
},
},
},
)
checks.NoError(t, err, "CreateChatCompletion (use structured outputs response) returned error")
var result = make(map[string]string)
err = json.Unmarshal([]byte(resp.Choices[0].Message.ToolCalls[0].Function.Arguments), &result)
checks.NoError(t, err, "CreateChatCompletion (use structured outputs response) unmarshal error")
for _, key := range []string{"PascalCase", "CamelCase", "KebabCase", "SnakeCase"} {
if _, ok := result[key]; !ok {
t.Errorf("key:%s does not exist.", key)
}
}
}
================================================
FILE: api_internal_test.go
================================================
package openai
import (
"context"
"testing"
)
func TestOpenAIFullURL(t *testing.T) {
cases := []struct {
Name string
Suffix string
Expect string
}{
{
"ChatCompletionsURL",
"/chat/completions",
"https://api.openai.com/v1/chat/completions",
},
{
"CompletionsURL",
"/completions",
"https://api.openai.com/v1/completions",
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
az := DefaultConfig("dummy")
cli := NewClientWithConfig(az)
actual := cli.fullURL(c.Suffix)
if actual != c.Expect {
t.Errorf("Expected %s, got %s", c.Expect, actual)
}
t.Logf("Full URL: %s", actual)
})
}
}
func TestRequestAuthHeader(t *testing.T) {
cases := []struct {
Name string
APIType APIType
HeaderKey string
Token string
OrgID string
Expect string
}{
{
"OpenAIDefault",
"",
"Authorization",
"dummy-token-openai",
"",
"Bearer dummy-token-openai",
},
{
"OpenAIOrg",
APITypeOpenAI,
"Authorization",
"dummy-token-openai",
"dummy-org-openai",
"Bearer dummy-token-openai",
},
{
"OpenAI",
APITypeOpenAI,
"Authorization",
"dummy-token-openai",
"",
"Bearer dummy-token-openai",
},
{
"AzureAD",
APITypeAzureAD,
"Authorization",
"dummy-token-azure",
"",
"Bearer dummy-token-azure",
},
{
"Azure",
APITypeAzure,
AzureAPIKeyHeader,
"dummy-api-key-here",
"",
"dummy-api-key-here",
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
az := DefaultConfig(c.Token)
az.APIType = c.APIType
az.OrgID = c.OrgID
cli := NewClientWithConfig(az)
req, err := cli.newRequest(context.Background(), "POST", "/chat/completions")
if err != nil {
t.Errorf("Failed to create request: %v", err)
}
actual := req.Header.Get(c.HeaderKey)
if actual != c.Expect {
t.Errorf("Expected %s, got %s", c.Expect, actual)
}
t.Logf("%s: %s", c.HeaderKey, actual)
})
}
}
func TestAzureFullURL(t *testing.T) {
cases := []struct {
Name string
BaseURL string
AzureModelMapper map[string]string
Suffix string
Model string
Expect string
}{
{
"AzureBaseURLWithSlashAutoStrip",
"https://httpbin.org/",
nil,
"/chat/completions",
"chatgpt-demo",
"https://httpbin.org/" +
"openai/deployments/chatgpt-demo" +
"/chat/completions?api-version=2023-05-15",
},
{
"AzureBaseURLWithoutSlashOK",
"https://httpbin.org",
nil,
"/chat/completions",
"chatgpt-demo",
"https://httpbin.org/" +
"openai/deployments/chatgpt-demo" +
"/chat/completions?api-version=2023-05-15",
},
{
"",
"https://httpbin.org",
nil,
"/assistants?limit=10",
"chatgpt-demo",
"https://httpbin.org/openai/assistants?api-version=2023-05-15&limit=10",
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
az := DefaultAzureConfig("dummy", c.BaseURL)
cli := NewClientWithConfig(az)
// /openai/deployments/{engine}/chat/completions?api-version={api_version}
actual := cli.fullURL(c.Suffix, withModel(c.Model))
if actual != c.Expect {
t.Errorf("Expected %s, got %s", c.Expect, actual)
}
t.Logf("Full URL: %s", actual)
})
}
}
func TestCloudflareAzureFullURL(t *testing.T) {
cases := []struct {
Name string
BaseURL string
Suffix string
Expect string
}{
{
"CloudflareAzureBaseURLWithSlashAutoStrip",
"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo/",
"/chat/completions",
"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo/" +
"chat/completions?api-version=2023-05-15",
},
{
"",
"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo",
"/assistants?limit=10",
"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo" +
"/assistants?api-version=2023-05-15&limit=10",
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
az := DefaultAzureConfig("dummy", c.BaseURL)
az.APIType = APITypeCloudflareAzure
cli := NewClientWithConfig(az)
actual := cli.fullURL(c.Suffix)
if actual != c.Expect {
t.Errorf("Expected %s, got %s", c.Expect, actual)
}
t.Logf("Full URL: %s", actual)
})
}
}
================================================
FILE: assistant.go
================================================
package openai
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
const (
assistantsSuffix = "/assistants"
assistantsFilesSuffix = "/files"
)
type Assistant struct {
ID string `json:"id"`
Object string `json:"object"`
CreatedAt int64 `json:"created_at"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Model string `json:"model"`
Instructions *string `json:"instructions,omitempty"`
Tools []AssistantTool `json:"tools"`
ToolResources *AssistantToolResource `json:"tool_resources,omitempty"`
FileIDs []string `json:"file_ids,omitempty"` // Deprecated in v2
Metadata map[string]any `json:"metadata,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
TopP *float32 `json:"top_p,omitempty"`
ResponseFormat any `json:"response_format,omitempty"`
httpHeader
}
type AssistantToolType string
const (
AssistantToolTypeCodeInterpreter AssistantToolType = "code_interpreter"
AssistantToolTypeRetrieval AssistantToolType = "retrieval"
AssistantToolTypeFunction AssistantToolType = "function"
AssistantToolTypeFileSearch AssistantToolType = "file_search"
)
type AssistantTool struct {
Type AssistantToolType `json:"type"`
Function *FunctionDefinition `json:"function,omitempty"`
}
type AssistantToolFileSearch struct {
VectorStoreIDs []string `json:"vector_store_ids"`
}
type AssistantToolCodeInterpreter struct {
FileIDs []string `json:"file_ids"`
}
type AssistantToolResource struct {
FileSearch *AssistantToolFileSearch `json:"file_search,omitempty"`
CodeInterpreter *AssistantToolCodeInterpreter `json:"code_interpreter,omitempty"`
}
// AssistantRequest provides the assistant request parameters.
// When modifying the tools the API functions as the following:
// If Tools is undefined, no changes are made to the Assistant's tools.
// If Tools is empty slice it will effectively delete all of the Assistant's tools.
// If Tools is populated, it will replace all of the existing Assistant's tools with the provided tools.
type AssistantRequest struct {
Model string `json:"model"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Instructions *string `json:"instructions,omitempty"`
Tools []AssistantTool `json:"-"`
FileIDs []string `json:"file_ids,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
ToolResources *AssistantToolResource `json:"tool_resources,omitempty"`
ResponseFormat any `json:"response_format,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
TopP *float32 `json:"top_p,omitempty"`
}
// MarshalJSON provides a custom marshaller for the assistant request to handle the API use cases
// If Tools is nil, the field is omitted from the JSON.
// If Tools is an empty slice, it's included in the JSON as an empty array ([]).
// If Tools is populated, it's included in the JSON with the elements.
func (a AssistantRequest) MarshalJSON() ([]byte, error) {
type Alias AssistantRequest
assistantAlias := &struct {
Tools *[]AssistantTool `json:"tools,omitempty"`
*Alias
}{
Alias: (*Alias)(&a),
}
if a.Tools != nil {
assistantAlias.Tools = &a.Tools
}
return json.Marshal(assistantAlias)
}
// AssistantsList is a list of assistants.
type AssistantsList struct {
Assistants []Assistant `json:"data"`
LastID *string `json:"last_id"`
FirstID *string `json:"first_id"`
HasMore bool `json:"has_more"`
httpHeader
}
type AssistantDeleteResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Deleted bool `json:"deleted"`
httpHeader
}
type AssistantFile struct {
ID string `json:"id"`
Object string `json:"object"`
CreatedAt int64 `json:"created_at"`
AssistantID string `json:"assistant_id"`
httpHeader
}
type AssistantFileRequest struct {
FileID string `json:"file_id"`
}
type AssistantFilesList struct {
AssistantFiles []AssistantFile `json:"data"`
httpHeader
}
// CreateAssistant creates a new assistant.
func (c *Client) CreateAssistant(ctx context.Context, request AssistantRequest) (response Assistant, err error) {
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(assistantsSuffix), withBody(request),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// RetrieveAssistant retrieves an assistant.
func (c *Client) RetrieveAssistant(
ctx context.Context,
assistantID string,
) (response Assistant, err error) {
urlSuffix := fmt.Sprintf("%s/%s", assistantsSuffix, assistantID)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// ModifyAssistant modifies an assistant.
func (c *Client) ModifyAssistant(
ctx context.Context,
assistantID string,
request AssistantRequest,
) (response Assistant, err error) {
urlSuffix := fmt.Sprintf("%s/%s", assistantsSuffix, assistantID)
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// DeleteAssistant deletes an assistant.
func (c *Client) DeleteAssistant(
ctx context.Context,
assistantID string,
) (response AssistantDeleteResponse, err error) {
urlSuffix := fmt.Sprintf("%s/%s", assistantsSuffix, assistantID)
req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// ListAssistants Lists the currently available assistants.
func (c *Client) ListAssistants(
ctx context.Context,
limit *int,
order *string,
after *string,
before *string,
) (response AssistantsList, err error) {
urlValues := url.Values{}
if limit != nil {
urlValues.Add("limit", fmt.Sprintf("%d", *limit))
}
if order != nil {
urlValues.Add("order", *order)
}
if after != nil {
urlValues.Add("after", *after)
}
if before != nil {
urlValues.Add("before", *before)
}
encodedValues := ""
if len(urlValues) > 0 {
encodedValues = "?" + urlValues.Encode()
}
urlSuffix := fmt.Sprintf("%s%s", assistantsSuffix, encodedValues)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// CreateAssistantFile creates a new assistant file.
func (c *Client) CreateAssistantFile(
ctx context.Context,
assistantID string,
request AssistantFileRequest,
) (response AssistantFile, err error) {
urlSuffix := fmt.Sprintf("%s/%s%s", assistantsSuffix, assistantID, assistantsFilesSuffix)
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),
withBody(request),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// RetrieveAssistantFile retrieves an assistant file.
func (c *Client) RetrieveAssistantFile(
ctx context.Context,
assistantID string,
fileID string,
) (response AssistantFile, err error) {
urlSuffix := fmt.Sprintf("%s/%s%s/%s", assistantsSuffix, assistantID, assistantsFilesSuffix, fileID)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// DeleteAssistantFile deletes an existing file.
func (c *Client) DeleteAssistantFile(
ctx context.Context,
assistantID string,
fileID string,
) (err error) {
urlSuffix := fmt.Sprintf("%s/%s%s/%s", assistantsSuffix, assistantID, assistantsFilesSuffix, fileID)
req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, nil)
return
}
// ListAssistantFiles Lists the currently available files for an assistant.
func (c *Client) ListAssistantFiles(
ctx context.Context,
assistantID string,
limit *int,
order *string,
after *string,
before *string,
) (response AssistantFilesList, err error) {
urlValues := url.Values{}
if limit != nil {
urlValues.Add("limit", fmt.Sprintf("%d", *limit))
}
if order != nil {
urlValues.Add("order", *order)
}
if after != nil {
urlValues.Add("after", *after)
}
if before != nil {
urlValues.Add("before", *before)
}
encodedValues := ""
if len(urlValues) > 0 {
encodedValues = "?" + urlValues.Encode()
}
urlSuffix := fmt.Sprintf("%s/%s%s%s", assistantsSuffix, assistantID, assistantsFilesSuffix, encodedValues)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),
withBetaAssistantVersion(c.config.AssistantVersion))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
================================================
FILE: assistant_test.go
================================================
package openai_test
import (
"context"
openai "github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test/checks"
"encoding/json"
"fmt"
"net/http"
"testing"
)
// TestAssistant Tests the assistant endpoint of the API using the mocked server.
func TestAssistant(t *testing.T) {
assistantID := "asst_abc123"
assistantName := "Ambrogio"
assistantDescription := "Ambrogio is a friendly assistant."
assistantInstructions := `You are a personal math tutor.
When asked a question, write and run Python code to answer the question.`
assistantFileID := "file-wB6RM6wHdA49HfS2DJ9fEyrH"
limit := 20
order := "desc"
after := "asst_abc122"
before := "asst_abc124"
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler(
"/v1/assistants/"+assistantID+"/files/"+assistantFileID,
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantFile{
ID: assistantFileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodDelete {
fmt.Fprintln(w, `{
id: "file-wB6RM6wHdA49HfS2DJ9fEyrH",
object: "assistant.file.deleted",
deleted: true
}`)
}
},
)
server.RegisterHandler(
"/v1/assistants/"+assistantID+"/files",
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantFilesList{
AssistantFiles: []openai.AssistantFile{
{
ID: assistantFileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
},
},
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodPost {
var request openai.AssistantFileRequest
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.AssistantFile{
ID: request.FileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
})
fmt.Fprintln(w, string(resBytes))
}
},
)
server.RegisterHandler(
"/v1/assistants/"+assistantID,
func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: &assistantName,
Model: openai.GPT4TurboPreview,
Description: &assistantDescription,
Instructions: &assistantInstructions,
})
fmt.Fprintln(w, string(resBytes))
case http.MethodPost:
var request openai.Assistant
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: request.Name,
Model: request.Model,
Description: request.Description,
Instructions: request.Instructions,
Tools: request.Tools,
})
fmt.Fprintln(w, string(resBytes))
case http.MethodDelete:
fmt.Fprintln(w, `{
"id": "asst_abc123",
"object": "assistant.deleted",
"deleted": true
}`)
}
},
)
server.RegisterHandler(
"/v1/assistants",
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var request openai.AssistantRequest
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: request.Name,
Model: request.Model,
Description: request.Description,
Instructions: request.Instructions,
Tools: request.Tools,
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantsList{
LastID: &assistantID,
FirstID: &assistantID,
Assistants: []openai.Assistant{
{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: &assistantName,
Model: openai.GPT4TurboPreview,
Description: &assistantDescription,
Instructions: &assistantInstructions,
},
},
})
fmt.Fprintln(w, string(resBytes))
}
},
)
ctx := context.Background()
t.Run("create_assistant", func(t *testing.T) {
_, err := client.CreateAssistant(ctx, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
})
checks.NoError(t, err, "CreateAssistant error")
})
t.Run("retrieve_assistant", func(t *testing.T) {
_, err := client.RetrieveAssistant(ctx, assistantID)
checks.NoError(t, err, "RetrieveAssistant error")
})
t.Run("delete_assistant", func(t *testing.T) {
_, err := client.DeleteAssistant(ctx, assistantID)
checks.NoError(t, err, "DeleteAssistant error")
})
t.Run("list_assistant", func(t *testing.T) {
_, err := client.ListAssistants(ctx, &limit, &order, &after, &before)
checks.NoError(t, err, "ListAssistants error")
})
t.Run("create_assistant_file", func(t *testing.T) {
_, err := client.CreateAssistantFile(ctx, assistantID, openai.AssistantFileRequest{
FileID: assistantFileID,
})
checks.NoError(t, err, "CreateAssistantFile error")
})
t.Run("list_assistant_files", func(t *testing.T) {
_, err := client.ListAssistantFiles(ctx, assistantID, &limit, &order, &after, &before)
checks.NoError(t, err, "ListAssistantFiles error")
})
t.Run("retrieve_assistant_file", func(t *testing.T) {
_, err := client.RetrieveAssistantFile(ctx, assistantID, assistantFileID)
checks.NoError(t, err, "RetrieveAssistantFile error")
})
t.Run("delete_assistant_file", func(t *testing.T) {
err := client.DeleteAssistantFile(ctx, assistantID, assistantFileID)
checks.NoError(t, err, "DeleteAssistantFile error")
})
t.Run("modify_assistant_no_tools", func(t *testing.T) {
assistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
})
checks.NoError(t, err, "ModifyAssistant error")
if assistant.Tools != nil {
t.Errorf("expected nil got %v", assistant.Tools)
}
})
t.Run("modify_assistant_with_tools", func(t *testing.T) {
assistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
Tools: []openai.AssistantTool{{Type: openai.AssistantToolTypeFunction}},
})
checks.NoError(t, err, "ModifyAssistant error")
if assistant.Tools == nil || len(assistant.Tools) != 1 {
t.Errorf("expected a slice got %v", assistant.Tools)
}
})
t.Run("modify_assistant_empty_tools", func(t *testing.T) {
assistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
Tools: make([]openai.AssistantTool, 0),
})
checks.NoError(t, err, "ModifyAssistant error")
if assistant.Tools == nil {
t.Errorf("expected a slice got %v", assistant.Tools)
}
})
}
func TestAzureAssistant(t *testing.T) {
assistantID := "asst_abc123"
assistantName := "Ambrogio"
assistantDescription := "Ambrogio is a friendly assistant."
assistantInstructions := `You are a personal math tutor.
When asked a question, write and run Python code to answer the question.`
assistantFileID := "file-wB6RM6wHdA49HfS2DJ9fEyrH"
limit := 20
order := "desc"
after := "asst_abc122"
before := "asst_abc124"
client, server, teardown := setupAzureTestServer()
defer teardown()
server.RegisterHandler(
"/openai/assistants/"+assistantID+"/files/"+assistantFileID,
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantFile{
ID: assistantFileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodDelete {
fmt.Fprintln(w, `{
id: "file-wB6RM6wHdA49HfS2DJ9fEyrH",
object: "assistant.file.deleted",
deleted: true
}`)
}
},
)
server.RegisterHandler(
"/openai/assistants/"+assistantID+"/files",
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantFilesList{
AssistantFiles: []openai.AssistantFile{
{
ID: assistantFileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
},
},
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodPost {
var request openai.AssistantFileRequest
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.AssistantFile{
ID: request.FileID,
Object: "assistant.file",
CreatedAt: 1234567890,
AssistantID: assistantID,
})
fmt.Fprintln(w, string(resBytes))
}
},
)
server.RegisterHandler(
"/openai/assistants/"+assistantID,
func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: &assistantName,
Model: openai.GPT4TurboPreview,
Description: &assistantDescription,
Instructions: &assistantInstructions,
})
fmt.Fprintln(w, string(resBytes))
case http.MethodPost:
var request openai.AssistantRequest
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: request.Name,
Model: request.Model,
Description: request.Description,
Instructions: request.Instructions,
Tools: request.Tools,
})
fmt.Fprintln(w, string(resBytes))
case http.MethodDelete:
fmt.Fprintln(w, `{
"id": "asst_abc123",
"object": "assistant.deleted",
"deleted": true
}`)
}
},
)
server.RegisterHandler(
"/openai/assistants",
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var request openai.AssistantRequest
err := json.NewDecoder(r.Body).Decode(&request)
checks.NoError(t, err, "Decode error")
resBytes, _ := json.Marshal(openai.Assistant{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: request.Name,
Model: request.Model,
Description: request.Description,
Instructions: request.Instructions,
Tools: request.Tools,
})
fmt.Fprintln(w, string(resBytes))
} else if r.Method == http.MethodGet {
resBytes, _ := json.Marshal(openai.AssistantsList{
LastID: &assistantID,
FirstID: &assistantID,
Assistants: []openai.Assistant{
{
ID: assistantID,
Object: "assistant",
CreatedAt: 1234567890,
Name: &assistantName,
Model: openai.GPT4TurboPreview,
Description: &assistantDescription,
Instructions: &assistantInstructions,
},
},
})
fmt.Fprintln(w, string(resBytes))
}
},
)
ctx := context.Background()
_, err := client.CreateAssistant(ctx, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
})
checks.NoError(t, err, "CreateAssistant error")
_, err = client.RetrieveAssistant(ctx, assistantID)
checks.NoError(t, err, "RetrieveAssistant error")
_, err = client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{
Name: &assistantName,
Description: &assistantDescription,
Model: openai.GPT4TurboPreview,
Instructions: &assistantInstructions,
})
checks.NoError(t, err, "ModifyAssistant error")
_, err = client.DeleteAssistant(ctx, assistantID)
checks.NoError(t, err, "DeleteAssistant error")
_, err = client.ListAssistants(ctx, &limit, &order, &after, &before)
checks.NoError(t, err, "ListAssistants error")
_, err = client.CreateAssistantFile(ctx, assistantID, openai.AssistantFileRequest{
FileID: assistantFileID,
})
checks.NoError(t, err, "CreateAssistantFile error")
_, err = client.ListAssistantFiles(ctx, assistantID, &limit, &order, &after, &before)
checks.NoError(t, err, "ListAssistantFiles error")
_, err = client.RetrieveAssistantFile(ctx, assistantID, assistantFileID)
checks.NoError(t, err, "RetrieveAssistantFile error")
err = client.DeleteAssistantFile(ctx, assistantID, assistantFileID)
checks.NoError(t, err, "DeleteAssistantFile error")
}
================================================
FILE: audio.go
================================================
package openai
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
utils "github.com/sashabaranov/go-openai/internal"
)
// Whisper Defines the models provided by OpenAI to use when processing audio with OpenAI.
const (
Whisper1 = "whisper-1"
)
// Response formats; Whisper uses AudioResponseFormatJSON by default.
type AudioResponseFormat string
const (
AudioResponseFormatJSON AudioResponseFormat = "json"
AudioResponseFormatText AudioResponseFormat = "text"
AudioResponseFormatSRT AudioResponseFormat = "srt"
AudioResponseFormatVerboseJSON AudioResponseFormat = "verbose_json"
AudioResponseFormatVTT AudioResponseFormat = "vtt"
)
type TranscriptionTimestampGranularity string
const (
TranscriptionTimestampGranularityWord TranscriptionTimestampGranularity = "word"
TranscriptionTimestampGranularitySegment TranscriptionTimestampGranularity = "segment"
)
// AudioRequest represents a request structure for audio API.
type AudioRequest struct {
Model string
// FilePath is either an existing file in your filesystem or a filename representing the contents of Reader.
FilePath string
// Reader is an optional io.Reader when you do not want to use an existing file.
Reader io.Reader
Prompt string
Temperature float32
Language string // Only for transcription.
Format AudioResponseFormat
TimestampGranularities []TranscriptionTimestampGranularity // Only for transcription.
}
// AudioResponse represents a response structure for audio API.
type AudioResponse struct {
Task string `json:"task"`
Language string `json:"language"`
Duration float64 `json:"duration"`
Segments []struct {
ID int `json:"id"`
Seek int `json:"seek"`
Start float64 `json:"start"`
End float64 `json:"end"`
Text string `json:"text"`
Tokens []int `json:"tokens"`
Temperature float64 `json:"temperature"`
AvgLogprob float64 `json:"avg_logprob"`
CompressionRatio float64 `json:"compression_ratio"`
NoSpeechProb float64 `json:"no_speech_prob"`
Transient bool `json:"transient"`
} `json:"segments"`
Words []struct {
Word string `json:"word"`
Start float64 `json:"start"`
End float64 `json:"end"`
} `json:"words"`
Text string `json:"text"`
httpHeader
}
type audioTextResponse struct {
Text string `json:"text"`
httpHeader
}
func (r *audioTextResponse) ToAudioResponse() AudioResponse {
return AudioResponse{
Text: r.Text,
httpHeader: r.httpHeader,
}
}
// CreateTranscription — API call to create a transcription. Returns transcribed text.
func (c *Client) CreateTranscription(
ctx context.Context,
request AudioRequest,
) (response AudioResponse, err error) {
return c.callAudioAPI(ctx, request, "transcriptions")
}
// CreateTranslation — API call to translate audio into English.
func (c *Client) CreateTranslation(
ctx context.Context,
request AudioRequest,
) (response AudioResponse, err error) {
return c.callAudioAPI(ctx, request, "translations")
}
// callAudioAPI — API call to an audio endpoint.
func (c *Client) callAudioAPI(
ctx context.Context,
request AudioRequest,
endpointSuffix string,
) (response AudioResponse, err error) {
var formBody bytes.Buffer
builder := c.createFormBuilder(&formBody)
if err = audioMultipartForm(request, builder); err != nil {
return AudioResponse{}, err
}
urlSuffix := fmt.Sprintf("/audio/%s", endpointSuffix)
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(urlSuffix, withModel(request.Model)),
withBody(&formBody),
withContentType(builder.FormDataContentType()),
)
if err != nil {
return AudioResponse{}, err
}
if request.HasJSONResponse() {
err = c.sendRequest(req, &response)
} else {
var textResponse audioTextResponse
err = c.sendRequest(req, &textResponse)
response = textResponse.ToAudioResponse()
}
if err != nil {
return AudioResponse{}, err
}
return
}
// HasJSONResponse returns true if the response format is JSON.
func (r AudioRequest) HasJSONResponse() bool {
return r.Format == "" || r.Format == AudioResponseFormatJSON || r.Format == AudioResponseFormatVerboseJSON
}
// audioMultipartForm creates a form with audio file contents and the name of the model to use for
// audio processing.
func audioMultipartForm(request AudioRequest, b utils.FormBuilder) error {
err := createFileField(request, b)
if err != nil {
return err
}
err = b.WriteField("model", request.Model)
if err != nil {
return fmt.Errorf("writing model name: %w", err)
}
// Create a form field for the prompt (if provided)
if request.Prompt != "" {
err = b.WriteField("prompt", request.Prompt)
if err != nil {
return fmt.Errorf("writing prompt: %w", err)
}
}
// Create a form field for the format (if provided)
if request.Format != "" {
err = b.WriteField("response_format", string(request.Format))
if err != nil {
return fmt.Errorf("writing format: %w", err)
}
}
// Create a form field for the temperature (if provided)
if request.Temperature != 0 {
err = b.WriteField("temperature", fmt.Sprintf("%.2f", request.Temperature))
if err != nil {
return fmt.Errorf("writing temperature: %w", err)
}
}
// Create a form field for the language (if provided)
if request.Language != "" {
err = b.WriteField("language", request.Language)
if err != nil {
return fmt.Errorf("writing language: %w", err)
}
}
if len(request.TimestampGranularities) > 0 {
for _, tg := range request.TimestampGranularities {
err = b.WriteField("timestamp_granularities[]", string(tg))
if err != nil {
return fmt.Errorf("writing timestamp_granularities[]: %w", err)
}
}
}
// Close the multipart writer
return b.Close()
}
// createFileField creates the "file" form field from either an existing file or by using the reader.
func createFileField(request AudioRequest, b utils.FormBuilder) error {
if request.Reader != nil {
err := b.CreateFormFileReader("file", request.Reader, request.FilePath)
if err != nil {
return fmt.Errorf("creating form using reader: %w", err)
}
return nil
}
f, err := os.Open(request.FilePath)
if err != nil {
return fmt.Errorf("opening audio file: %w", err)
}
defer f.Close()
err = b.CreateFormFile("file", f)
if err != nil {
return fmt.Errorf("creating form file: %w", err)
}
return nil
}
================================================
FILE: audio_api_test.go
================================================
package openai_test
import (
"bytes"
"context"
"errors"
"io"
"mime"
"mime/multipart"
"net/http"
"path/filepath"
"strings"
"testing"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test"
"github.com/sashabaranov/go-openai/internal/test/checks"
)
// TestAudio Tests the transcription and translation endpoints of the API using the mocked server.
func TestAudio(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/audio/transcriptions", handleAudioEndpoint)
server.RegisterHandler("/v1/audio/translations", handleAudioEndpoint)
testcases := []struct {
name string
createFn func(context.Context, openai.AudioRequest) (openai.AudioResponse, error)
}{
{
"transcribe",
client.CreateTranscription,
},
{
"translate",
client.CreateTranslation,
},
}
ctx := context.Background()
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "fake.mp3")
test.CreateTestFile(t, path)
req := openai.AudioRequest{
FilePath: path,
Model: "whisper-3",
}
_, err := tc.createFn(ctx, req)
checks.NoError(t, err, "audio API error")
})
t.Run(tc.name+" (with reader)", func(t *testing.T) {
req := openai.AudioRequest{
FilePath: "fake.webm",
Reader: bytes.NewBuffer([]byte(`some webm binary data`)),
Model: "whisper-3",
}
_, err := tc.createFn(ctx, req)
checks.NoError(t, err, "audio API error")
})
}
}
func TestAudioWithOptionalArgs(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/audio/transcriptions", handleAudioEndpoint)
server.RegisterHandler("/v1/audio/translations", handleAudioEndpoint)
testcases := []struct {
name string
createFn func(context.Context, openai.AudioRequest) (openai.AudioResponse, error)
}{
{
"transcribe",
client.CreateTranscription,
},
{
"translate",
client.CreateTranslation,
},
}
ctx := context.Background()
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "fake.mp3")
test.CreateTestFile(t, path)
req := openai.AudioRequest{
FilePath: path,
Model: "whisper-3",
Prompt: "用简体中文",
Temperature: 0.5,
Language: "zh",
Format: openai.AudioResponseFormatSRT,
TimestampGranularities: []openai.TranscriptionTimestampGranularity{
openai.TranscriptionTimestampGranularitySegment,
openai.TranscriptionTimestampGranularityWord,
},
}
_, err := tc.createFn(ctx, req)
checks.NoError(t, err, "audio API error")
})
}
}
// handleAudioEndpoint Handles the completion endpoint by the test server.
func handleAudioEndpoint(w http.ResponseWriter, r *http.Request) {
var err error
// audio endpoints only accept POST requests
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
http.Error(w, "failed to parse media type", http.StatusBadRequest)
return
}
if !strings.HasPrefix(mediaType, "multipart") {
http.Error(w, "request is not multipart", http.StatusBadRequest)
}
boundary, ok := params["boundary"]
if !ok {
http.Error(w, "no boundary in params", http.StatusBadRequest)
return
}
fileData := &bytes.Buffer{}
mr := multipart.NewReader(r.Body, boundary)
part, err := mr.NextPart()
if err != nil && errors.Is(err, io.EOF) {
http.Error(w, "error accessing file", http.StatusBadRequest)
return
}
if _, err = io.Copy(fileData, part); err != nil {
http.Error(w, "failed to copy file", http.StatusInternalServerError)
return
}
if len(fileData.Bytes()) == 0 {
w.WriteHeader(http.StatusInternalServerError)
http.Error(w, "received empty file data", http.StatusBadRequest)
return
}
if _, err = w.Write([]byte(`{"body": "hello"}`)); err != nil {
http.Error(w, "failed to write body", http.StatusInternalServerError)
return
}
}
================================================
FILE: audio_test.go
================================================
package openai //nolint:testpackage // testing private field
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"testing"
utils "github.com/sashabaranov/go-openai/internal"
"github.com/sashabaranov/go-openai/internal/test"
"github.com/sashabaranov/go-openai/internal/test/checks"
)
func TestAudioWithFailingFormBuilder(t *testing.T) {
path := filepath.Join(t.TempDir(), "fake.mp3")
test.CreateTestFile(t, path)
req := AudioRequest{
FilePath: path,
Prompt: "test",
Temperature: 0.5,
Language: "en",
Format: AudioResponseFormatSRT,
TimestampGranularities: []TranscriptionTimestampGranularity{
TranscriptionTimestampGranularitySegment,
TranscriptionTimestampGranularityWord,
},
}
mockFailedErr := fmt.Errorf("mock form builder fail")
mockBuilder := &mockFormBuilder{}
mockBuilder.mockCreateFormFile = func(string, *os.File) error {
return mockFailedErr
}
err := audioMultipartForm(req, mockBuilder)
checks.ErrorIs(t, err, mockFailedErr, "audioMultipartForm should return error if form builder fails")
mockBuilder.mockCreateFormFile = func(string, *os.File) error {
return nil
}
var failForField string
mockBuilder.mockWriteField = func(fieldname, _ string) error {
if fieldname == failForField {
return mockFailedErr
}
return nil
}
failOn := []string{"model", "prompt", "temperature", "language", "response_format", "timestamp_granularities[]"}
for _, failingField := range failOn {
failForField = failingField
mockFailedErr = fmt.Errorf("mock form builder fail on field %s", failingField)
err = audioMultipartForm(req, mockBuilder)
checks.ErrorIs(t, err, mockFailedErr, "audioMultipartForm should return error if form builder fails")
}
}
func TestCreateFileField(t *testing.T) {
t.Run("createFileField failing file", func(t *testing.T) {
path := filepath.Join(t.TempDir(), "fake.mp3")
test.CreateTestFile(t, path)
req := AudioRequest{
FilePath: path,
}
mockFailedErr := fmt.Errorf("mock form builder fail")
mockBuilder := &mockFormBuilder{
mockCreateFormFile: func(string, *os.File) error {
return mockFailedErr
},
}
err := createFileField(req, mockBuilder)
checks.ErrorIs(t, err, mockFailedErr, "createFileField using a file should return error if form builder fails")
})
t.Run("createFileField failing reader", func(t *testing.T) {
req := AudioRequest{
FilePath: "test.wav",
Reader: bytes.NewBuffer([]byte(`wav test contents`)),
}
mockFailedErr := fmt.Errorf("mock form builder fail")
mockBuilder := &mockFormBuilder{
mockCreateFormFileReader: func(string, io.Reader, string) error {
return mockFailedErr
},
}
err := createFileField(req, mockBuilder)
checks.ErrorIs(t, err, mockFailedErr, "createFileField using a reader should return error if form builder fails")
})
t.Run("createFileField failing open", func(t *testing.T) {
req := AudioRequest{
FilePath: "non_existing_file.wav",
}
mockBuilder := &mockFormBuilder{}
err := createFileField(req, mockBuilder)
checks.HasError(t, err, "createFileField using file should return error when open file fails")
})
}
// failingFormBuilder always returns an error when creating form files.
type failingFormBuilder struct{ err error }
func (f *failingFormBuilder) CreateFormFile(_ string, _ *os.File) error {
return f.err
}
func (f *failingFormBuilder) CreateFormFileReader(_ string, _ io.Reader, _ string) error {
return f.err
}
func (f *failingFormBuilder) WriteField(_, _ string) error {
return nil
}
func (f *failingFormBuilder) Close() error {
return nil
}
func (f *failingFormBuilder) FormDataContentType() string {
return "multipart/form-data"
}
// failingAudioRequestBuilder simulates an error during HTTP request construction.
type failingAudioRequestBuilder struct{ err error }
func (f *failingAudioRequestBuilder) Build(
_ context.Context,
_, _ string,
_ any,
_ http.Header,
) (*http.Request, error) {
return nil, f.err
}
// errorHTTPClient always returns an error when making HTTP calls.
type errorHTTPClient struct{ err error }
func (e *errorHTTPClient) Do(_ *http.Request) (*http.Response, error) {
return nil, e.err
}
func TestCallAudioAPIMultipartFormError(t *testing.T) {
client := NewClient("test-token")
errForm := errors.New("mock create form file failure")
// Override form builder to force an error during multipart form creation.
client.createFormBuilder = func(_ io.Writer) utils.FormBuilder {
return &failingFormBuilder{err: errForm}
}
// Provide a reader so createFileField uses the reader path (no file open).
req := AudioRequest{FilePath: "fake.mp3", Reader: bytes.NewBuffer([]byte("dummy")), Model: Whisper1}
_, err := client.callAudioAPI(context.Background(), req, "transcriptions")
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, errForm) {
t.Errorf("expected error %v, got %v", errForm, err)
}
}
func TestCallAudioAPINewRequestError(t *testing.T) {
client := NewClient("test-token")
// Create a real temp file so multipart form succeeds.
tmp := t.TempDir()
path := filepath.Join(tmp, "file.mp3")
if err := os.WriteFile(path, []byte("content"), 0644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
errBuild := errors.New("mock build failure")
client.requestBuilder = &failingAudioRequestBuilder{err: errBuild}
req := AudioRequest{FilePath: path, Model: Whisper1}
_, err := client.callAudioAPI(context.Background(), req, "translations")
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, errBuild) {
t.Errorf("expected error %v, got %v", errBuild, err)
}
}
func TestCallAudioAPISendRequestErrorJSON(t *testing.T) {
client := NewClient("test-token")
// Create a real temp file so multipart form succeeds.
tmp := t.TempDir()
path := filepath.Join(tmp, "file.mp3")
if err := os.WriteFile(path, []byte("content"), 0644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
errHTTP := errors.New("mock HTTPClient failure")
// Override HTTP client to simulate a network error.
client.config.HTTPClient = &errorHTTPClient{err: errHTTP}
req := AudioRequest{FilePath: path, Model: Whisper1}
_, err := client.callAudioAPI(context.Background(), req, "transcriptions")
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, errHTTP) {
t.Errorf("expected error %v, got %v", errHTTP, err)
}
}
func TestCallAudioAPISendRequestErrorText(t *testing.T) {
client := NewClient("test-token")
tmp := t.TempDir()
path := filepath.Join(tmp, "file.mp3")
if err := os.WriteFile(path, []byte("content"), 0644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
errHTTP := errors.New("mock HTTPClient failure")
client.config.HTTPClient = &errorHTTPClient{err: errHTTP}
// Use a non-JSON response format to exercise the text path.
req := AudioRequest{FilePath: path, Model: Whisper1, Format: AudioResponseFormatText}
_, err := client.callAudioAPI(context.Background(), req, "translations")
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, errHTTP) {
t.Errorf("expected error %v, got %v", errHTTP, err)
}
}
================================================
FILE: batch.go
================================================
package openai
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
const batchesSuffix = "/batches"
type BatchEndpoint string
const (
BatchEndpointChatCompletions BatchEndpoint = "/v1/chat/completions"
BatchEndpointCompletions BatchEndpoint = "/v1/completions"
BatchEndpointEmbeddings BatchEndpoint = "/v1/embeddings"
)
type BatchLineItem interface {
MarshalBatchLineItem() []byte
}
type BatchChatCompletionRequest struct {
CustomID string `json:"custom_id"`
Body ChatCompletionRequest `json:"body"`
Method string `json:"method"`
URL BatchEndpoint `json:"url"`
}
func (r BatchChatCompletionRequest) MarshalBatchLineItem() []byte {
marshal, _ := json.Marshal(r)
return marshal
}
type BatchCompletionRequest struct {
CustomID string `json:"custom_id"`
Body CompletionRequest `json:"body"`
Method string `json:"method"`
URL BatchEndpoint `json:"url"`
}
func (r BatchCompletionRequest) MarshalBatchLineItem() []byte {
marshal, _ := json.Marshal(r)
return marshal
}
type BatchEmbeddingRequest struct {
CustomID string `json:"custom_id"`
Body EmbeddingRequest `json:"body"`
Method string `json:"method"`
URL BatchEndpoint `json:"url"`
}
func (r BatchEmbeddingRequest) MarshalBatchLineItem() []byte {
marshal, _ := json.Marshal(r)
return marshal
}
type Batch struct {
ID string `json:"id"`
Object string `json:"object"`
Endpoint BatchEndpoint `json:"endpoint"`
Errors *struct {
Object string `json:"object,omitempty"`
Data []struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Param *string `json:"param,omitempty"`
Line *int `json:"line,omitempty"`
} `json:"data"`
} `json:"errors"`
InputFileID string `json:"input_file_id"`
CompletionWindow string `json:"completion_window"`
Status string `json:"status"`
OutputFileID *string `json:"output_file_id"`
ErrorFileID *string `json:"error_file_id"`
CreatedAt int `json:"created_at"`
InProgressAt *int `json:"in_progress_at"`
ExpiresAt *int `json:"expires_at"`
FinalizingAt *int `json:"finalizing_at"`
CompletedAt *int `json:"completed_at"`
FailedAt *int `json:"failed_at"`
ExpiredAt *int `json:"expired_at"`
CancellingAt *int `json:"cancelling_at"`
CancelledAt *int `json:"cancelled_at"`
RequestCounts BatchRequestCounts `json:"request_counts"`
Metadata map[string]any `json:"metadata"`
}
type BatchRequestCounts struct {
Total int `json:"total"`
Completed int `json:"completed"`
Failed int `json:"failed"`
}
type CreateBatchRequest struct {
InputFileID string `json:"input_file_id"`
Endpoint BatchEndpoint `json:"endpoint"`
CompletionWindow string `json:"completion_window"`
Metadata map[string]any `json:"metadata"`
}
type BatchResponse struct {
httpHeader
Batch
}
// CreateBatch — API call to Create batch.
func (c *Client) CreateBatch(
ctx context.Context,
request CreateBatchRequest,
) (response BatchResponse, err error) {
if request.CompletionWindow == "" {
request.CompletionWindow = "24h"
}
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(batchesSuffix), withBody(request))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
type UploadBatchFileRequest struct {
FileName string
Lines []BatchLineItem
}
func (r *UploadBatchFileRequest) MarshalJSONL() []byte {
buff := bytes.Buffer{}
for i, line := range r.Lines {
if i != 0 {
buff.Write([]byte("\n"))
}
buff.Write(line.MarshalBatchLineItem())
}
return buff.Bytes()
}
func (r *UploadBatchFileRequest) AddChatCompletion(customerID string, body ChatCompletionRequest) {
r.Lines = append(r.Lines, BatchChatCompletionRequest{
CustomID: customerID,
Body: body,
Method: "POST",
URL: BatchEndpointChatCompletions,
})
}
func (r *UploadBatchFileRequest) AddCompletion(customerID string, body CompletionRequest) {
r.Lines = append(r.Lines, BatchCompletionRequest{
CustomID: customerID,
Body: body,
Method: "POST",
URL: BatchEndpointCompletions,
})
}
func (r *UploadBatchFileRequest) AddEmbedding(customerID string, body EmbeddingRequest) {
r.Lines = append(r.Lines, BatchEmbeddingRequest{
CustomID: customerID,
Body: body,
Method: "POST",
URL: BatchEndpointEmbeddings,
})
}
// UploadBatchFile — upload batch file.
func (c *Client) UploadBatchFile(ctx context.Context, request UploadBatchFileRequest) (File, error) {
if request.FileName == "" {
request.FileName = "@batchinput.jsonl"
}
return c.CreateFileBytes(ctx, FileBytesRequest{
Name: request.FileName,
Bytes: request.MarshalJSONL(),
Purpose: PurposeBatch,
})
}
type CreateBatchWithUploadFileRequest struct {
Endpoint BatchEndpoint `json:"endpoint"`
CompletionWindow string `json:"completion_window"`
Metadata map[string]any `json:"metadata"`
UploadBatchFileRequest
}
// CreateBatchWithUploadFile — API call to Create batch with upload file.
func (c *Client) CreateBatchWithUploadFile(
ctx context.Context,
request CreateBatchWithUploadFileRequest,
) (response BatchResponse, err error) {
var file File
file, err = c.UploadBatchFile(ctx, UploadBatchFileRequest{
FileName: request.FileName,
Lines: request.Lines,
})
if err != nil {
return
}
return c.CreateBatch(ctx, CreateBatchRequest{
InputFileID: file.ID,
Endpoint: request.Endpoint,
CompletionWindow: request.CompletionWindow,
Metadata: request.Metadata,
})
}
// RetrieveBatch — API call to Retrieve batch.
func (c *Client) RetrieveBatch(
ctx context.Context,
batchID string,
) (response BatchResponse, err error) {
urlSuffix := fmt.Sprintf("%s/%s", batchesSuffix, batchID)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
// CancelBatch — API call to Cancel batch.
func (c *Client) CancelBatch(
ctx context.Context,
batchID string,
) (response BatchResponse, err error) {
urlSuffix := fmt.Sprintf("%s/%s/cancel", batchesSuffix, batchID)
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
type ListBatchResponse struct {
httpHeader
Object string `json:"object"`
Data []Batch `json:"data"`
FirstID string `json:"first_id"`
LastID string `json:"last_id"`
HasMore bool `json:"has_more"`
}
// ListBatch API call to List batch.
func (c *Client) ListBatch(ctx context.Context, after *string, limit *int) (response ListBatchResponse, err error) {
urlValues := url.Values{}
if limit != nil {
urlValues.Add("limit", fmt.Sprintf("%d", *limit))
}
if after != nil {
urlValues.Add("after", *after)
}
encodedValues := ""
if len(urlValues) > 0 {
encodedValues = "?" + urlValues.Encode()
}
urlSuffix := fmt.Sprintf("%s%s", batchesSuffix, encodedValues)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
================================================
FILE: batch_test.go
================================================
package openai_test
import (
"context"
"fmt"
"net/http"
"reflect"
"testing"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test/checks"
)
func TestUploadBatchFile(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/files", handleCreateFile)
req := openai.UploadBatchFileRequest{}
req.AddChatCompletion("req-1", openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
_, err := client.UploadBatchFile(context.Background(), req)
checks.NoError(t, err, "UploadBatchFile error")
}
func TestCreateBatch(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/batches", handleBatchEndpoint)
_, err := client.CreateBatch(context.Background(), openai.CreateBatchRequest{
InputFileID: "file-abc",
Endpoint: openai.BatchEndpointChatCompletions,
CompletionWindow: "24h",
})
checks.NoError(t, err, "CreateBatch error")
}
func TestCreateBatchWithUploadFile(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/files", handleCreateFile)
server.RegisterHandler("/v1/batches", handleBatchEndpoint)
req := openai.CreateBatchWithUploadFileRequest{
Endpoint: openai.BatchEndpointChatCompletions,
}
req.AddChatCompletion("req-1", openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
_, err := client.CreateBatchWithUploadFile(context.Background(), req)
checks.NoError(t, err, "CreateBatchWithUploadFile error")
}
func TestRetrieveBatch(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/batches/file-id-1", handleRetrieveBatchEndpoint)
_, err := client.RetrieveBatch(context.Background(), "file-id-1")
checks.NoError(t, err, "RetrieveBatch error")
}
func TestCancelBatch(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/batches/file-id-1/cancel", handleCancelBatchEndpoint)
_, err := client.CancelBatch(context.Background(), "file-id-1")
checks.NoError(t, err, "RetrieveBatch error")
}
func TestListBatch(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/batches", handleBatchEndpoint)
after := "batch_abc123"
limit := 10
_, err := client.ListBatch(context.Background(), &after, &limit)
checks.NoError(t, err, "RetrieveBatch error")
}
func TestUploadBatchFileRequest_AddChatCompletion(t *testing.T) {
type args struct {
customerID string
body openai.ChatCompletionRequest
}
tests := []struct {
name string
args []args
want []byte
}{
{"", []args{
{
customerID: "req-1",
body: openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
},
},
{
customerID: "req-2",
body: openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
},
},
}, []byte("{\"custom_id\":\"req-1\",\"body\":{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello!\"}],\"max_tokens\":5},\"method\":\"POST\",\"url\":\"/v1/chat/completions\"}\n{\"custom_id\":\"req-2\",\"body\":{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello!\"}],\"max_tokens\":5},\"method\":\"POST\",\"url\":\"/v1/chat/completions\"}")}, //nolint:lll
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &openai.UploadBatchFileRequest{}
for _, arg := range tt.args {
r.AddChatCompletion(arg.customerID, arg.body)
}
got := r.MarshalJSONL()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Marshal() got = %v, want %v", got, tt.want)
}
})
}
}
func TestUploadBatchFileRequest_AddCompletion(t *testing.T) {
type args struct {
customerID string
body openai.CompletionRequest
}
tests := []struct {
name string
args []args
want []byte
}{
{"", []args{
{
customerID: "req-1",
body: openai.CompletionRequest{
Model: openai.GPT3Dot5Turbo,
User: "Hello",
},
},
{
customerID: "req-2",
body: openai.CompletionRequest{
Model: openai.GPT3Dot5Turbo,
User: "Hello",
},
},
}, []byte("{\"custom_id\":\"req-1\",\"body\":{\"model\":\"gpt-3.5-turbo\",\"user\":\"Hello\"},\"method\":\"POST\",\"url\":\"/v1/completions\"}\n{\"custom_id\":\"req-2\",\"body\":{\"model\":\"gpt-3.5-turbo\",\"user\":\"Hello\"},\"method\":\"POST\",\"url\":\"/v1/completions\"}")}, //nolint:lll
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &openai.UploadBatchFileRequest{}
for _, arg := range tt.args {
r.AddCompletion(arg.customerID, arg.body)
}
got := r.MarshalJSONL()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Marshal() got = %v, want %v", got, tt.want)
}
})
}
}
func TestUploadBatchFileRequest_AddEmbedding(t *testing.T) {
type args struct {
customerID string
body openai.EmbeddingRequest
}
tests := []struct {
name string
args []args
want []byte
}{
{"", []args{
{
customerID: "req-1",
body: openai.EmbeddingRequest{
Model: openai.GPT3Dot5Turbo,
Input: []string{"Hello", "World"},
},
},
{
customerID: "req-2",
body: openai.EmbeddingRequest{
Model: openai.AdaEmbeddingV2,
Input: []string{"Hello", "World"},
},
},
}, []byte("{\"custom_id\":\"req-1\",\"body\":{\"input\":[\"Hello\",\"World\"],\"model\":\"gpt-3.5-turbo\"},\"method\":\"POST\",\"url\":\"/v1/embeddings\"}\n{\"custom_id\":\"req-2\",\"body\":{\"input\":[\"Hello\",\"World\"],\"model\":\"text-embedding-ada-002\"},\"method\":\"POST\",\"url\":\"/v1/embeddings\"}")}, //nolint:lll
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &openai.UploadBatchFileRequest{}
for _, arg := range tt.args {
r.AddEmbedding(arg.customerID, arg.body)
}
got := r.MarshalJSONL()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Marshal() got = %v, want %v", got, tt.want)
}
})
}
}
func handleBatchEndpoint(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
_, _ = fmt.Fprintln(w, `{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "completed",
"output_file_id": "file-cvaTdG",
"error_file_id": "file-HOWS94",
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": 1711493133,
"completed_at": 1711493163,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 95,
"failed": 5
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job"
}
}`)
} else if r.Method == http.MethodGet {
_, _ = fmt.Fprintln(w, `{
"object": "list",
"data": [
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "completed",
"output_file_id": "file-cvaTdG",
"error_file_id": "file-HOWS94",
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": 1711493133,
"completed_at": 1711493163,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 95,
"failed": 5
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly job"
}
}
],
"first_id": "batch_abc123",
"last_id": "batch_abc456",
"has_more": true
}`)
}
}
func handleRetrieveBatchEndpoint(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
_, _ = fmt.Fprintln(w, `{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "completed",
"output_file_id": "file-cvaTdG",
"error_file_id": "file-HOWS94",
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": 1711493133,
"completed_at": 1711493163,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 95,
"failed": 5
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job"
}
}`)
}
}
func handleCancelBatchEndpoint(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
_, _ = fmt.Fprintln(w, `{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "cancelling",
"output_file_id": null,
"error_file_id": null,
"created_at": 1711471533,
"in_progress_at": 1711471538,
"expires_at": 1711557933,
"finalizing_at": null,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"cancelling_at": 1711475133,
"cancelled_at": null,
"request_counts": {
"total": 100,
"completed": 23,
"failed": 1
},
"metadata": {
"customer_id": "user_123456789",
"batch_description": "Nightly eval job"
}
}`)
}
}
================================================
FILE: chat.go
================================================
package openai
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/sashabaranov/go-openai/jsonschema"
)
// Chat message role defined by the OpenAI API.
const (
ChatMessageRoleSystem = "system"
ChatMessageRoleUser = "user"
ChatMessageRoleAssistant = "assistant"
ChatMessageRoleFunction = "function"
ChatMessageRoleTool = "tool"
ChatMessageRoleDeveloper = "developer"
)
const chatCompletionsSuffix = "/chat/completions"
var (
ErrChatCompletionInvalidModel = errors.New("this model is not supported with this method, please use CreateCompletion client method instead") //nolint:lll
ErrChatCompletionStreamNotSupported = errors.New("streaming is not supported with this method, please use CreateChatCompletionStream") //nolint:lll
ErrContentFieldsMisused = errors.New("can't use both Content and MultiContent properties simultaneously")
)
type Hate struct {
Filtered bool `json:"filtered"`
Severity string `json:"severity,omitempty"`
}
type SelfHarm struct {
Filtered bool `json:"filtered"`
Severity string `json:"severity,omitempty"`
}
type Sexual struct {
Filtered bool `json:"filtered"`
Severity string `json:"severity,omitempty"`
}
type Violence struct {
Filtered bool `json:"filtered"`
Severity string `json:"severity,omitempty"`
}
type JailBreak struct {
Filtered bool `json:"filtered"`
Detected bool `json:"detected"`
}
type Profanity struct {
Filtered bool `json:"filtered"`
Detected bool `json:"detected"`
}
type ContentFilterResults struct {
Hate Hate `json:"hate,omitempty"`
SelfHarm SelfHarm `json:"self_harm,omitempty"`
Sexual Sexual `json:"sexual,omitempty"`
Violence Violence `json:"violence,omitempty"`
JailBreak JailBreak `json:"jailbreak,omitempty"`
Profanity Profanity `json:"profanity,omitempty"`
}
type PromptAnnotation struct {
PromptIndex int `json:"prompt_index,omitempty"`
ContentFilterResults ContentFilterResults `json:"content_filter_results,omitempty"`
}
type ImageURLDetail string
const (
ImageURLDetailHigh ImageURLDetail = "high"
ImageURLDetailLow ImageURLDetail = "low"
ImageURLDetailAuto ImageURLDetail = "auto"
)
type ChatMessageImageURL struct {
URL string `json:"url,omitempty"`
Detail ImageURLDetail `json:"detail,omitempty"`
}
type ChatMessagePartType string
const (
ChatMessagePartTypeText ChatMessagePartType = "text"
ChatMessagePartTypeImageURL ChatMessagePartType = "image_url"
)
type ChatMessagePart struct {
Type ChatMessagePartType `json:"type,omitempty"`
Text string `json:"text,omitempty"`
ImageURL *ChatMessageImageURL `json:"image_url,omitempty"`
}
type ChatCompletionMessage struct {
Role string `json:"role"`
Content string `json:"content,omitempty"`
Refusal string `json:"refusal,omitempty"`
MultiContent []ChatMessagePart
// This property isn't in the official documentation, but it's in
// the documentation for the official library for python:
// - https://github.com/openai/openai-python/blob/main/chatml.md
// - https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
Name string `json:"name,omitempty"`
// This property is used for the "reasoning" feature supported by deepseek-reasoner
// which is not in the official documentation.
// the doc from deepseek:
// - https://api-docs.deepseek.com/api/create-chat-completion#responses
ReasoningContent string `json:"reasoning_content,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
// For Role=assistant prompts this may be set to the tool calls generated by the model, such as function calls.
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
// For Role=tool prompts this should be set to the ID given in the assistant's prior request to call a tool.
ToolCallID string `json:"tool_call_id,omitempty"`
}
func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
if m.Content != "" && m.MultiContent != nil {
return nil, ErrContentFieldsMisused
}
if len(m.MultiContent) > 0 {
msg := struct {
Role string `json:"role"`
Content string `json:"-"`
Refusal string `json:"refusal,omitempty"`
MultiContent []ChatMessagePart `json:"content,omitempty"`
Name string `json:"name,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}(m)
return json.Marshal(msg)
}
msg := struct {
Role string `json:"role"`
Content string `json:"content,omitempty"`
Refusal string `json:"refusal,omitempty"`
MultiContent []ChatMessagePart `json:"-"`
Name string `json:"name,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}(m)
return json.Marshal(msg)
}
func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
msg := struct {
Role string `json:"role"`
Content string `json:"content"`
Refusal string `json:"refusal,omitempty"`
MultiContent []ChatMessagePart
Name string `json:"name,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}{}
if err := json.Unmarshal(bs, &msg); err == nil {
*m = ChatCompletionMessage(msg)
return nil
}
multiMsg := struct {
Role string `json:"role"`
Content string
Refusal string `json:"refusal,omitempty"`
MultiContent []ChatMessagePart `json:"content"`
Name string `json:"name,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}{}
if err := json.Unmarshal(bs, &multiMsg); err != nil {
return err
}
*m = ChatCompletionMessage(multiMsg)
return nil
}
type ToolCall struct {
// Index is not nil only in chat completion chunk object
Index *int `json:"index,omitempty"`
ID string `json:"id,omitempty"`
Type ToolType `json:"type"`
Function FunctionCall `json:"function"`
}
type FunctionCall struct {
Name string `json:"name,omitempty"`
// call function with arguments in JSON format
Arguments string `json:"arguments,omitempty"`
}
type ChatCompletionResponseFormatType string
const (
ChatCompletionResponseFormatTypeJSONObject ChatCompletionResponseFormatType = "json_object"
ChatCompletionResponseFormatTypeJSONSchema ChatCompletionResponseFormatType = "json_schema"
ChatCompletionResponseFormatTypeText ChatCompletionResponseFormatType = "text"
)
type ChatCompletionResponseFormat struct {
Type ChatCompletionResponseFormatType `json:"type,omitempty"`
JSONSchema *ChatCompletionResponseFormatJSONSchema `json:"json_schema,omitempty"`
}
type ChatCompletionResponseFormatJSONSchema struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Schema json.Marshaler `json:"schema"`
Strict bool `json:"strict"`
}
func (r *ChatCompletionResponseFormatJSONSchema) UnmarshalJSON(data []byte) error {
type rawJSONSchema struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Schema json.RawMessage `json:"schema"`
Strict bool `json:"strict"`
}
var raw rawJSONSchema
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
r.Name = raw.Name
r.Description = raw.Description
r.Strict = raw.Strict
if len(raw.Schema) > 0 && string(raw.Schema) != "null" {
var d jsonschema.Definition
err := json.Unmarshal(raw.Schema, &d)
if err != nil {
return err
}
r.Schema = &d
}
return nil
}
// ChatCompletionRequestExtensions contains third-party OpenAI API extensions
// (e.g., vendor-specific implementations like vLLM).
type ChatCompletionRequestExtensions struct {
// GuidedChoice is a vLLM-specific extension that restricts the model's output
// to one of the predefined string choices provided in this field. This feature
// is used to constrain the model's responses to a controlled set of options,
// ensuring predictable and consistent outputs in scenarios where specific
// choices are required.
GuidedChoice []string `json:"guided_choice,omitempty"`
}
// ChatCompletionRequest represents a request structure for chat completion API.
type ChatCompletionRequest struct {
Model string `json:"model"`
Messages []ChatCompletionMessage `json:"messages"`
// MaxTokens The maximum number of tokens that can be generated in the chat completion.
// This value can be used to control costs for text generated via API.
// Deprecated: use MaxCompletionTokens. Not compatible with o1-series models.
// refs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens
MaxTokens int `json:"max_tokens,omitempty"`
// MaxCompletionTokens An upper bound for the number of tokens that can be generated for a completion,
// including visible output tokens and reasoning tokens https://platform.openai.com/docs/guides/reasoning
MaxCompletionTokens int `json:"max_completion_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"`
ResponseFormat *ChatCompletionResponseFormat `json:"response_format,omitempty"`
Seed *int `json:"seed,omitempty"`
FrequencyPenalty float32 `json:"frequency_penalty,omitempty"`
// LogitBias is must be a token id string (specified by their token ID in the tokenizer), not a word string.
// incorrect: `"logit_bias":{"You": 6}`, correct: `"logit_bias":{"1639": 6}`
// refs: https://platform.openai.com/docs/api-reference/chat/create#chat/create-logit_bias
LogitBias map[string]int `json:"logit_bias,omitempty"`
// LogProbs indicates whether to return log probabilities of the output tokens or not.
// If true, returns the log probabilities of each output token returned in the content of message.
// This option is currently not available on the gpt-4-vision-preview model.
LogProbs bool `json:"logprobs,omitempty"`
// TopLogProbs is an integer between 0 and 5 specifying the number of most likely tokens to return at each
// token position, each with an associated log probability.
// logprobs must be set to true if this parameter is used.
TopLogProbs int `json:"top_logprobs,omitempty"`
User string `json:"user,omitempty"`
// Deprecated: use Tools instead.
Functions []FunctionDefinition `json:"functions,omitempty"`
// Deprecated: use ToolChoice instead.
FunctionCall any `json:"function_call,omitempty"`
Tools []Tool `json:"tools,omitempty"`
// This can be either a string or an ToolChoice object.
ToolChoice any `json:"tool_choice,omitempty"`
// Options for streaming response. Only set this when you set stream: true.
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
// Disable the default behavior of parallel tool calls by setting it: false.
ParallelToolCalls any `json:"parallel_tool_calls,omitempty"`
// Store can be set to true to store the output of this completion request for use in distillations and evals.
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-store
Store bool `json:"store,omitempty"`
// Controls effort on reasoning for reasoning models. It can be set to "low", "medium", or "high".
ReasoningEffort string `json:"reasoning_effort,omitempty"`
// Metadata to store with the completion.
Metadata map[string]string `json:"metadata,omitempty"`
// Configuration for a predicted output.
Prediction *Prediction `json:"prediction,omitempty"`
// ChatTemplateKwargs provides a way to add non-standard parameters to the request body.
// Additional kwargs to pass to the template renderer. Will be accessible by the chat template.
// Such as think mode for qwen3. "chat_template_kwargs": {"enable_thinking": false}
// https://qwen.readthedocs.io/en/latest/deployment/vllm.html#thinking-non-thinking-modes
ChatTemplateKwargs map[string]any `json:"chat_template_kwargs,omitempty"`
// Specifies the latency tier to use for processing the request.
ServiceTier ServiceTier `json:"service_tier,omitempty"`
// Verbosity determines how many output tokens are generated. Lowering the number of
// tokens reduces overall latency. It can be set to "low", "medium", or "high".
// Note: This field is only confirmed to work with gpt-5, gpt-5-mini and gpt-5-nano.
// Also, it is not in the API reference of chat completion at the time of writing,
// though it is supported by the API.
Verbosity string `json:"verbosity,omitempty"`
// A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.
// The IDs should be a string that uniquely identifies each user.
// We recommend hashing their username or email address, in order to avoid sending us any identifying information.
// https://platform.openai.com/docs/api-reference/chat/create#chat_create-safety_identifier
SafetyIdentifier string `json:"safety_identifier,omitempty"`
// Embedded struct for non-OpenAI extensions
ChatCompletionRequestExtensions
}
type StreamOptions struct {
// If set, an additional chunk will be streamed before the data: [DONE] message.
// The usage field on this chunk shows the token usage statistics for the entire request,
// and the choices field will always be an empty array.
// All other chunks will also include a usage field, but with a null value.
IncludeUsage bool `json:"include_usage,omitempty"`
}
type ToolType string
const (
ToolTypeFunction ToolType = "function"
)
type Tool struct {
Type ToolType `json:"type"`
Function *FunctionDefinition `json:"function,omitempty"`
}
type ToolChoice struct {
Type ToolType `json:"type"`
Function ToolFunction `json:"function,omitempty"`
}
type ToolFunction struct {
Name string `json:"name"`
}
type FunctionDefinition struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Strict bool `json:"strict,omitempty"`
// Parameters is an object describing the function.
// You can pass json.RawMessage to describe the schema,
// or you can pass in a struct which serializes to the proper JSON schema.
// The jsonschema package is provided for convenience, but you should
// consider another specialized library if you require more complex schemas.
Parameters any `json:"parameters"`
}
// Deprecated: use FunctionDefinition instead.
type FunctionDefine = FunctionDefinition
type TopLogProbs struct {
Token string `json:"token"`
LogProb float64 `json:"logprob"`
Bytes []byte `json:"bytes,omitempty"`
}
// LogProb represents the probability information for a token.
type LogProb struct {
Token string `json:"token"`
LogProb float64 `json:"logprob"`
Bytes []byte `json:"bytes,omitempty"` // Omitting the field if it is null
// TopLogProbs is a list of the most likely tokens and their log probability, at this token position.
// In rare cases, there may be fewer than the number of requested top_logprobs returned.
TopLogProbs []TopLogProbs `json:"top_logprobs"`
}
// LogProbs is the top-level structure containing the log probability information.
type LogProbs struct {
// Content is a list of message content tokens with log probability information.
Content []LogProb `json:"content"`
}
type Prediction struct {
Content string `json:"content"`
Type string `json:"type"`
}
type FinishReason string
const (
FinishReasonStop FinishReason = "stop"
FinishReasonLength FinishReason = "length"
FinishReasonFunctionCall FinishReason = "function_call"
FinishReasonToolCalls FinishReason = "tool_calls"
FinishReasonContentFilter FinishReason = "content_filter"
FinishReasonNull FinishReason = "null"
)
type ServiceTier string
const (
ServiceTierAuto ServiceTier = "auto"
ServiceTierDefault ServiceTier = "default"
ServiceTierFlex ServiceTier = "flex"
ServiceTierPriority ServiceTier = "priority"
)
func (r FinishReason) MarshalJSON() ([]byte, error) {
if r == FinishReasonNull || r == "" {
return []byte("null"), nil
}
return []byte(`"` + string(r) + `"`), nil // best effort to not break future API changes
}
type ChatCompletionChoice struct {
Index int `json:"index"`
Message ChatCompletionMessage `json:"message"`
// FinishReason
// stop: API returned complete message,
// or a message terminated by one of the stop sequences provided via the stop parameter
// length: Incomplete model output due to max_tokens parameter or token limit
// function_call: The model decided to call a function
// content_filter: Omitted content due to a flag from our content filters
// null: API response still in progress or incomplete
FinishReason FinishReason `json:"finish_reason"`
LogProbs *LogProbs `json:"logprobs,omitempty"`
ContentFilterResults ContentFilterResults `json:"content_filter_results,omitempty"`
}
// 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"`
SystemFingerprint string `json:"system_fingerprint"`
PromptFilterResults []PromptFilterResult `json:"prompt_filter_results,omitempty"`
ServiceTier ServiceTier `json:"service_tier,omitempty"`
httpHeader
}
// CreateChatCompletion — API call to Create a completion for the chat message.
func (c *Client) CreateChatCompletion(
ctx context.Context,
request ChatCompletionRequest,
) (response ChatCompletionResponse, err error) {
if request.Stream {
err = ErrChatCompletionStreamNotSupported
return
}
urlSuffix := chatCompletionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrChatCompletionInvalidModel
return
}
reasoningValidator := NewReasoningValidator()
if err = reasoningValidator.Validate(request); err != nil {
return
}
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(urlSuffix, withModel(request.Model)),
withBody(request),
)
if err != nil {
return
}
err = c.sendRequest(req, &response)
return
}
================================================
FILE: chat_stream.go
================================================
package openai
import (
"context"
"net/http"
)
type ChatCompletionStreamChoiceDelta struct {
Content string `json:"content,omitempty"`
Role string `json:"role,omitempty"`
FunctionCall *FunctionCall `json:"function_call,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
Refusal string `json:"refusal,omitempty"`
// This property is used for the "reasoning" feature supported by deepseek-reasoner
// which is not in the official documentation.
// the doc from deepseek:
// - https://api-docs.deepseek.com/api/create-chat-completion#responses
ReasoningContent string `json:"reasoning_content,omitempty"`
}
type ChatCompletionStreamChoiceLogprobs struct {
Content []ChatCompletionTokenLogprob `json:"content,omitempty"`
Refusal []ChatCompletionTokenLogprob `json:"refusal,omitempty"`
}
type ChatCompletionTokenLogprob struct {
Token string `json:"token"`
Bytes []int64 `json:"bytes,omitempty"`
Logprob float64 `json:"logprob,omitempty"`
TopLogprobs []ChatCompletionTokenLogprobTopLogprob `json:"top_logprobs"`
}
type ChatCompletionTokenLogprobTopLogprob struct {
Token string `json:"token"`
Bytes []int64 `json:"bytes"`
Logprob float64 `json:"logprob"`
}
type ChatCompletionStreamChoice struct {
Index int `json:"index"`
Delta ChatCompletionStreamChoiceDelta `json:"delta"`
Logprobs *ChatCompletionStreamChoiceLogprobs `json:"logprobs,omitempty"`
FinishReason FinishReason `json:"finish_reason"`
ContentFilterResults ContentFilterResults `json:"content_filter_results,omitempty"`
}
type PromptFilterResult struct {
Index int `json:"index"`
ContentFilterResults ContentFilterResults `json:"content_filter_results,omitempty"`
}
type ChatCompletionStreamResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []ChatCompletionStreamChoice `json:"choices"`
SystemFingerprint string `json:"system_fingerprint"`
PromptAnnotations []PromptAnnotation `json:"prompt_annotations,omitempty"`
PromptFilterResults []PromptFilterResult `json:"prompt_filter_results,omitempty"`
// An optional field that will only be present when you set stream_options: {"include_usage": true} in your request.
// When present, it contains a null value except for the last chunk which contains the token usage statistics
// for the entire request.
Usage *Usage `json:"usage,omitempty"`
}
// ChatCompletionStream
// Note: Perhaps it is more elegant to abstract Stream using generics.
type ChatCompletionStream struct {
*streamReader[ChatCompletionStreamResponse]
}
// CreateChatCompletionStream — API call to create a chat 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) CreateChatCompletionStream(
ctx context.Context,
request ChatCompletionRequest,
) (stream *ChatCompletionStream, err error) {
urlSuffix := chatCompletionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrChatCompletionInvalidModel
return
}
request.Stream = true
reasoningValidator := NewReasoningValidator()
if err = reasoningValidator.Validate(request); err != nil {
return
}
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(urlSuffix, withModel(request.Model)),
withBody(request),
)
if err != nil {
return nil, err
}
resp, err := sendRequestStream[ChatCompletionStreamResponse](c, req)
if err != nil {
return
}
stream = &ChatCompletionStream{
streamReader: resp,
}
return
}
================================================
FILE: chat_stream_test.go
================================================
package openai_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"testing"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test/checks"
)
func TestChatCompletionsStreamWrongModel(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
req := openai.ChatCompletionRequest{
MaxTokens: 5,
Model: "ada",
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
}
_, err := client.CreateChatCompletionStream(ctx, req)
if !errors.Is(err, openai.ErrChatCompletionInvalidModel) {
t.Fatalf("CreateChatCompletion should return ErrChatCompletionInvalidModel, but returned: %v", err)
}
}
func TestCreateChatCompletionStream(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
// Send test responses
dataBytes := []byte{}
dataBytes = append(dataBytes, []byte("event: message\n")...)
//nolint:lll
data := `{"id":"1","object":"completion","created":1598069254,"model":"gpt-3.5-turbo","system_fingerprint": "fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":"response1"},"finish_reason":"max_tokens"}]}`
dataBytes = append(dataBytes, []byte("data: "+data+"\n\n")...)
dataBytes = append(dataBytes, []byte("event: message\n")...)
//nolint:lll
data = `{"id":"2","object":"completion","created":1598069255,"model":"gpt-3.5-turbo","system_fingerprint": "fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":"response2"},"finish_reason":"max_tokens"}]}`
dataBytes = append(dataBytes, []byte("data: "+data+"\n\n")...)
dataBytes = append(dataBytes, []byte("event: done\n")...)
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
expectedResponses := []openai.ChatCompletionStreamResponse{
{
ID: "1",
Object: "completion",
Created: 1598069254,
Model: openai.GPT3Dot5Turbo,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "response1",
},
FinishReason: "max_tokens",
},
},
},
{
ID: "2",
Object: "completion",
Created: 1598069255,
Model: openai.GPT3Dot5Turbo,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "response2",
},
FinishReason: "max_tokens",
},
},
},
}
for ix, expectedResponse := range expectedResponses {
b, _ := json.Marshal(expectedResponse)
t.Logf("%d: %s", ix, string(b))
receivedResponse, streamErr := stream.Recv()
checks.NoError(t, streamErr, "stream.Recv() failed")
if !compareChatResponses(expectedResponse, receivedResponse) {
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
}
}
_, streamErr := stream.Recv()
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
}
_, streamErr = stream.Recv()
checks.ErrorIs(t, streamErr, io.EOF, "stream.Recv() did not return EOF when the stream is finished")
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF when the stream is finished: %v", streamErr)
}
}
func TestCreateChatCompletionStreamError(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
// Send test responses
dataBytes := []byte{}
dataStr := []string{
`{`,
`"error": {`,
`"message": "Incorrect API key provided: sk-***************************************",`,
`"type": "invalid_request_error",`,
`"param": null,`,
`"code": "invalid_api_key"`,
`}`,
`}`,
}
for _, str := range dataStr {
dataBytes = append(dataBytes, []byte(str+"\n")...)
}
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
_, streamErr := stream.Recv()
checks.HasError(t, streamErr, "stream.Recv() did not return error")
var apiErr *openai.APIError
if !errors.As(streamErr, &apiErr) {
t.Errorf("stream.Recv() did not return APIError")
}
t.Logf("%+v\n", apiErr)
}
func TestCreateChatCompletionStreamWithHeaders(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set(xCustomHeader, xCustomHeaderValue)
// Send test responses
//nolint:lll
dataBytes := []byte(`data: {"error":{"message":"The server had an error while processing your request. Sorry about that!", "type":"server_ error", "param":null,"code":null}}`)
dataBytes = append(dataBytes, []byte("\n\ndata: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
value := stream.Header().Get(xCustomHeader)
if value != xCustomHeaderValue {
t.Errorf("expected %s to be %s", xCustomHeaderValue, value)
}
}
func TestCreateChatCompletionStreamWithRatelimitHeaders(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
for k, v := range rateLimitHeaders {
switch val := v.(type) {
case int:
w.Header().Set(k, strconv.Itoa(val))
default:
w.Header().Set(k, fmt.Sprintf("%s", v))
}
}
// Send test responses
//nolint:lll
dataBytes := []byte(`data: {"error":{"message":"The server had an error while processing your request. Sorry about that!", "type":"server_ error", "param":null,"code":null}}`)
dataBytes = append(dataBytes, []byte("\n\ndata: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
headers := stream.GetRateLimitHeaders()
bs1, _ := json.Marshal(headers)
bs2, _ := json.Marshal(rateLimitHeaders)
if string(bs1) != string(bs2) {
t.Errorf("expected rate limit header %s to be %s", bs2, bs1)
}
}
func TestCreateChatCompletionStreamErrorWithDataPrefix(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
// Send test responses
//nolint:lll
dataBytes := []byte(`data: {"error":{"message":"The server had an error while processing your request. Sorry about that!", "type":"server_ error", "param":null,"code":null}}`)
dataBytes = append(dataBytes, []byte("\n\ndata: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
_, streamErr := stream.Recv()
checks.HasError(t, streamErr, "stream.Recv() did not return error")
var apiErr *openai.APIError
if !errors.As(streamErr, &apiErr) {
t.Errorf("stream.Recv() did not return APIError")
}
t.Logf("%+v\n", apiErr)
}
func TestCreateChatCompletionStreamRateLimitError(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(429)
// Send test responses
dataBytes := []byte(`{"error":{` +
`"message": "You are sending requests too quickly.",` +
`"type":"rate_limit_reached",` +
`"param":null,` +
`"code":"rate_limit_reached"}}`)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
_, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
var apiErr *openai.APIError
if !errors.As(err, &apiErr) {
t.Errorf("TestCreateChatCompletionStreamRateLimitError did not return APIError")
}
t.Logf("%+v\n", apiErr)
}
func TestCreateChatCompletionStreamWithRefusal(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
dataBytes := []byte{}
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"1","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"2","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"refusal":"Hello"},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"3","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"refusal":" World"},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"4","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 2000,
Model: openai.GPT4oMini20240718,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
expectedResponses := []openai.ChatCompletionStreamResponse{
{
ID: "1",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{},
},
},
},
{
ID: "2",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Refusal: "Hello",
},
},
},
},
{
ID: "3",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Refusal: " World",
},
},
},
},
{
ID: "4",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
FinishReason: "stop",
},
},
},
}
for ix, expectedResponse := range expectedResponses {
b, _ := json.Marshal(expectedResponse)
t.Logf("%d: %s", ix, string(b))
receivedResponse, streamErr := stream.Recv()
checks.NoError(t, streamErr, "stream.Recv() failed")
if !compareChatResponses(expectedResponse, receivedResponse) {
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
}
}
_, streamErr := stream.Recv()
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
}
}
func TestCreateChatCompletionStreamWithLogprobs(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
// Send test responses
dataBytes := []byte{}
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"1","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"2","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":{"content":[{"token":"Hello","logprob":-0.000020458236,"bytes":[72,101,108,108,111],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"3","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":" World"},"logprobs":{"content":[{"token":" World","logprob":-0.00055303273,"bytes":[32,87,111,114,108,100],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"4","object":"chat.completion.chunk","created":1729585728,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_d9767fc5b9","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 2000,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
expectedResponses := []openai.ChatCompletionStreamResponse{
{
ID: "1",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{},
Logprobs: &openai.ChatCompletionStreamChoiceLogprobs{
Content: []openai.ChatCompletionTokenLogprob{},
},
},
},
},
{
ID: "2",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "Hello",
},
Logprobs: &openai.ChatCompletionStreamChoiceLogprobs{
Content: []openai.ChatCompletionTokenLogprob{
{
Token: "Hello",
Logprob: -0.000020458236,
Bytes: []int64{72, 101, 108, 108, 111},
TopLogprobs: []openai.ChatCompletionTokenLogprobTopLogprob{},
},
},
},
},
},
},
{
ID: "3",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: " World",
},
Logprobs: &openai.ChatCompletionStreamChoiceLogprobs{
Content: []openai.ChatCompletionTokenLogprob{
{
Token: " World",
Logprob: -0.00055303273,
Bytes: []int64{32, 87, 111, 114, 108, 100},
TopLogprobs: []openai.ChatCompletionTokenLogprobTopLogprob{},
},
},
},
},
},
},
{
ID: "4",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.GPT4oMini20240718,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{},
FinishReason: "stop",
},
},
},
}
for ix, expectedResponse := range expectedResponses {
b, _ := json.Marshal(expectedResponse)
t.Logf("%d: %s", ix, string(b))
receivedResponse, streamErr := stream.Recv()
checks.NoError(t, streamErr, "stream.Recv() failed")
if !compareChatResponses(expectedResponse, receivedResponse) {
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
}
}
_, streamErr := stream.Recv()
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
}
}
func TestAzureCreateChatCompletionStreamRateLimitError(t *testing.T) {
wantCode := "429"
wantMessage := "Requests to the Creates a completion for the chat message Operation under Azure OpenAI API " +
"version 2023-03-15-preview have exceeded token rate limit of your current OpenAI S0 pricing tier. " +
"Please retry after 20 seconds. " +
"Please go here: https://aka.ms/oai/quotaincrease if you would like to further increase the default rate limit."
client, server, teardown := setupAzureTestServer()
defer teardown()
server.RegisterHandler("/openai/deployments/gpt-35-turbo/chat/completions",
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
// Send test responses
dataBytes := []byte(`{"error": { "code": "` + wantCode + `", "message": "` + wantMessage + `"}}`)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
apiErr := &openai.APIError{}
_, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
if !errors.As(err, &apiErr) {
t.Errorf("Did not return APIError: %+v\n", apiErr)
return
}
if apiErr.HTTPStatusCode != http.StatusTooManyRequests {
t.Errorf("Did not return HTTPStatusCode got = %d, want = %d\n", apiErr.HTTPStatusCode, http.StatusTooManyRequests)
return
}
code, ok := apiErr.Code.(string)
if !ok || code != wantCode {
t.Errorf("Did not return Code. got = %v, want = %s\n", apiErr.Code, wantCode)
return
}
if apiErr.Message != wantMessage {
t.Errorf("Did not return Message. got = %s, want = %s\n", apiErr.Message, wantMessage)
return
}
}
func TestCreateChatCompletionStreamStreamOptions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
// Send test responses
var dataBytes []byte
//nolint:lll
data := `{"id":"1","object":"completion","created":1598069254,"model":"gpt-3.5-turbo","system_fingerprint": "fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":"response1"},"finish_reason":"max_tokens"}],"usage":null}`
dataBytes = append(dataBytes, []byte("data: "+data+"\n\n")...)
//nolint:lll
data = `{"id":"2","object":"completion","created":1598069255,"model":"gpt-3.5-turbo","system_fingerprint": "fp_d9767fc5b9","choices":[{"index":0,"delta":{"content":"response2"},"finish_reason":"max_tokens"}],"usage":null}`
dataBytes = append(dataBytes, []byte("data: "+data+"\n\n")...)
//nolint:lll
data = `{"id":"3","object":"completion","created":1598069256,"model":"gpt-3.5-turbo","system_fingerprint": "fp_d9767fc5b9","choices":[],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`
dataBytes = append(dataBytes, []byte("data: "+data+"\n\n")...)
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
StreamOptions: &openai.StreamOptions{
IncludeUsage: true,
},
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
expectedResponses := []openai.ChatCompletionStreamResponse{
{
ID: "1",
Object: "completion",
Created: 1598069254,
Model: openai.GPT3Dot5Turbo,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "response1",
},
FinishReason: "max_tokens",
},
},
},
{
ID: "2",
Object: "completion",
Created: 1598069255,
Model: openai.GPT3Dot5Turbo,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{
{
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "response2",
},
FinishReason: "max_tokens",
},
},
},
{
ID: "3",
Object: "completion",
Created: 1598069256,
Model: openai.GPT3Dot5Turbo,
SystemFingerprint: "fp_d9767fc5b9",
Choices: []openai.ChatCompletionStreamChoice{},
Usage: &openai.Usage{
PromptTokens: 1,
CompletionTokens: 1,
TotalTokens: 2,
},
},
}
for ix, expectedResponse := range expectedResponses {
b, _ := json.Marshal(expectedResponse)
t.Logf("%d: %s", ix, string(b))
receivedResponse, streamErr := stream.Recv()
checks.NoError(t, streamErr, "stream.Recv() failed")
if !compareChatResponses(expectedResponse, receivedResponse) {
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
}
}
_, streamErr := stream.Recv()
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
}
_, streamErr = stream.Recv()
checks.ErrorIs(t, streamErr, io.EOF, "stream.Recv() did not return EOF when the stream is finished")
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF when the stream is finished: %v", streamErr)
}
}
// Helper funcs.
func compareChatResponses(r1, r2 openai.ChatCompletionStreamResponse) bool {
if r1.ID != r2.ID || r1.Object != r2.Object || r1.Created != r2.Created || r1.Model != r2.Model {
return false
}
if len(r1.Choices) != len(r2.Choices) {
return false
}
for i := range r1.Choices {
if !compareChatStreamResponseChoices(r1.Choices[i], r2.Choices[i]) {
return false
}
}
if r1.Usage != nil || r2.Usage != nil {
if r1.Usage == nil || r2.Usage == nil {
return false
}
if r1.Usage.PromptTokens != r2.Usage.PromptTokens || r1.Usage.CompletionTokens != r2.Usage.CompletionTokens ||
r1.Usage.TotalTokens != r2.Usage.TotalTokens {
return false
}
}
return true
}
func TestCreateChatCompletionStreamWithReasoningModel(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
dataBytes := []byte{}
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"1","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"2","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"3","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":" from"},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"4","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{"content":" O3Mini"},"finish_reason":null}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
//nolint:lll
dataBytes = append(dataBytes, []byte(`data: {"id":"5","object":"chat.completion.chunk","created":1729585728,"model":"o3-mini-2025-01-31","system_fingerprint":"fp_mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}`)...)
dataBytes = append(dataBytes, []byte("\n\n")...)
dataBytes = append(dataBytes, []byte("data: [DONE]\n\n")...)
_, err := w.Write(dataBytes)
checks.NoError(t, err, "Write error")
})
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxCompletionTokens: 2000,
Model: openai.O3Mini20250131,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
checks.NoError(t, err, "CreateCompletionStream returned error")
defer stream.Close()
expectedResponses := []openai.ChatCompletionStreamResponse{
{
ID: "1",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.O3Mini20250131,
SystemFingerprint: "fp_mini",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Role: "assistant",
},
},
},
},
{
ID: "2",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.O3Mini20250131,
SystemFingerprint: "fp_mini",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "Hello",
},
},
},
},
{
ID: "3",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.O3Mini20250131,
SystemFingerprint: "fp_mini",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: " from",
},
},
},
},
{
ID: "4",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.O3Mini20250131,
SystemFingerprint: "fp_mini",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: " O3Mini",
},
},
},
},
{
ID: "5",
Object: "chat.completion.chunk",
Created: 1729585728,
Model: openai.O3Mini20250131,
SystemFingerprint: "fp_mini",
Choices: []openai.ChatCompletionStreamChoice{
{
Index: 0,
Delta: openai.ChatCompletionStreamChoiceDelta{},
FinishReason: "stop",
},
},
},
}
for ix, expectedResponse := range expectedResponses {
b, _ := json.Marshal(expectedResponse)
t.Logf("%d: %s", ix, string(b))
receivedResponse, streamErr := stream.Recv()
checks.NoError(t, streamErr, "stream.Recv() failed")
if !compareChatResponses(expectedResponse, receivedResponse) {
t.Errorf("Stream response %v is %v, expected %v", ix, receivedResponse, expectedResponse)
}
}
_, streamErr := stream.Recv()
if !errors.Is(streamErr, io.EOF) {
t.Errorf("stream.Recv() did not return EOF in the end: %v", streamErr)
}
}
func TestCreateChatCompletionStreamReasoningValidatorFails(t *testing.T) {
client, _, _ := setupOpenAITestServer()
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 100, // This will trigger the validator to fail
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
if stream != nil {
t.Error("Expected nil stream when validation fails")
stream.Close()
}
if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated, got: %v", err)
}
}
func TestCreateChatCompletionStreamO3ReasoningValidatorFails(t *testing.T) {
client, _, _ := setupOpenAITestServer()
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 100, // This will trigger the validator to fail
Model: openai.O3,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
if stream != nil {
t.Error("Expected nil stream when validation fails")
stream.Close()
}
if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated for O3, got: %v", err)
}
}
func TestCreateChatCompletionStreamO4MiniReasoningValidatorFails(t *testing.T) {
client, _, _ := setupOpenAITestServer()
stream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 100, // This will trigger the validator to fail
Model: openai.O4Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Stream: true,
})
if stream != nil {
t.Error("Expected nil stream when validation fails")
stream.Close()
}
if !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {
t.Errorf("Expected ErrReasoningModelMaxTokensDeprecated for O4Mini, got: %v", err)
}
}
func compareChatStreamResponseChoices(c1, c2 openai.ChatCompletionStreamChoice) bool {
if c1.Index != c2.Index {
return false
}
if c1.Delta.Content != c2.Delta.Content {
return false
}
if c1.FinishReason != c2.FinishReason {
return false
}
return true
}
================================================
FILE: chat_test.go
================================================
package openai_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"testing"
"time"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/internal/test/checks"
"github.com/sashabaranov/go-openai/jsonschema"
)
const (
xCustomHeader = "X-CUSTOM-HEADER"
xCustomHeaderValue = "test"
)
var rateLimitHeaders = map[string]any{
"x-ratelimit-limit-requests": 60,
"x-ratelimit-limit-tokens": 150000,
"x-ratelimit-remaining-requests": 59,
"x-ratelimit-remaining-tokens": 149984,
"x-ratelimit-reset-requests": "1s",
"x-ratelimit-reset-tokens": "6m0s",
}
func TestChatCompletionsWrongModel(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
req := openai.ChatCompletionRequest{
MaxTokens: 5,
Model: "ada",
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
}
_, err := client.CreateChatCompletion(ctx, req)
msg := fmt.Sprintf("CreateChatCompletion should return wrong model error, returned: %s", err)
checks.ErrorIs(t, err, openai.ErrChatCompletionInvalidModel, msg)
}
func TestO1ModelsChatCompletionsDeprecatedFields(t *testing.T) {
tests := []struct {
name string
in openai.ChatCompletionRequest
expectedError error
}{
{
name: "o1-preview_MaxTokens_deprecated",
in: openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.O1Preview,
},
expectedError: openai.ErrReasoningModelMaxTokensDeprecated,
},
{
name: "o1-mini_MaxTokens_deprecated",
in: openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.O1Mini,
},
expectedError: openai.ErrReasoningModelMaxTokensDeprecated,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
_, err := client.CreateChatCompletion(ctx, tt.in)
checks.HasError(t, err)
msg := fmt.Sprintf("CreateChatCompletion should return wrong model error, returned: %s", err)
checks.ErrorIs(t, err, tt.expectedError, msg)
})
}
}
func TestO1ModelsChatCompletionsBetaLimitations(t *testing.T) {
tests := []struct {
name string
in openai.ChatCompletionRequest
expectedError error
}{
{
name: "log_probs_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
LogProbs: true,
Model: openai.O1Preview,
},
expectedError: openai.ErrReasoningModelLimitationsLogprobs,
},
{
name: "set_temperature_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O1Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(2),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_top_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O1Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_n_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O1Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(1),
N: 2,
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_presence_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O1Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
PresencePenalty: float32(1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_frequency_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O1Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
FrequencyPenalty: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
_, err := client.CreateChatCompletion(ctx, tt.in)
checks.HasError(t, err)
msg := fmt.Sprintf("CreateChatCompletion should return wrong model error, returned: %s", err)
checks.ErrorIs(t, err, tt.expectedError, msg)
})
}
}
func TestO3ModelsChatCompletionsBetaLimitations(t *testing.T) {
tests := []struct {
name string
in openai.ChatCompletionRequest
expectedError error
}{
{
name: "log_probs_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
LogProbs: true,
Model: openai.O3Mini,
},
expectedError: openai.ErrReasoningModelLimitationsLogprobs,
},
{
name: "set_temperature_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(2),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_top_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_n_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(1),
N: 2,
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_presence_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
PresencePenalty: float32(1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_frequency_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.O3Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
FrequencyPenalty: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
_, err := client.CreateChatCompletion(ctx, tt.in)
checks.HasError(t, err)
msg := fmt.Sprintf("CreateChatCompletion should return wrong model error, returned: %s", err)
checks.ErrorIs(t, err, tt.expectedError, msg)
})
}
}
func TestGPT5ModelsChatCompletionsBetaLimitations(t *testing.T) {
tests := []struct {
name string
in openai.ChatCompletionRequest
expectedError error
}{
{
name: "log_probs_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
LogProbs: true,
Model: openai.GPT5,
},
expectedError: openai.ErrReasoningModelLimitationsLogprobs,
},
{
name: "set_temperature_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.GPT5Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(2),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_top_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.GPT5Nano,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_n_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.GPT5ChatLatest,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
Temperature: float32(1),
TopP: float32(1),
N: 2,
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_presence_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.GPT5,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
PresencePenalty: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
{
name: "set_frequency_penalty_unsupported",
in: openai.ChatCompletionRequest{
MaxCompletionTokens: 1000,
Model: openai.GPT5Mini,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
},
{
Role: openai.ChatMessageRoleAssistant,
},
},
FrequencyPenalty: float32(0.1),
},
expectedError: openai.ErrReasoningModelLimitationsOther,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
_, err := client.CreateChatCompletion(ctx, tt.in)
checks.HasError(t, err)
msg := fmt.Sprintf("CreateChatCompletion should return wrong model error, returned: %s", err)
checks.ErrorIs(t, err, tt.expectedError, msg)
})
}
}
func TestChatRequestOmitEmpty(t *testing.T) {
data, err := json.Marshal(openai.ChatCompletionRequest{
// We set model b/c it's required, so omitempty doesn't make sense
Model: "gpt-4",
})
checks.NoError(t, err)
// messages is also required so isn't omitted
const expected = `{"model":"gpt-4","messages":null}`
if string(data) != expected {
t.Errorf("expected JSON with all empty fields to be %v but was %v", expected, string(data))
}
}
func TestChatCompletionsWithStream(t *testing.T) {
config := openai.DefaultConfig("whatever")
config.BaseURL = "http://localhost/v1"
client := openai.NewClientWithConfig(config)
ctx := context.Background()
req := openai.ChatCompletionRequest{
Stream: true,
}
_, err := client.CreateChatCompletion(ctx, req)
checks.ErrorIs(t, err, openai.ErrChatCompletionStreamNotSupported, "unexpected error")
}
// TestCompletions Tests the completions endpoint of the API using the mocked server.
func TestChatCompletions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
}
// TestCompletions Tests the completions endpoint of the API using the mocked server.
func TestO1ModelChatCompletions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
Model: openai.O1Preview,
MaxCompletionTokens: 1000,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
}
func TestO3ModelChatCompletions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
Model: openai.O3Mini,
MaxCompletionTokens: 1000,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
}
func TestDeepseekR1ModelChatCompletions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleDeepseekR1ChatCompletionEndpoint)
_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
Model: "deepseek-reasoner",
MaxCompletionTokens: 100,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
}
// TestCompletions Tests the completions endpoint of the API using the mocked server.
func TestChatCompletionsWithHeaders(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
resp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
a := resp.Header().Get(xCustomHeader)
_ = a
if resp.Header().Get(xCustomHeader) != xCustomHeaderValue {
t.Errorf("expected header %s to be %s", xCustomHeader, xCustomHeaderValue)
}
}
// TestChatCompletionsWithRateLimitHeaders Tests the completions endpoint of the API using the mocked server.
func TestChatCompletionsWithRateLimitHeaders(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
resp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
})
checks.NoError(t, err, "CreateChatCompletion error")
headers := resp.GetRateLimitHeaders()
resetRequests := headers.ResetRequests.String()
if resetRequests != rateLimitHeaders["x-ratelimit-reset-requests"] {
t.Errorf("expected resetRequests %s to be %s", resetRequests, rateLimitHeaders["x-ratelimit-reset-requests"])
}
resetRequestsTime := headers.ResetRequests.Time()
if resetRequestsTime.Before(time.Now()) {
t.Errorf("unexpected reset requests: %v", resetRequestsTime)
}
bs1, _ := json.Marshal(headers)
bs2, _ := json.Marshal(rateLimitHeaders)
if string(bs1) != string(bs2) {
t.Errorf("expected rate limit header %s to be %s", bs2, bs1)
}
}
// TestChatCompletionsFunctions tests including a function call.
func TestChatCompletionsFunctions(t *testing.T) {
client, server, teardown := setupOpenAITestServer()
defer teardown()
server.RegisterHandler("/v1/chat/completions", handleChatCompletionEndpoint)
t.Run("bytes", func(t *testing.T) {
//nolint:lll
msg := json.RawMessage(`{"properties":{"count":{"type":"integer","description":"total number of words in sentence"},"words":{"items":{"type":"string"},"type":"array","description":"list of words in sentence"}},"type":"object","required":["count","words"]}`)
_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
MaxTokens: 5,
Model: openai.GPT3Dot5Turbo0613,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Hello!",
},
},
Functions: []openai.FunctionDefinition{{
Name: "test",
Parameters: &msg,
}},
})
checks.NoError(t, err, "CreateChatCompletion with functions error")
})
t.Run("struct", func(t *testing.T) {
type testMessage struct {
Count int `json:"count"`
Words []string `json:"words"`
}
msg := testMessage{
Count: 2,
Words: []string{"hello", "world"},
}
_, err :=
gitextract_r1hjdmvj/ ├── .codecov.yml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── close-inactive-issues.yml │ ├── integration-tests.yml │ └── pr.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── api_integration_test.go ├── api_internal_test.go ├── assistant.go ├── assistant_test.go ├── audio.go ├── audio_api_test.go ├── audio_test.go ├── batch.go ├── batch_test.go ├── chat.go ├── chat_stream.go ├── chat_stream_test.go ├── chat_test.go ├── client.go ├── client_test.go ├── common.go ├── completion.go ├── completion_test.go ├── config.go ├── config_test.go ├── edits.go ├── edits_test.go ├── embeddings.go ├── embeddings_test.go ├── engines.go ├── engines_test.go ├── error.go ├── error_test.go ├── example_test.go ├── examples/ │ ├── README.md │ ├── chatbot/ │ │ └── main.go │ ├── completion/ │ │ └── main.go │ ├── completion-with-tool/ │ │ └── main.go │ ├── images/ │ │ └── main.go │ └── voice-to-text/ │ └── main.go ├── files.go ├── files_api_test.go ├── files_test.go ├── fine_tunes.go ├── fine_tunes_test.go ├── fine_tuning_job.go ├── fine_tuning_job_test.go ├── go.mod ├── image.go ├── image_api_test.go ├── image_test.go ├── internal/ │ ├── error_accumulator.go │ ├── error_accumulator_test.go │ ├── form_builder.go │ ├── form_builder_test.go │ ├── marshaller.go │ ├── marshaller_test.go │ ├── request_builder.go │ ├── request_builder_test.go │ ├── test/ │ │ ├── checks/ │ │ │ ├── checks.go │ │ │ └── checks_test.go │ │ ├── failer.go │ │ ├── failer_test.go │ │ ├── helpers.go │ │ ├── helpers_test.go │ │ ├── server.go │ │ └── server_test.go │ ├── unmarshaler.go │ └── unmarshaler_test.go ├── jsonschema/ │ ├── containsref_test.go │ ├── json.go │ ├── json_additional_test.go │ ├── json_errors_test.go │ ├── json_test.go │ ├── validate.go │ └── validate_test.go ├── messages.go ├── messages_test.go ├── models.go ├── models_test.go ├── moderation.go ├── moderation_test.go ├── openai_test.go ├── ratelimit.go ├── reasoning_validator.go ├── run.go ├── run_test.go ├── speech.go ├── speech_test.go ├── stream.go ├── stream_reader.go ├── stream_reader_test.go ├── stream_test.go ├── thread.go ├── thread_test.go ├── vector_store.go └── vector_store_test.go
SYMBOL INDEX (970 symbols across 90 files)
FILE: api_integration_test.go
function TestAPI (line 18) | func TestAPI(t *testing.T) {
function TestCompletionStream (line 113) | func TestCompletionStream(t *testing.T) {
function TestAPIError (line 148) | func TestAPIError(t *testing.T) {
function TestChatCompletionResponseFormat_JSONSchema (line 183) | func TestChatCompletionResponseFormat_JSONSchema(t *testing.T) {
function TestChatCompletionStructuredOutputsFunctionCalling (line 240) | func TestChatCompletionStructuredOutputsFunctionCalling(t *testing.T) {
FILE: api_internal_test.go
function TestOpenAIFullURL (line 8) | func TestOpenAIFullURL(t *testing.T) {
function TestRequestAuthHeader (line 39) | func TestRequestAuthHeader(t *testing.T) {
function TestAzureFullURL (line 110) | func TestAzureFullURL(t *testing.T) {
function TestCloudflareAzureFullURL (line 163) | func TestCloudflareAzureFullURL(t *testing.T) {
FILE: assistant.go
constant assistantsSuffix (line 12) | assistantsSuffix = "/assistants"
constant assistantsFilesSuffix (line 13) | assistantsFilesSuffix = "/files"
type Assistant (line 16) | type Assistant struct
type AssistantToolType (line 35) | type AssistantToolType
constant AssistantToolTypeCodeInterpreter (line 38) | AssistantToolTypeCodeInterpreter AssistantToolType = "code_interpreter"
constant AssistantToolTypeRetrieval (line 39) | AssistantToolTypeRetrieval AssistantToolType = "retrieval"
constant AssistantToolTypeFunction (line 40) | AssistantToolTypeFunction AssistantToolType = "function"
constant AssistantToolTypeFileSearch (line 41) | AssistantToolTypeFileSearch AssistantToolType = "file_search"
type AssistantTool (line 44) | type AssistantTool struct
type AssistantToolFileSearch (line 49) | type AssistantToolFileSearch struct
type AssistantToolCodeInterpreter (line 53) | type AssistantToolCodeInterpreter struct
type AssistantToolResource (line 57) | type AssistantToolResource struct
type AssistantRequest (line 67) | type AssistantRequest struct
method MarshalJSON (line 85) | func (a AssistantRequest) MarshalJSON() ([]byte, error) {
type AssistantsList (line 102) | type AssistantsList struct
type AssistantDeleteResponse (line 110) | type AssistantDeleteResponse struct
type AssistantFile (line 118) | type AssistantFile struct
type AssistantFileRequest (line 127) | type AssistantFileRequest struct
type AssistantFilesList (line 131) | type AssistantFilesList struct
method CreateAssistant (line 138) | func (c *Client) CreateAssistant(ctx context.Context, request AssistantR...
method RetrieveAssistant (line 150) | func (c *Client) RetrieveAssistant(
method ModifyAssistant (line 166) | func (c *Client) ModifyAssistant(
method DeleteAssistant (line 183) | func (c *Client) DeleteAssistant(
method ListAssistants (line 199) | func (c *Client) ListAssistants(
method CreateAssistantFile (line 237) | func (c *Client) CreateAssistantFile(
method RetrieveAssistantFile (line 255) | func (c *Client) RetrieveAssistantFile(
method DeleteAssistantFile (line 272) | func (c *Client) DeleteAssistantFile(
method ListAssistantFiles (line 289) | func (c *Client) ListAssistantFiles(
FILE: assistant_test.go
function TestAssistant (line 16) | func TestAssistant(t *testing.T) {
function TestAzureAssistant (line 259) | func TestAzureAssistant(t *testing.T) {
FILE: audio.go
constant Whisper1 (line 16) | Whisper1 = "whisper-1"
type AudioResponseFormat (line 20) | type AudioResponseFormat
constant AudioResponseFormatJSON (line 23) | AudioResponseFormatJSON AudioResponseFormat = "json"
constant AudioResponseFormatText (line 24) | AudioResponseFormatText AudioResponseFormat = "text"
constant AudioResponseFormatSRT (line 25) | AudioResponseFormatSRT AudioResponseFormat = "srt"
constant AudioResponseFormatVerboseJSON (line 26) | AudioResponseFormatVerboseJSON AudioResponseFormat = "verbose_json"
constant AudioResponseFormatVTT (line 27) | AudioResponseFormatVTT AudioResponseFormat = "vtt"
type TranscriptionTimestampGranularity (line 30) | type TranscriptionTimestampGranularity
constant TranscriptionTimestampGranularityWord (line 33) | TranscriptionTimestampGranularityWord TranscriptionTimestampGranulari...
constant TranscriptionTimestampGranularitySegment (line 34) | TranscriptionTimestampGranularitySegment TranscriptionTimestampGranulari...
type AudioRequest (line 38) | type AudioRequest struct
method HasJSONResponse (line 150) | func (r AudioRequest) HasJSONResponse() bool {
type AudioResponse (line 55) | type AudioResponse struct
type audioTextResponse (line 82) | type audioTextResponse struct
method ToAudioResponse (line 88) | func (r *audioTextResponse) ToAudioResponse() AudioResponse {
method CreateTranscription (line 96) | func (c *Client) CreateTranscription(
method CreateTranslation (line 104) | func (c *Client) CreateTranslation(
method callAudioAPI (line 112) | func (c *Client) callAudioAPI(
function audioMultipartForm (line 156) | func audioMultipartForm(request AudioRequest, b utils.FormBuilder) error {
function createFileField (line 213) | func createFileField(request AudioRequest, b utils.FormBuilder) error {
FILE: audio_api_test.go
function TestAudio (line 21) | func TestAudio(t *testing.T) {
function TestAudioWithOptionalArgs (line 68) | func TestAudioWithOptionalArgs(t *testing.T) {
function handleAudioEndpoint (line 114) | func handleAudioEndpoint(w http.ResponseWriter, r *http.Request) {
FILE: audio_test.go
function TestAudioWithFailingFormBuilder (line 19) | func TestAudioWithFailingFormBuilder(t *testing.T) {
function TestCreateFileField (line 66) | func TestCreateFileField(t *testing.T) {
type failingFormBuilder (line 116) | type failingFormBuilder struct
method CreateFormFile (line 118) | func (f *failingFormBuilder) CreateFormFile(_ string, _ *os.File) error {
method CreateFormFileReader (line 122) | func (f *failingFormBuilder) CreateFormFileReader(_ string, _ io.Reade...
method WriteField (line 126) | func (f *failingFormBuilder) WriteField(_, _ string) error {
method Close (line 130) | func (f *failingFormBuilder) Close() error {
method FormDataContentType (line 134) | func (f *failingFormBuilder) FormDataContentType() string {
type failingAudioRequestBuilder (line 139) | type failingAudioRequestBuilder struct
method Build (line 141) | func (f *failingAudioRequestBuilder) Build(
type errorHTTPClient (line 151) | type errorHTTPClient struct
method Do (line 153) | func (e *errorHTTPClient) Do(_ *http.Request) (*http.Response, error) {
function TestCallAudioAPIMultipartFormError (line 157) | func TestCallAudioAPIMultipartFormError(t *testing.T) {
function TestCallAudioAPINewRequestError (line 176) | func TestCallAudioAPINewRequestError(t *testing.T) {
function TestCallAudioAPISendRequestErrorJSON (line 198) | func TestCallAudioAPISendRequestErrorJSON(t *testing.T) {
function TestCallAudioAPISendRequestErrorText (line 221) | func TestCallAudioAPISendRequestErrorText(t *testing.T) {
FILE: batch.go
constant batchesSuffix (line 12) | batchesSuffix = "/batches"
type BatchEndpoint (line 14) | type BatchEndpoint
constant BatchEndpointChatCompletions (line 17) | BatchEndpointChatCompletions BatchEndpoint = "/v1/chat/completions"
constant BatchEndpointCompletions (line 18) | BatchEndpointCompletions BatchEndpoint = "/v1/completions"
constant BatchEndpointEmbeddings (line 19) | BatchEndpointEmbeddings BatchEndpoint = "/v1/embeddings"
type BatchLineItem (line 22) | type BatchLineItem interface
type BatchChatCompletionRequest (line 26) | type BatchChatCompletionRequest struct
method MarshalBatchLineItem (line 33) | func (r BatchChatCompletionRequest) MarshalBatchLineItem() []byte {
type BatchCompletionRequest (line 38) | type BatchCompletionRequest struct
method MarshalBatchLineItem (line 45) | func (r BatchCompletionRequest) MarshalBatchLineItem() []byte {
type BatchEmbeddingRequest (line 50) | type BatchEmbeddingRequest struct
method MarshalBatchLineItem (line 57) | func (r BatchEmbeddingRequest) MarshalBatchLineItem() []byte {
type Batch (line 62) | type Batch struct
type BatchRequestCounts (line 93) | type BatchRequestCounts struct
type CreateBatchRequest (line 99) | type CreateBatchRequest struct
type BatchResponse (line 106) | type BatchResponse struct
method CreateBatch (line 112) | func (c *Client) CreateBatch(
type UploadBatchFileRequest (line 129) | type UploadBatchFileRequest struct
method MarshalJSONL (line 134) | func (r *UploadBatchFileRequest) MarshalJSONL() []byte {
method AddChatCompletion (line 145) | func (r *UploadBatchFileRequest) AddChatCompletion(customerID string, ...
method AddCompletion (line 154) | func (r *UploadBatchFileRequest) AddCompletion(customerID string, body...
method AddEmbedding (line 163) | func (r *UploadBatchFileRequest) AddEmbedding(customerID string, body ...
method UploadBatchFile (line 173) | func (c *Client) UploadBatchFile(ctx context.Context, request UploadBatc...
type CreateBatchWithUploadFileRequest (line 184) | type CreateBatchWithUploadFileRequest struct
method CreateBatchWithUploadFile (line 192) | func (c *Client) CreateBatchWithUploadFile(
method RetrieveBatch (line 213) | func (c *Client) RetrieveBatch(
method CancelBatch (line 227) | func (c *Client) CancelBatch(
type ListBatchResponse (line 240) | type ListBatchResponse struct
method ListBatch (line 250) | func (c *Client) ListBatch(ctx context.Context, after *string, limit *in...
FILE: batch_test.go
function TestUploadBatchFile (line 14) | func TestUploadBatchFile(t *testing.T) {
function TestCreateBatch (line 34) | func TestCreateBatch(t *testing.T) {
function TestCreateBatchWithUploadFile (line 47) | func TestCreateBatchWithUploadFile(t *testing.T) {
function TestRetrieveBatch (line 69) | func TestRetrieveBatch(t *testing.T) {
function TestCancelBatch (line 77) | func TestCancelBatch(t *testing.T) {
function TestListBatch (line 85) | func TestListBatch(t *testing.T) {
function TestUploadBatchFileRequest_AddChatCompletion (line 95) | func TestUploadBatchFileRequest_AddChatCompletion(t *testing.T) {
function TestUploadBatchFileRequest_AddCompletion (line 148) | func TestUploadBatchFileRequest_AddCompletion(t *testing.T) {
function TestUploadBatchFileRequest_AddEmbedding (line 189) | func TestUploadBatchFileRequest_AddEmbedding(t *testing.T) {
function handleBatchEndpoint (line 230) | func handleBatchEndpoint(w http.ResponseWriter, r *http.Request) {
function handleRetrieveBatchEndpoint (line 302) | func handleRetrieveBatchEndpoint(w http.ResponseWriter, r *http.Request) {
function handleCancelBatchEndpoint (line 336) | func handleCancelBatchEndpoint(w http.ResponseWriter, r *http.Request) {
FILE: chat.go
constant ChatMessageRoleSystem (line 14) | ChatMessageRoleSystem = "system"
constant ChatMessageRoleUser (line 15) | ChatMessageRoleUser = "user"
constant ChatMessageRoleAssistant (line 16) | ChatMessageRoleAssistant = "assistant"
constant ChatMessageRoleFunction (line 17) | ChatMessageRoleFunction = "function"
constant ChatMessageRoleTool (line 18) | ChatMessageRoleTool = "tool"
constant ChatMessageRoleDeveloper (line 19) | ChatMessageRoleDeveloper = "developer"
constant chatCompletionsSuffix (line 22) | chatCompletionsSuffix = "/chat/completions"
type Hate (line 30) | type Hate struct
type SelfHarm (line 34) | type SelfHarm struct
type Sexual (line 38) | type Sexual struct
type Violence (line 42) | type Violence struct
type JailBreak (line 47) | type JailBreak struct
type Profanity (line 52) | type Profanity struct
type ContentFilterResults (line 57) | type ContentFilterResults struct
type PromptAnnotation (line 66) | type PromptAnnotation struct
type ImageURLDetail (line 71) | type ImageURLDetail
constant ImageURLDetailHigh (line 74) | ImageURLDetailHigh ImageURLDetail = "high"
constant ImageURLDetailLow (line 75) | ImageURLDetailLow ImageURLDetail = "low"
constant ImageURLDetailAuto (line 76) | ImageURLDetailAuto ImageURLDetail = "auto"
type ChatMessageImageURL (line 79) | type ChatMessageImageURL struct
type ChatMessagePartType (line 84) | type ChatMessagePartType
constant ChatMessagePartTypeText (line 87) | ChatMessagePartTypeText ChatMessagePartType = "text"
constant ChatMessagePartTypeImageURL (line 88) | ChatMessagePartTypeImageURL ChatMessagePartType = "image_url"
type ChatMessagePart (line 91) | type ChatMessagePart struct
type ChatCompletionMessage (line 97) | type ChatCompletionMessage struct
method MarshalJSON (line 124) | func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 157) | func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
type ToolCall (line 192) | type ToolCall struct
type FunctionCall (line 200) | type FunctionCall struct
type ChatCompletionResponseFormatType (line 206) | type ChatCompletionResponseFormatType
constant ChatCompletionResponseFormatTypeJSONObject (line 209) | ChatCompletionResponseFormatTypeJSONObject ChatCompletionResponseFormatT...
constant ChatCompletionResponseFormatTypeJSONSchema (line 210) | ChatCompletionResponseFormatTypeJSONSchema ChatCompletionResponseFormatT...
constant ChatCompletionResponseFormatTypeText (line 211) | ChatCompletionResponseFormatTypeText ChatCompletionResponseFormatT...
type ChatCompletionResponseFormat (line 214) | type ChatCompletionResponseFormat struct
type ChatCompletionResponseFormatJSONSchema (line 219) | type ChatCompletionResponseFormatJSONSchema struct
method UnmarshalJSON (line 226) | func (r *ChatCompletionResponseFormatJSONSchema) UnmarshalJSON(data []...
type ChatCompletionRequestExtensions (line 253) | type ChatCompletionRequestExtensions struct
type ChatCompletionRequest (line 263) | type ChatCompletionRequest struct
type StreamOptions (line 338) | type StreamOptions struct
type ToolType (line 346) | type ToolType
constant ToolTypeFunction (line 349) | ToolTypeFunction ToolType = "function"
type Tool (line 352) | type Tool struct
type ToolChoice (line 357) | type ToolChoice struct
type ToolFunction (line 362) | type ToolFunction struct
type FunctionDefinition (line 366) | type FunctionDefinition struct
type TopLogProbs (line 381) | type TopLogProbs struct
type LogProb (line 388) | type LogProb struct
type LogProbs (line 398) | type LogProbs struct
type Prediction (line 403) | type Prediction struct
type FinishReason (line 408) | type FinishReason
method MarshalJSON (line 428) | func (r FinishReason) MarshalJSON() ([]byte, error) {
constant FinishReasonStop (line 411) | FinishReasonStop FinishReason = "stop"
constant FinishReasonLength (line 412) | FinishReasonLength FinishReason = "length"
constant FinishReasonFunctionCall (line 413) | FinishReasonFunctionCall FinishReason = "function_call"
constant FinishReasonToolCalls (line 414) | FinishReasonToolCalls FinishReason = "tool_calls"
constant FinishReasonContentFilter (line 415) | FinishReasonContentFilter FinishReason = "content_filter"
constant FinishReasonNull (line 416) | FinishReasonNull FinishReason = "null"
type ServiceTier (line 419) | type ServiceTier
constant ServiceTierAuto (line 422) | ServiceTierAuto ServiceTier = "auto"
constant ServiceTierDefault (line 423) | ServiceTierDefault ServiceTier = "default"
constant ServiceTierFlex (line 424) | ServiceTierFlex ServiceTier = "flex"
constant ServiceTierPriority (line 425) | ServiceTierPriority ServiceTier = "priority"
type ChatCompletionChoice (line 435) | type ChatCompletionChoice struct
type ChatCompletionResponse (line 451) | type ChatCompletionResponse struct
method CreateChatCompletion (line 466) | func (c *Client) CreateChatCompletion(
FILE: chat_stream.go
type ChatCompletionStreamChoiceDelta (line 8) | type ChatCompletionStreamChoiceDelta struct
type ChatCompletionStreamChoiceLogprobs (line 22) | type ChatCompletionStreamChoiceLogprobs struct
type ChatCompletionTokenLogprob (line 27) | type ChatCompletionTokenLogprob struct
type ChatCompletionTokenLogprobTopLogprob (line 34) | type ChatCompletionTokenLogprobTopLogprob struct
type ChatCompletionStreamChoice (line 40) | type ChatCompletionStreamChoice struct
type PromptFilterResult (line 48) | type PromptFilterResult struct
type ChatCompletionStreamResponse (line 53) | type ChatCompletionStreamResponse struct
type ChatCompletionStream (line 70) | type ChatCompletionStream struct
method CreateChatCompletionStream (line 78) | func (c *Client) CreateChatCompletionStream(
FILE: chat_stream_test.go
function TestChatCompletionsStreamWrongModel (line 17) | func TestChatCompletionsStreamWrongModel(t *testing.T) {
function TestCreateChatCompletionStream (line 39) | func TestCreateChatCompletionStream(t *testing.T) {
function TestCreateChatCompletionStreamError (line 135) | func TestCreateChatCompletionStreamError(t *testing.T) {
function TestCreateChatCompletionStreamWithHeaders (line 185) | func TestCreateChatCompletionStreamWithHeaders(t *testing.T) {
function TestCreateChatCompletionStreamWithRatelimitHeaders (line 221) | func TestCreateChatCompletionStreamWithRatelimitHeaders(t *testing.T) {
function TestCreateChatCompletionStreamErrorWithDataPrefix (line 266) | func TestCreateChatCompletionStreamErrorWithDataPrefix(t *testing.T) {
function TestCreateChatCompletionStreamRateLimitError (line 305) | func TestCreateChatCompletionStreamRateLimitError(t *testing.T) {
function TestCreateChatCompletionStreamWithRefusal (line 340) | func TestCreateChatCompletionStreamWithRefusal(t *testing.T) {
function TestCreateChatCompletionStreamWithLogprobs (line 460) | func TestCreateChatCompletionStreamWithLogprobs(t *testing.T) {
function TestAzureCreateChatCompletionStreamRateLimitError (line 605) | func TestAzureCreateChatCompletionStreamRateLimitError(t *testing.T) {
function TestCreateChatCompletionStreamStreamOptions (line 656) | func TestCreateChatCompletionStreamStreamOptions(t *testing.T) {
function compareChatResponses (line 771) | func compareChatResponses(r1, r2 openai.ChatCompletionStreamResponse) bo...
function TestCreateChatCompletionStreamWithReasoningModel (line 795) | func TestCreateChatCompletionStreamWithReasoningModel(t *testing.T) {
function TestCreateChatCompletionStreamReasoningValidatorFails (line 937) | func TestCreateChatCompletionStreamReasoningValidatorFails(t *testing.T) {
function TestCreateChatCompletionStreamO3ReasoningValidatorFails (line 962) | func TestCreateChatCompletionStreamO3ReasoningValidatorFails(t *testing....
function TestCreateChatCompletionStreamO4MiniReasoningValidatorFails (line 987) | func TestCreateChatCompletionStreamO4MiniReasoningValidatorFails(t *test...
function compareChatStreamResponseChoices (line 1012) | func compareChatStreamResponseChoices(c1, c2 openai.ChatCompletionStream...
FILE: chat_test.go
constant xCustomHeader (line 21) | xCustomHeader = "X-CUSTOM-HEADER"
constant xCustomHeaderValue (line 22) | xCustomHeaderValue = "test"
function TestChatCompletionsWrongModel (line 34) | func TestChatCompletionsWrongModel(t *testing.T) {
function TestO1ModelsChatCompletionsDeprecatedFields (line 55) | func TestO1ModelsChatCompletionsDeprecatedFields(t *testing.T) {
function TestO1ModelsChatCompletionsBetaLimitations (line 94) | func TestO1ModelsChatCompletionsBetaLimitations(t *testing.T) {
function TestO3ModelsChatCompletionsBetaLimitations (line 214) | func TestO3ModelsChatCompletionsBetaLimitations(t *testing.T) {
function TestGPT5ModelsChatCompletionsBetaLimitations (line 334) | func TestGPT5ModelsChatCompletionsBetaLimitations(t *testing.T) {
function TestChatRequestOmitEmpty (line 454) | func TestChatRequestOmitEmpty(t *testing.T) {
function TestChatCompletionsWithStream (line 468) | func TestChatCompletionsWithStream(t *testing.T) {
function TestChatCompletions (line 482) | func TestChatCompletions(t *testing.T) {
function TestO1ModelChatCompletions (line 500) | func TestO1ModelChatCompletions(t *testing.T) {
function TestO3ModelChatCompletions (line 517) | func TestO3ModelChatCompletions(t *testing.T) {
function TestDeepseekR1ModelChatCompletions (line 534) | func TestDeepseekR1ModelChatCompletions(t *testing.T) {
function TestChatCompletionsWithHeaders (line 552) | func TestChatCompletionsWithHeaders(t *testing.T) {
function TestChatCompletionsWithRateLimitHeaders (line 576) | func TestChatCompletionsWithRateLimitHeaders(t *testing.T) {
function TestChatCompletionsFunctions (line 610) | func TestChatCompletionsFunctions(t *testing.T) {
function TestAzureChatCompletions (line 759) | func TestAzureChatCompletions(t *testing.T) {
function TestMultipartChatCompletions (line 777) | func TestMultipartChatCompletions(t *testing.T) {
function TestMultipartChatMessageSerialization (line 807) | func TestMultipartChatMessageSerialization(t *testing.T) {
function handleChatCompletionEndpoint (line 880) | func handleChatCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
function handleDeepseekR1ChatCompletionEndpoint (line 962) | func handleDeepseekR1ChatCompletionEndpoint(w http.ResponseWriter, r *ht...
function getChatCompletionBody (line 1025) | func getChatCompletionBody(r *http.Request) (openai.ChatCompletionReques...
function TestFinishReason (line 1039) | func TestFinishReason(t *testing.T) {
function TestChatCompletionResponseFormatJSONSchema_UnmarshalJSON (line 1070) | func TestChatCompletionResponseFormatJSONSchema_UnmarshalJSON(t *testing...
function TestChatCompletionRequest_UnmarshalJSON (line 1150) | func TestChatCompletionRequest_UnmarshalJSON(t *testing.T) {
FILE: client.go
type Client (line 17) | type Client struct
method newRequest (line 113) | func (c *Client) newRequest(ctx context.Context, method, url string, s...
method sendRequest (line 130) | func (c *Client) sendRequest(req *http.Request, v Response) error {
method sendRequestRaw (line 158) | func (c *Client) sendRequestRaw(req *http.Request) (response RawRespon...
method setCommonHeaders (line 197) | func (c *Client) setCommonHeaders(req *http.Request) {
method fullURL (line 270) | func (c *Client) fullURL(suffix string, setters ...fullURLOption) stri...
method suffixWithAPIVersion (line 287) | func (c *Client) suffixWithAPIVersion(suffix string) string {
method baseURLWithAzureDeployment (line 297) | func (c *Client) baseURLWithAzureDeployment(baseURL, suffix, model str...
method handleErrorResp (line 309) | func (c *Client) handleErrorResp(resp *http.Response) error {
type Response (line 24) | type Response interface
type httpHeader (line 28) | type httpHeader
method SetHeader (line 30) | func (h *httpHeader) SetHeader(header http.Header) {
method Header (line 34) | func (h *httpHeader) Header() http.Header {
method GetRateLimitHeaders (line 38) | func (h *httpHeader) GetRateLimitHeaders() RateLimitHeaders {
type RawResponse (line 42) | type RawResponse struct
function NewClient (line 49) | func NewClient(authToken string) *Client {
function NewClientWithConfig (line 55) | func NewClientWithConfig(config ClientConfig) *Client {
function NewOrgClient (line 68) | func NewOrgClient(authToken, org string) *Client {
type requestOptions (line 74) | type requestOptions struct
type requestOption (line 79) | type requestOption
function withBody (line 81) | func withBody(body any) requestOption {
function withExtraBody (line 87) | func withExtraBody(extraBody map[string]any) requestOption {
function withContentType (line 101) | func withContentType(contentType string) requestOption {
function withBetaAssistantVersion (line 107) | func withBetaAssistantVersion(version string) requestOption {
function sendRequestStream (line 174) | func sendRequestStream[T streamable](client *Client, req *http.Request) ...
function isFailureStatusCode (line 219) | func isFailureStatusCode(resp *http.Response) bool {
function decodeResponse (line 223) | func decodeResponse(body io.Reader, v any) error {
function decodeString (line 238) | func decodeString(body io.Reader, output *string) error {
type fullURLOptions (line 247) | type fullURLOptions struct
type fullURLOption (line 251) | type fullURLOption
function withModel (line 253) | func withModel(model string) fullURLOption {
function containsSubstr (line 334) | func containsSubstr(s []string, e string) bool {
FILE: client_test.go
type failingRequestBuilder (line 19) | type failingRequestBuilder struct
method Build (line 21) | func (*failingRequestBuilder) Build(_ context.Context, _, _ string, _ ...
function TestClient (line 25) | func TestClient(t *testing.T) {
function TestSetCommonHeadersAnthropic (line 42) | func TestSetCommonHeadersAnthropic(t *testing.T) {
function TestDecodeResponse (line 57) | func TestDecodeResponse(t *testing.T) {
type errorReader (line 129) | type errorReader struct
method Read (line 133) | func (e *errorReader) Read(_ []byte) (n int, err error) {
function TestHandleErrorResp (line 137) | func TestHandleErrorResp(t *testing.T) {
function TestClientReturnsRequestBuilderErrors (line 266) | func TestClientReturnsRequestBuilderErrors(t *testing.T) {
function TestClientReturnsRequestBuilderErrorsAddition (line 473) | func TestClientReturnsRequestBuilderErrorsAddition(t *testing.T) {
function TestClient_suffixWithAPIVersion (line 488) | func TestClient_suffixWithAPIVersion(t *testing.T) {
function TestClient_baseURLWithAzureDeployment (line 549) | func TestClient_baseURLWithAzureDeployment(t *testing.T) {
FILE: common.go
type Usage (line 6) | type Usage struct
type CompletionTokensDetails (line 15) | type CompletionTokensDetails struct
type PromptTokensDetails (line 23) | type PromptTokensDetails struct
FILE: completion.go
constant O1Mini (line 13) | O1Mini = "o1-mini"
constant O1Mini20240912 (line 14) | O1Mini20240912 = "o1-mini-2024-09-12"
constant O1Preview (line 15) | O1Preview = "o1-preview"
constant O1Preview20240912 (line 16) | O1Preview20240912 = "o1-preview-2024-09-12"
constant O1 (line 17) | O1 = "o1"
constant O120241217 (line 18) | O120241217 = "o1-2024-12-17"
constant O3 (line 19) | O3 = "o3"
constant O320250416 (line 20) | O320250416 = "o3-2025-04-16"
constant O3Mini (line 21) | O3Mini = "o3-mini"
constant O3Mini20250131 (line 22) | O3Mini20250131 = "o3-mini-2025-01-31"
constant O4Mini (line 23) | O4Mini = "o4-mini"
constant O4Mini20250416 (line 24) | O4Mini20250416 = "o4-mini-2025-04-16"
constant GPT432K0613 (line 25) | GPT432K0613 = "gpt-4-32k-0613"
constant GPT432K0314 (line 26) | GPT432K0314 = "gpt-4-32k-0314"
constant GPT432K (line 27) | GPT432K = "gpt-4-32k"
constant GPT40613 (line 28) | GPT40613 = "gpt-4-0613"
constant GPT40314 (line 29) | GPT40314 = "gpt-4-0314"
constant GPT4o (line 30) | GPT4o = "gpt-4o"
constant GPT4o20240513 (line 31) | GPT4o20240513 = "gpt-4o-2024-05-13"
constant GPT4o20240806 (line 32) | GPT4o20240806 = "gpt-4o-2024-08-06"
constant GPT4o20241120 (line 33) | GPT4o20241120 = "gpt-4o-2024-11-20"
constant GPT4oLatest (line 34) | GPT4oLatest = "chatgpt-4o-latest"
constant GPT4oMini (line 35) | GPT4oMini = "gpt-4o-mini"
constant GPT4oMini20240718 (line 36) | GPT4oMini20240718 = "gpt-4o-mini-2024-07-18"
constant GPT4Turbo (line 37) | GPT4Turbo = "gpt-4-turbo"
constant GPT4Turbo20240409 (line 38) | GPT4Turbo20240409 = "gpt-4-turbo-2024-04-09"
constant GPT4Turbo0125 (line 39) | GPT4Turbo0125 = "gpt-4-0125-preview"
constant GPT4Turbo1106 (line 40) | GPT4Turbo1106 = "gpt-4-1106-preview"
constant GPT4TurboPreview (line 41) | GPT4TurboPreview = "gpt-4-turbo-preview"
constant GPT4VisionPreview (line 42) | GPT4VisionPreview = "gpt-4-vision-preview"
constant GPT4 (line 43) | GPT4 = "gpt-4"
constant GPT4Dot1 (line 44) | GPT4Dot1 = "gpt-4.1"
constant GPT4Dot120250414 (line 45) | GPT4Dot120250414 = "gpt-4.1-2025-04-14"
constant GPT4Dot1Mini (line 46) | GPT4Dot1Mini = "gpt-4.1-mini"
constant GPT4Dot1Mini20250414 (line 47) | GPT4Dot1Mini20250414 = "gpt-4.1-mini-2025-04-14"
constant GPT4Dot1Nano (line 48) | GPT4Dot1Nano = "gpt-4.1-nano"
constant GPT4Dot1Nano20250414 (line 49) | GPT4Dot1Nano20250414 = "gpt-4.1-nano-2025-04-14"
constant GPT4Dot5Preview (line 50) | GPT4Dot5Preview = "gpt-4.5-preview"
constant GPT4Dot5Preview20250227 (line 51) | GPT4Dot5Preview20250227 = "gpt-4.5-preview-2025-02-27"
constant GPT5 (line 52) | GPT5 = "gpt-5"
constant GPT5Mini (line 53) | GPT5Mini = "gpt-5-mini"
constant GPT5Nano (line 54) | GPT5Nano = "gpt-5-nano"
constant GPT5ChatLatest (line 55) | GPT5ChatLatest = "gpt-5-chat-latest"
constant GPT3Dot5Turbo0125 (line 56) | GPT3Dot5Turbo0125 = "gpt-3.5-turbo-0125"
constant GPT3Dot5Turbo1106 (line 57) | GPT3Dot5Turbo1106 = "gpt-3.5-turbo-1106"
constant GPT3Dot5Turbo0613 (line 58) | GPT3Dot5Turbo0613 = "gpt-3.5-turbo-0613"
constant GPT3Dot5Turbo0301 (line 59) | GPT3Dot5Turbo0301 = "gpt-3.5-turbo-0301"
constant GPT3Dot5Turbo16K (line 60) | GPT3Dot5Turbo16K = "gpt-3.5-turbo-16k"
constant GPT3Dot5Turbo16K0613 (line 61) | GPT3Dot5Turbo16K0613 = "gpt-3.5-turbo-16k-0613"
constant GPT3Dot5Turbo (line 62) | GPT3Dot5Turbo = "gpt-3.5-turbo"
constant GPT3Dot5TurboInstruct (line 63) | GPT3Dot5TurboInstruct = "gpt-3.5-turbo-instruct"
constant GPT3TextDavinci003 (line 65) | GPT3TextDavinci003 = "text-davinci-003"
constant GPT3TextDavinci002 (line 67) | GPT3TextDavinci002 = "text-davinci-002"
constant GPT3TextCurie001 (line 69) | GPT3TextCurie001 = "text-curie-001"
constant GPT3TextBabbage001 (line 71) | GPT3TextBabbage001 = "text-babbage-001"
constant GPT3TextAda001 (line 73) | GPT3TextAda001 = "text-ada-001"
constant GPT3TextDavinci001 (line 75) | GPT3TextDavinci001 = "text-davinci-001"
constant GPT3DavinciInstructBeta (line 77) | GPT3DavinciInstructBeta = "davinci-instruct-beta"
constant GPT3Davinci (line 79) | GPT3Davinci = "davinci"
constant GPT3Davinci002 (line 80) | GPT3Davinci002 = "davinci-002"
constant GPT3CurieInstructBeta (line 82) | GPT3CurieInstructBeta = "curie-instruct-beta"
constant GPT3Curie (line 83) | GPT3Curie = "curie"
constant GPT3Curie002 (line 84) | GPT3Curie002 = "curie-002"
constant GPT3Ada (line 86) | GPT3Ada = "ada"
constant GPT3Ada002 (line 87) | GPT3Ada002 = "ada-002"
constant GPT3Babbage (line 89) | GPT3Babbage = "babbage"
constant GPT3Babbage002 (line 90) | GPT3Babbage002 = "babbage-002"
constant CodexCodeDavinci002 (line 97) | CodexCodeDavinci002 = "code-davinci-002"
constant CodexCodeCushman001 (line 98) | CodexCodeCushman001 = "code-cushman-001"
constant CodexCodeDavinci001 (line 99) | CodexCodeDavinci001 = "code-davinci-001"
function checkEndpointSupportsModel (line 173) | func checkEndpointSupportsModel(endpoint, model string) bool {
function checkPromptType (line 177) | func checkPromptType(prompt any) bool {
type CompletionRequest (line 200) | type CompletionRequest struct
type CompletionChoice (line 231) | type CompletionChoice struct
type LogprobResult (line 239) | type LogprobResult struct
type CompletionResponse (line 247) | type CompletionResponse struct
method CreateCompletion (line 263) | func (c *Client) CreateCompletion(
FILE: completion_test.go
function TestCompletionsWrongModel (line 19) | func TestCompletionsWrongModel(t *testing.T) {
function TestCompletionsWrongModelO3 (line 37) | func TestCompletionsWrongModelO3(t *testing.T) {
function TestCompletionsWrongModelO4Mini (line 55) | func TestCompletionsWrongModelO4Mini(t *testing.T) {
function TestCompletionWithStream (line 72) | func TestCompletionWithStream(t *testing.T) {
function TestCompletions (line 85) | func TestCompletions(t *testing.T) {
function TestMultiplePromptsCompletionsWrong (line 100) | func TestMultiplePromptsCompletionsWrong(t *testing.T) {
function TestMultiplePromptsCompletions (line 117) | func TestMultiplePromptsCompletions(t *testing.T) {
function handleCompletionEndpoint (line 131) | func handleCompletionEndpoint(w http.ResponseWriter, r *http.Request) {
function getCompletionBody (line 207) | func getCompletionBody(r *http.Request) (openai.CompletionRequest, error) {
function TestCompletionWithO1Model (line 222) | func TestCompletionWithO1Model(t *testing.T) {
function TestCompletionWithGPT4DotModels (line 240) | func TestCompletionWithGPT4DotModels(t *testing.T) {
function TestCompletionWithGPT4oModels (line 273) | func TestCompletionWithGPT4oModels(t *testing.T) {
function TestCompletionWithGPT5Models (line 305) | func TestCompletionWithGPT5Models(t *testing.T) {
FILE: config.go
constant openaiAPIURLv1 (line 10) | openaiAPIURLv1 = "https://api.openai.com/v1"
constant defaultEmptyMessagesLimit (line 11) | defaultEmptyMessagesLimit uint = 300
constant azureAPIPrefix (line 13) | azureAPIPrefix = "openai"
constant azureDeploymentsPrefix (line 14) | azureDeploymentsPrefix = "deployments"
constant AnthropicAPIVersion (line 16) | AnthropicAPIVersion = "2023-06-01"
type APIType (line 19) | type APIType
constant APITypeOpenAI (line 22) | APITypeOpenAI APIType = "OPEN_AI"
constant APITypeAzure (line 23) | APITypeAzure APIType = "AZURE"
constant APITypeAzureAD (line 24) | APITypeAzureAD APIType = "AZURE_AD"
constant APITypeCloudflareAzure (line 25) | APITypeCloudflareAzure APIType = "CLOUDFLARE_AZURE"
constant APITypeAnthropic (line 26) | APITypeAnthropic APIType = "ANTHROPIC"
constant AzureAPIKeyHeader (line 29) | AzureAPIKeyHeader = "api-key"
constant defaultAssistantVersion (line 31) | defaultAssistantVersion = "v2"
type HTTPDoer (line 33) | type HTTPDoer interface
type ClientConfig (line 38) | type ClientConfig struct
method String (line 104) | func (ClientConfig) String() string {
method GetAzureDeploymentByModel (line 108) | func (c ClientConfig) GetAzureDeploymentByModel(model string) string {
function DefaultConfig (line 52) | func DefaultConfig(authToken string) ClientConfig {
function DefaultAzureConfig (line 66) | func DefaultAzureConfig(apiKey, baseURL string) ClientConfig {
function DefaultAnthropicConfig (line 87) | func DefaultAnthropicConfig(apiKey, baseURL string) ClientConfig {
FILE: config_test.go
function TestGetAzureDeploymentByModel (line 9) | func TestGetAzureDeploymentByModel(t *testing.T) {
function TestDefaultAnthropicConfig (line 68) | func TestDefaultAnthropicConfig(t *testing.T) {
function TestDefaultAnthropicConfigWithEmptyValues (line 91) | func TestDefaultAnthropicConfigWithEmptyValues(t *testing.T) {
function TestClientConfigString (line 108) | func TestClientConfigString(t *testing.T) {
function TestGetAzureDeploymentByModel_NoMapper (line 118) | func TestGetAzureDeploymentByModel_NoMapper(t *testing.T) {
FILE: edits.go
type EditsRequest (line 10) | type EditsRequest struct
type EditsChoice (line 20) | type EditsChoice struct
type EditsResponse (line 26) | type EditsResponse struct
method Edits (line 40) | func (c *Client) Edits(ctx context.Context, request EditsRequest) (respo...
FILE: edits_test.go
function TestEdits (line 17) | func TestEdits(t *testing.T) {
function handleEditEndpoint (line 40) | func handleEditEndpoint(w http.ResponseWriter, r *http.Request) {
function getEditBody (line 80) | func getEditBody(r *http.Request) (openai.EditsRequest, error) {
FILE: embeddings.go
type EmbeddingModel (line 17) | type EmbeddingModel
constant AdaSimilarity (line 21) | AdaSimilarity EmbeddingModel = "text-similarity-ada-001"
constant BabbageSimilarity (line 22) | BabbageSimilarity EmbeddingModel = "text-similarity-babbage-001"
constant CurieSimilarity (line 23) | CurieSimilarity EmbeddingModel = "text-similarity-curie-001"
constant DavinciSimilarity (line 24) | DavinciSimilarity EmbeddingModel = "text-similarity-davinci-001"
constant AdaSearchDocument (line 25) | AdaSearchDocument EmbeddingModel = "text-search-ada-doc-001"
constant AdaSearchQuery (line 26) | AdaSearchQuery EmbeddingModel = "text-search-ada-query-001"
constant BabbageSearchDocument (line 27) | BabbageSearchDocument EmbeddingModel = "text-search-babbage-doc-001"
constant BabbageSearchQuery (line 28) | BabbageSearchQuery EmbeddingModel = "text-search-babbage-query-001"
constant CurieSearchDocument (line 29) | CurieSearchDocument EmbeddingModel = "text-search-curie-doc-001"
constant CurieSearchQuery (line 30) | CurieSearchQuery EmbeddingModel = "text-search-curie-query-001"
constant DavinciSearchDocument (line 31) | DavinciSearchDocument EmbeddingModel = "text-search-davinci-doc-001"
constant DavinciSearchQuery (line 32) | DavinciSearchQuery EmbeddingModel = "text-search-davinci-query-001"
constant AdaCodeSearchCode (line 33) | AdaCodeSearchCode EmbeddingModel = "code-search-ada-code-001"
constant AdaCodeSearchText (line 34) | AdaCodeSearchText EmbeddingModel = "code-search-ada-text-001"
constant BabbageCodeSearchCode (line 35) | BabbageCodeSearchCode EmbeddingModel = "code-search-babbage-code-001"
constant BabbageCodeSearchText (line 36) | BabbageCodeSearchText EmbeddingModel = "code-search-babbage-text-001"
constant AdaEmbeddingV2 (line 38) | AdaEmbeddingV2 EmbeddingModel = "text-embedding-ada-002"
constant SmallEmbedding3 (line 39) | SmallEmbedding3 EmbeddingModel = "text-embedding-3-small"
constant LargeEmbedding3 (line 40) | LargeEmbedding3 EmbeddingModel = "text-embedding-3-large"
type Embedding (line 49) | type Embedding struct
method DotProduct (line 59) | func (e *Embedding) DotProduct(other *Embedding) (float32, error) {
type EmbeddingResponse (line 73) | type EmbeddingResponse struct
type base64String (line 82) | type base64String
method Decode (line 84) | func (b base64String) Decode() ([]float32, error) {
type Base64Embedding (line 100) | type Base64Embedding struct
type EmbeddingResponseBase64 (line 107) | type EmbeddingResponseBase64 struct
method ToEmbeddingResponse (line 117) | func (r *EmbeddingResponseBase64) ToEmbeddingResponse() (EmbeddingResp...
type EmbeddingRequestConverter (line 141) | type EmbeddingRequestConverter interface
type EmbeddingEncodingFormat (line 149) | type EmbeddingEncodingFormat
constant EmbeddingEncodingFormatFloat (line 152) | EmbeddingEncodingFormatFloat EmbeddingEncodingFormat = "float"
constant EmbeddingEncodingFormatBase64 (line 153) | EmbeddingEncodingFormatBase64 EmbeddingEncodingFormat = "base64"
type EmbeddingRequest (line 156) | type EmbeddingRequest struct
method Convert (line 169) | func (r EmbeddingRequest) Convert() EmbeddingRequest {
type EmbeddingRequestStrings (line 174) | type EmbeddingRequestStrings struct
method Convert (line 199) | func (r EmbeddingRequestStrings) Convert() EmbeddingRequest {
type EmbeddingRequestTokens (line 210) | type EmbeddingRequestTokens struct
method Convert (line 235) | func (r EmbeddingRequestTokens) Convert() EmbeddingRequest {
method CreateEmbeddings (line 251) | func (c *Client) CreateEmbeddings(
FILE: embeddings_test.go
function TestEmbedding (line 18) | func TestEmbedding(t *testing.T) {
function TestEmbeddingEndpoint (line 102) | func TestEmbeddingEndpoint(t *testing.T) {
function TestAzureEmbeddingEndpoint (line 205) | func TestAzureEmbeddingEndpoint(t *testing.T) {
function TestEmbeddingResponseBase64_ToEmbeddingResponse (line 231) | func TestEmbeddingResponseBase64_ToEmbeddingResponse(t *testing.T) {
function TestDotProduct (line 293) | func TestDotProduct(t *testing.T) {
FILE: engines.go
type Engine (line 10) | type Engine struct
type EnginesList (line 20) | type EnginesList struct
method ListEngines (line 28) | func (c *Client) ListEngines(ctx context.Context) (engines EnginesList, ...
method GetEngine (line 40) | func (c *Client) GetEngine(
FILE: engines_test.go
function TestGetEngine (line 15) | func TestGetEngine(t *testing.T) {
function TestListEngines (line 27) | func TestListEngines(t *testing.T) {
function TestListEnginesReturnError (line 38) | func TestListEnginesReturnError(t *testing.T) {
FILE: error.go
type APIError (line 11) | type APIError struct
method Error (line 39) | func (e *APIError) Error() string {
method UnmarshalJSON (line 47) | func (e *APIError) UnmarshalJSON(data []byte) (err error) {
type InnerError (line 22) | type InnerError struct
type RequestError (line 28) | type RequestError struct
method Error (line 106) | func (e *RequestError) Error() string {
method Unwrap (line 113) | func (e *RequestError) Unwrap() error {
type ErrorResponse (line 35) | type ErrorResponse struct
FILE: error_test.go
function TestAPIErrorUnmarshalJSON (line 12) | func TestAPIErrorUnmarshalJSON(t *testing.T) {
function assertAPIErrorMessage (line 221) | func assertAPIErrorMessage(t *testing.T, apiErr openai.APIError, expecte...
function assertAPIErrorInnerError (line 227) | func assertAPIErrorInnerError(t *testing.T, apiErr openai.APIError, expe...
function assertAPIErrorCode (line 233) | func assertAPIErrorCode(t *testing.T, apiErr openai.APIError, expected i...
function assertAPIErrorParam (line 249) | func assertAPIErrorParam(t *testing.T, apiErr openai.APIError, expected ...
function assertAPIErrorType (line 255) | func assertAPIErrorType(t *testing.T, apiErr openai.APIError, typ string) {
function TestRequestError (line 261) | func TestRequestError(t *testing.T) {
FILE: example_test.go
function Example (line 17) | func Example() {
function ExampleClient_CreateChatCompletionStream (line 39) | func ExampleClient_CreateChatCompletionStream() {
function ExampleClient_CreateCompletion (line 80) | func ExampleClient_CreateCompletion() {
function ExampleClient_CreateCompletionStream (line 97) | func ExampleClient_CreateCompletionStream() {
function ExampleClient_CreateTranscription (line 131) | func ExampleClient_CreateTranscription() {
function ExampleClient_CreateTranscription_captions (line 147) | func ExampleClient_CreateTranscription_captions() {
function ExampleClient_CreateTranslation (line 174) | func ExampleClient_CreateTranslation() {
function ExampleClient_CreateImage (line 190) | func ExampleClient_CreateImage() {
function ExampleClient_CreateImage_base64 (line 209) | func ExampleClient_CreateImage_base64() {
function ExampleClientConfig_clientWithProxy (line 248) | func ExampleClientConfig_clientWithProxy() {
function Example_chatbot (line 272) | func Example_chatbot() {
function ExampleDefaultAzureConfig (line 304) | func ExampleDefaultAzureConfig() {
function ExampleAPIError (line 332) | func ExampleAPIError() {
FILE: examples/chatbot/main.go
function main (line 12) | func main() {
FILE: examples/completion-with-tool/main.go
function main (line 12) | func main() {
FILE: examples/completion/main.go
function main (line 11) | func main() {
FILE: examples/images/main.go
function main (line 11) | func main() {
FILE: examples/voice-to-text/main.go
function main (line 12) | func main() {
FILE: files.go
type FileRequest (line 11) | type FileRequest struct
type PurposeType (line 18) | type PurposeType
constant PurposeFineTune (line 21) | PurposeFineTune PurposeType = "fine-tune"
constant PurposeFineTuneResults (line 22) | PurposeFineTuneResults PurposeType = "fine-tune-results"
constant PurposeAssistants (line 23) | PurposeAssistants PurposeType = "assistants"
constant PurposeAssistantsOutput (line 24) | PurposeAssistantsOutput PurposeType = "assistants_output"
constant PurposeBatch (line 25) | PurposeBatch PurposeType = "batch"
type FileBytesRequest (line 29) | type FileBytesRequest struct
type File (line 39) | type File struct
type FilesList (line 53) | type FilesList struct
method CreateFileBytes (line 60) | func (c *Client) CreateFileBytes(ctx context.Context, request FileBytesR...
method CreateFile (line 92) | func (c *Client) CreateFile(ctx context.Context, request FileRequest) (f...
method DeleteFile (line 128) | func (c *Client) DeleteFile(ctx context.Context, fileID string) (err err...
method ListFiles (line 140) | func (c *Client) ListFiles(ctx context.Context) (files FilesList, err er...
method GetFile (line 152) | func (c *Client) GetFile(ctx context.Context, fileID string) (file File,...
method GetFileContent (line 163) | func (c *Client) GetFileContent(ctx context.Context, fileID string) (con...
FILE: files_api_test.go
function TestFileBytesUpload (line 19) | func TestFileBytesUpload(t *testing.T) {
function TestFileUpload (line 32) | func TestFileUpload(t *testing.T) {
function handleCreateFile (line 46) | func handleCreateFile(w http.ResponseWriter, r *http.Request) {
function TestDeleteFile (line 86) | func TestDeleteFile(t *testing.T) {
function TestListFile (line 94) | func TestListFile(t *testing.T) {
function TestGetFile (line 105) | func TestGetFile(t *testing.T) {
function TestGetFileContent (line 116) | func TestGetFileContent(t *testing.T) {
function TestGetFileContentReturnError (line 141) | func TestGetFileContentReturnError(t *testing.T) {
function TestGetFileContentReturnTimeoutError (line 179) | func TestGetFileContentReturnTimeoutError(t *testing.T) {
FILE: files_test.go
function TestFileBytesUploadWithFailingFormBuilder (line 14) | func TestFileBytesUploadWithFailingFormBuilder(t *testing.T) {
function TestFileUploadWithFailingFormBuilder (line 61) | func TestFileUploadWithFailingFormBuilder(t *testing.T) {
function TestFileUploadWithNonExistentPath (line 111) | func TestFileUploadWithNonExistentPath(t *testing.T) {
function TestCreateFileRequestBuilderFailure (line 124) | func TestCreateFileRequestBuilderFailure(t *testing.T) {
FILE: fine_tunes.go
type FineTuneRequest (line 12) | type FineTuneRequest struct
type FineTune (line 30) | type FineTune struct
type FineTuneEvent (line 51) | type FineTuneEvent struct
type FineTuneHyperParams (line 61) | type FineTuneHyperParams struct
type FineTuneList (line 71) | type FineTuneList struct
type FineTuneEventList (line 81) | type FineTuneEventList struct
type FineTuneDeleteResponse (line 91) | type FineTuneDeleteResponse struct
method CreateFineTune (line 102) | func (c *Client) CreateFineTune(ctx context.Context, request FineTuneReq...
method CancelFineTune (line 117) | func (c *Client) CancelFineTune(ctx context.Context, fineTuneID string) ...
method ListFineTunes (line 130) | func (c *Client) ListFineTunes(ctx context.Context) (response FineTuneLi...
method GetFineTune (line 143) | func (c *Client) GetFineTune(ctx context.Context, fineTuneID string) (re...
method DeleteFineTune (line 157) | func (c *Client) DeleteFineTune(ctx context.Context, fineTuneID string) ...
method ListFineTuneEvents (line 170) | func (c *Client) ListFineTuneEvents(ctx context.Context, fineTuneID stri...
FILE: fine_tunes_test.go
constant testFineTuneID (line 14) | testFineTuneID = "fine-tune-id"
function TestFineTunes (line 17) | func TestFineTunes(t *testing.T) {
FILE: fine_tuning_job.go
type FineTuningJob (line 10) | type FineTuningJob struct
type Hyperparameters (line 28) | type Hyperparameters struct
type FineTuningJobRequest (line 34) | type FineTuningJobRequest struct
type FineTuningJobEventList (line 42) | type FineTuningJobEventList struct
type FineTuningJobEvent (line 50) | type FineTuningJobEvent struct
method CreateFineTuningJob (line 61) | func (c *Client) CreateFineTuningJob(
method CancelFineTuningJob (line 76) | func (c *Client) CancelFineTuningJob(ctx context.Context, fineTuningJobI...
method RetrieveFineTuningJob (line 87) | func (c *Client) RetrieveFineTuningJob(
type listFineTuningJobEventsParameters (line 101) | type listFineTuningJobEventsParameters struct
type ListFineTuningJobEventsParameter (line 106) | type ListFineTuningJobEventsParameter
function ListFineTuningJobEventsWithAfter (line 108) | func ListFineTuningJobEventsWithAfter(after string) ListFineTuningJobEve...
function ListFineTuningJobEventsWithLimit (line 114) | func ListFineTuningJobEventsWithLimit(limit int) ListFineTuningJobEvents...
method ListFineTuningJobEvents (line 121) | func (c *Client) ListFineTuningJobEvents(
FILE: fine_tuning_job_test.go
constant testFineTuninigJobID (line 14) | testFineTuninigJobID = "fine-tuning-job-id"
function TestFineTuningJob (line 17) | func TestFineTuningJob(t *testing.T) {
FILE: image.go
constant CreateImageSize256x256 (line 13) | CreateImageSize256x256 = "256x256"
constant CreateImageSize512x512 (line 14) | CreateImageSize512x512 = "512x512"
constant CreateImageSize1024x1024 (line 15) | CreateImageSize1024x1024 = "1024x1024"
constant CreateImageSize1792x1024 (line 18) | CreateImageSize1792x1024 = "1792x1024"
constant CreateImageSize1024x1792 (line 19) | CreateImageSize1024x1792 = "1024x1792"
constant CreateImageSize1536x1024 (line 22) | CreateImageSize1536x1024 = "1536x1024"
constant CreateImageSize1024x1536 (line 23) | CreateImageSize1024x1536 = "1024x1536"
constant CreateImageResponseFormatB64JSON (line 28) | CreateImageResponseFormatB64JSON = "b64_json"
constant CreateImageResponseFormatURL (line 29) | CreateImageResponseFormatURL = "url"
constant CreateImageModelDallE2 (line 33) | CreateImageModelDallE2 = "dall-e-2"
constant CreateImageModelDallE3 (line 34) | CreateImageModelDallE3 = "dall-e-3"
constant CreateImageModelGptImage1 (line 35) | CreateImageModelGptImage1 = "gpt-image-1"
constant CreateImageQualityHD (line 39) | CreateImageQualityHD = "hd"
constant CreateImageQualityStandard (line 40) | CreateImageQualityStandard = "standard"
constant CreateImageQualityHigh (line 43) | CreateImageQualityHigh = "high"
constant CreateImageQualityMedium (line 44) | CreateImageQualityMedium = "medium"
constant CreateImageQualityLow (line 45) | CreateImageQualityLow = "low"
constant CreateImageStyleVivid (line 50) | CreateImageStyleVivid = "vivid"
constant CreateImageStyleNatural (line 51) | CreateImageStyleNatural = "natural"
constant CreateImageBackgroundTransparent (line 56) | CreateImageBackgroundTransparent = "transparent"
constant CreateImageBackgroundOpaque (line 57) | CreateImageBackgroundOpaque = "opaque"
constant CreateImageModerationLow (line 62) | CreateImageModerationLow = "low"
constant CreateImageOutputFormatPNG (line 67) | CreateImageOutputFormatPNG = "png"
constant CreateImageOutputFormatJPEG (line 68) | CreateImageOutputFormatJPEG = "jpeg"
constant CreateImageOutputFormatWEBP (line 69) | CreateImageOutputFormatWEBP = "webp"
type ImageRequest (line 73) | type ImageRequest struct
type ImageResponse (line 89) | type ImageResponse struct
type ImageResponseInputTokensDetails (line 98) | type ImageResponseInputTokensDetails struct
type ImageResponseUsage (line 104) | type ImageResponseUsage struct
type ImageResponseDataInner (line 112) | type ImageResponseDataInner struct
method CreateImage (line 119) | func (c *Client) CreateImage(ctx context.Context, request ImageRequest) ...
function WrapReader (line 136) | func WrapReader(rdr io.Reader, filename string, contentType string) io.R...
type file (line 140) | type file struct
method Name (line 146) | func (f file) Name() string {
method ContentType (line 155) | func (f file) ContentType() string {
type ImageEditRequest (line 161) | type ImageEditRequest struct
method CreateEditImage (line 174) | func (c *Client) CreateEditImage(ctx context.Context, request ImageEditR...
type ImageVariRequest (line 235) | type ImageVariRequest struct
method CreateVariImage (line 246) | func (c *Client) CreateVariImage(ctx context.Context, request ImageVariR...
FILE: image_api_test.go
function TestImages (line 18) | func TestImages(t *testing.T) {
function handleImageEndpoint (line 36) | func handleImageEndpoint(w http.ResponseWriter, r *http.Request) {
function getImageBody (line 71) | func getImageBody(r *http.Request) (openai.ImageRequest, error) {
function TestImageEdit (line 85) | func TestImageEdit(t *testing.T) {
function TestImageEditWithoutMask (line 113) | func TestImageEditWithoutMask(t *testing.T) {
function handleEditImageEndpoint (line 135) | func handleEditImageEndpoint(w http.ResponseWriter, r *http.Request) {
function TestImageVariation (line 165) | func TestImageVariation(t *testing.T) {
function handleVariateImageEndpoint (line 186) | func handleVariateImageEndpoint(w http.ResponseWriter, r *http.Request) {
FILE: image_test.go
type mockFormBuilder (line 15) | type mockFormBuilder struct
method CreateFormFile (line 22) | func (fb *mockFormBuilder) CreateFormFile(fieldname string, file *os.F...
method CreateFormFileReader (line 26) | func (fb *mockFormBuilder) CreateFormFileReader(fieldname string, r io...
method WriteField (line 30) | func (fb *mockFormBuilder) WriteField(fieldname, value string) error {
method Close (line 34) | func (fb *mockFormBuilder) Close() error {
method FormDataContentType (line 38) | func (fb *mockFormBuilder) FormDataContentType() string {
function TestImageFormBuilderFailures (line 42) | func TestImageFormBuilderFailures(t *testing.T) {
function TestVariImageFormBuilderFailures (line 173) | func TestVariImageFormBuilderFailures(t *testing.T) {
type testNamedReader (line 276) | type testNamedReader struct
method Name (line 278) | func (testNamedReader) Name() string { return "named.txt" }
function TestWrapReader (line 280) | func TestWrapReader(t *testing.T) {
FILE: internal/error_accumulator.go
type ErrorAccumulator (line 9) | type ErrorAccumulator interface
type errorBuffer (line 14) | type errorBuffer interface
type DefaultErrorAccumulator (line 20) | type DefaultErrorAccumulator struct
method Write (line 30) | func (e *DefaultErrorAccumulator) Write(p []byte) error {
method Bytes (line 38) | func (e *DefaultErrorAccumulator) Bytes() (errBytes []byte) {
function NewErrorAccumulator (line 24) | func NewErrorAccumulator() ErrorAccumulator {
FILE: internal/error_accumulator_test.go
function TestDefaultErrorAccumulator_WriteMultiple (line 11) | func TestDefaultErrorAccumulator_WriteMultiple(t *testing.T) {
function TestDefaultErrorAccumulator_EmptyBuffer (line 25) | func TestDefaultErrorAccumulator_EmptyBuffer(t *testing.T) {
function TestDefaultErrorAccumulator_WriteError (line 35) | func TestDefaultErrorAccumulator_WriteError(t *testing.T) {
FILE: internal/form_builder.go
type FormBuilder (line 13) | type FormBuilder interface
type DefaultFormBuilder (line 21) | type DefaultFormBuilder struct
method CreateFormFile (line 31) | func (fb *DefaultFormBuilder) CreateFormFile(fieldname string, file *o...
method CreateFormFileReader (line 43) | func (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r...
method createFormFile (line 81) | func (fb *DefaultFormBuilder) createFormFile(fieldname string, r io.Re...
method WriteField (line 99) | func (fb *DefaultFormBuilder) WriteField(fieldname, value string) error {
method Close (line 106) | func (fb *DefaultFormBuilder) Close() error {
method FormDataContentType (line 110) | func (fb *DefaultFormBuilder) FormDataContentType() string {
function NewFormBuilder (line 25) | func NewFormBuilder(body io.Writer) *DefaultFormBuilder {
function escapeQuotes (line 37) | func escapeQuotes(s string) string {
FILE: internal/form_builder_test.go
type mockFormBuilder (line 15) | type mockFormBuilder struct
method CreateFormFile (line 21) | func (m *mockFormBuilder) CreateFormFile(fieldname string, file *os.Fi...
method WriteField (line 25) | func (m *mockFormBuilder) WriteField(fieldname, value string) error {
method Close (line 29) | func (m *mockFormBuilder) Close() error {
method FormDataContentType (line 33) | func (m *mockFormBuilder) FormDataContentType() string {
function TestCloseMethod (line 37) | func TestCloseMethod(t *testing.T) {
type failingWriter (line 56) | type failingWriter struct
method Write (line 61) | func (*failingWriter) Write([]byte) (int, error) {
function TestFormBuilderWithFailingWriter (line 65) | func TestFormBuilderWithFailingWriter(t *testing.T) {
function TestFormBuilderWithClosedFile (line 77) | func TestFormBuilderWithClosedFile(t *testing.T) {
type failingReader (line 91) | type failingReader struct
method Read (line 96) | func (*failingReader) Read([]byte) (int, error) {
type readerWithNameAndContentType (line 100) | type readerWithNameAndContentType struct
method Name (line 104) | func (*readerWithNameAndContentType) Name() string {
method ContentType (line 108) | func (*readerWithNameAndContentType) ContentType() string {
function TestFormBuilderWithReader (line 112) | func TestFormBuilderWithReader(t *testing.T) {
function TestFormDataContentType (line 136) | func TestFormDataContentType(t *testing.T) {
function TestWriteField (line 148) | func TestWriteField(t *testing.T) {
function TestCreateFormFile (line 166) | func TestCreateFormFile(t *testing.T) {
function TestCreateFormFileSuccess (line 180) | func TestCreateFormFileSuccess(t *testing.T) {
FILE: internal/marshaller.go
type Marshaller (line 7) | type Marshaller interface
type JSONMarshaller (line 11) | type JSONMarshaller struct
method Marshal (line 13) | func (jm *JSONMarshaller) Marshal(value any) ([]byte, error) {
FILE: internal/marshaller_test.go
function TestJSONMarshaller_Normal (line 10) | func TestJSONMarshaller_Normal(t *testing.T) {
function TestJSONMarshaller_InvalidInput (line 21) | func TestJSONMarshaller_InvalidInput(t *testing.T) {
function TestJSONMarshaller_EmptyValue (line 27) | func TestJSONMarshaller_EmptyValue(t *testing.T) {
FILE: internal/request_builder.go
type RequestBuilder (line 10) | type RequestBuilder interface
type HTTPRequestBuilder (line 14) | type HTTPRequestBuilder struct
method Build (line 24) | func (b *HTTPRequestBuilder) Build(
function NewRequestBuilder (line 18) | func NewRequestBuilder() *HTTPRequestBuilder {
FILE: internal/request_builder_test.go
type failingMarshaller (line 15) | type failingMarshaller struct
method Marshal (line 17) | func (*failingMarshaller) Marshal(_ any) ([]byte, error) {
function TestRequestBuilderReturnsMarshallerErrors (line 21) | func TestRequestBuilderReturnsMarshallerErrors(t *testing.T) {
function TestRequestBuilderReturnsRequest (line 32) | func TestRequestBuilderReturnsRequest(t *testing.T) {
function TestRequestBuilderReturnsRequestWhenRequestOfArgsIsNil (line 50) | func TestRequestBuilderReturnsRequestWhenRequestOfArgsIsNil(t *testing.T) {
function TestRequestBuilderWithReaderBodyAndHeader (line 64) | func TestRequestBuilderWithReaderBodyAndHeader(t *testing.T) {
function TestRequestBuilderInvalidURL (line 90) | func TestRequestBuilderInvalidURL(t *testing.T) {
FILE: internal/test/checks/checks.go
function NoError (line 8) | func NoError(t *testing.T, err error, message ...string) {
function NoErrorF (line 15) | func NoErrorF(t *testing.T, err error, message ...string) {
function HasError (line 22) | func HasError(t *testing.T, err error, message ...string) {
function ErrorIs (line 29) | func ErrorIs(t *testing.T, err, target error, msg ...string) {
function ErrorIsF (line 36) | func ErrorIsF(t *testing.T, err, target error, format string, msg ...str...
function ErrorIsNot (line 43) | func ErrorIsNot(t *testing.T, err, target error, msg ...string) {
function ErrorIsNotf (line 50) | func ErrorIsNotf(t *testing.T, err, target error, format string, msg ......
FILE: internal/test/checks/checks_test.go
function TestChecksSuccessPaths (line 10) | func TestChecksSuccessPaths(t *testing.T) {
FILE: internal/test/failer.go
type FailingErrorBuffer (line 9) | type FailingErrorBuffer struct
method Write (line 11) | func (b *FailingErrorBuffer) Write(_ []byte) (n int, err error) {
method Len (line 15) | func (b *FailingErrorBuffer) Len() int {
method Bytes (line 19) | func (b *FailingErrorBuffer) Bytes() []byte {
FILE: internal/test/failer_test.go
function TestFailingErrorBuffer (line 9) | func TestFailingErrorBuffer(t *testing.T) {
FILE: internal/test/helpers.go
function CreateTestFile (line 12) | func CreateTestFile(t *testing.T, path string) {
type TokenRoundTripper (line 27) | type TokenRoundTripper struct
method RoundTrip (line 40) | func (t *TokenRoundTripper) RoundTrip(req *http.Request) (*http.Respon...
FILE: internal/test/helpers_test.go
function TestCreateTestFile (line 14) | func TestCreateTestFile(t *testing.T) {
function TestTokenRoundTripperAddsHeader (line 27) | func TestTokenRoundTripperAddsHeader(t *testing.T) {
FILE: internal/test/server.go
constant testAPI (line 11) | testAPI = "this-is-my-secure-token-do-not-steal!!"
function GetTestToken (line 13) | func GetTestToken() string {
type ServerTest (line 17) | type ServerTest struct
method HandlerCount (line 27) | func (ts *ServerTest) HandlerCount() int {
method HasHandler (line 32) | func (ts *ServerTest) HasHandler(path string) bool {
method RegisterHandler (line 38) | func (ts *ServerTest) RegisterHandler(path string, handler handler) {
method OpenAITestServer (line 46) | func (ts *ServerTest) OpenAITestServer() *httptest.Server {
type handler (line 20) | type handler
function NewTestServer (line 22) | func NewTestServer() *ServerTest {
FILE: internal/test/server_test.go
function TestGetTestToken (line 11) | func TestGetTestToken(t *testing.T) {
function TestNewTestServer (line 17) | func TestNewTestServer(t *testing.T) {
function TestRegisterHandlerTransformsPath (line 27) | func TestRegisterHandlerTransformsPath(t *testing.T) {
function TestOpenAITestServer (line 36) | func TestOpenAITestServer(t *testing.T) {
FILE: internal/unmarshaler.go
type Unmarshaler (line 7) | type Unmarshaler interface
type JSONUnmarshaler (line 11) | type JSONUnmarshaler struct
method Unmarshal (line 13) | func (jm *JSONUnmarshaler) Unmarshal(data []byte, v any) error {
FILE: internal/unmarshaler_test.go
function TestJSONUnmarshaler_Normal (line 10) | func TestJSONUnmarshaler_Normal(t *testing.T) {
function TestJSONUnmarshaler_InvalidJSON (line 22) | func TestJSONUnmarshaler_InvalidJSON(t *testing.T) {
function TestJSONUnmarshaler_EmptyInput (line 31) | func TestJSONUnmarshaler_EmptyInput(t *testing.T) {
FILE: jsonschema/containsref_test.go
type SelfRef (line 10) | type SelfRef struct
type Address (line 15) | type Address struct
type Person (line 19) | type Person struct
function TestGenerateSchemaForType_SelfRef (line 25) | func TestGenerateSchemaForType_SelfRef(t *testing.T) {
function TestGenerateSchemaForType_NoSelfRef (line 37) | func TestGenerateSchemaForType_NoSelfRef(t *testing.T) {
FILE: jsonschema/json.go
type DataType (line 15) | type DataType
constant Object (line 18) | Object DataType = "object"
constant Number (line 19) | Number DataType = "number"
constant Integer (line 20) | Integer DataType = "integer"
constant String (line 21) | String DataType = "string"
constant Array (line 22) | Array DataType = "array"
constant Null (line 23) | Null DataType = "null"
constant Boolean (line 24) | Boolean DataType = "boolean"
type Definition (line 29) | type Definition struct
method MarshalJSON (line 58) | func (d *Definition) MarshalJSON() ([]byte, error) {
method Unmarshal (line 70) | func (d *Definition) Unmarshal(content string, v any) error {
function GenerateSchemaForType (line 74) | func GenerateSchemaForType(v any) (*Definition, error) {
function reflectSchema (line 105) | func reflectSchema(t reflect.Type, defs map[string]Definition) (*Definit...
function reflectSchemaObject (line 158) | func reflectSchemaObject(t reflect.Type, defs map[string]Definition) (*D...
function containsRef (line 214) | func containsRef(def Definition, targetRef string) bool {
FILE: jsonschema/json_additional_test.go
function TestDefinitionUnmarshal (line 11) | func TestDefinitionUnmarshal(t *testing.T) {
function TestGenerateSchemaForTypeUnsupported (line 45) | func TestGenerateSchemaForTypeUnsupported(t *testing.T) {
function TestValidateInvalidContainers (line 55) | func TestValidateInvalidContainers(t *testing.T) {
function TestValidateRefNotFound (line 68) | func TestValidateRefNotFound(t *testing.T) {
FILE: jsonschema/json_errors_test.go
function TestGenerateSchemaForType_ErrorPaths (line 10) | func TestGenerateSchemaForType_ErrorPaths(t *testing.T) {
FILE: jsonschema/json_test.go
function TestDefinition_MarshalJSON (line 11) | func TestDefinition_MarshalJSON(t *testing.T) {
type User (line 185) | type User struct
type Order (line 191) | type Order struct
function TestStructToSchema (line 197) | func TestStructToSchema(t *testing.T) {
function structToMap (line 655) | func structToMap(t *testing.T, v any) map[string]any {
FILE: jsonschema/validate.go
function CollectDefs (line 8) | func CollectDefs(def Definition) map[string]Definition {
function collectDefsRecursive (line 14) | func collectDefsRecursive(def Definition, result map[string]Definition, ...
function VerifySchemaAndUnmarshal (line 28) | func VerifySchemaAndUnmarshal(schema Definition, content []byte, v any) ...
type validateArgs (line 40) | type validateArgs struct
type ValidateOption (line 44) | type ValidateOption
function WithDefs (line 46) | func WithDefs(defs map[string]Definition) ValidateOption {
function Validate (line 52) | func Validate(schema Definition, data any, opts ...ValidateOption) bool {
function validateObject (line 99) | func validateObject(schema Definition, data any, defs map[string]Definit...
function validateArray (line 120) | func validateArray(schema Definition, data any, defs map[string]Definiti...
function contains (line 133) | func contains[S ~[]E, E comparable](s S, v E) bool {
FILE: jsonschema/validate_test.go
function Test_Validate (line 10) | func Test_Validate(t *testing.T) {
function TestUnmarshal (line 174) | func TestUnmarshal(t *testing.T) {
function TestCollectDefs (line 255) | func TestCollectDefs(t *testing.T) {
FILE: messages.go
constant messagesSuffix (line 11) | messagesSuffix = "messages"
type Message (line 14) | type Message struct
type MessagesList (line 29) | type MessagesList struct
type MessageContent (line 40) | type MessageContent struct
type MessageText (line 46) | type MessageText struct
type ImageFile (line 51) | type ImageFile struct
type ImageURL (line 55) | type ImageURL struct
type MessageRequest (line 60) | type MessageRequest struct
type MessageFile (line 68) | type MessageFile struct
type MessageFilesList (line 77) | type MessageFilesList struct
type MessageDeletionStatus (line 83) | type MessageDeletionStatus struct
method CreateMessage (line 92) | func (c *Client) CreateMessage(ctx context.Context, threadID string, req...
method ListMessage (line 105) | func (c *Client) ListMessage(ctx context.Context, threadID string,
method RetrieveMessage (line 146) | func (c *Client) RetrieveMessage(
method ModifyMessage (line 162) | func (c *Client) ModifyMessage(
method RetrieveMessageFile (line 179) | func (c *Client) RetrieveMessageFile(
method ListMessageFiles (line 195) | func (c *Client) ListMessageFiles(
method DeleteMessage (line 211) | func (c *Client) DeleteMessage(
FILE: messages_test.go
function setupServerForTestMessage (line 17) | func setupServerForTestMessage(t *testing.T, server *test.ServerTest) {
function TestMessages (line 186) | func TestMessages(t *testing.T) {
FILE: models.go
type Model (line 10) | type Model struct
type Permission (line 23) | type Permission struct
type FineTuneModelDeleteResponse (line 39) | type FineTuneModelDeleteResponse struct
type ModelsList (line 48) | type ModelsList struct
method ListModels (line 56) | func (c *Client) ListModels(ctx context.Context) (models ModelsList, err...
method GetModel (line 68) | func (c *Client) GetModel(ctx context.Context, modelID string) (model Mo...
method DeleteFineTuneModel (line 81) | func (c *Client) DeleteFineTuneModel(ctx context.Context, modelID string) (
FILE: models_test.go
constant testFineTuneModelID (line 16) | testFineTuneModelID = "fine-tune-model-id"
function TestListModels (line 19) | func TestListModels(t *testing.T) {
function TestAzureListModels (line 27) | func TestAzureListModels(t *testing.T) {
function handleListModelsEndpoint (line 36) | func handleListModelsEndpoint(w http.ResponseWriter, _ *http.Request) {
function TestGetModel (line 42) | func TestGetModel(t *testing.T) {
function TestGetModelO3 (line 51) | func TestGetModelO3(t *testing.T) {
function TestGetModelO4Mini (line 60) | func TestGetModelO4Mini(t *testing.T) {
function TestAzureGetModel (line 68) | func TestAzureGetModel(t *testing.T) {
function handleGetModelEndpoint (line 77) | func handleGetModelEndpoint(w http.ResponseWriter, _ *http.Request) {
function TestGetModelReturnTimeoutError (line 82) | func TestGetModelReturnTimeoutError(t *testing.T) {
function TestDeleteFineTuneModel (line 101) | func TestDeleteFineTuneModel(t *testing.T) {
function handleDeleteFineTuneModelEndpoint (line 109) | func handleDeleteFineTuneModelEndpoint(w http.ResponseWriter, _ *http.Re...
FILE: moderation.go
constant ModerationOmniLatest (line 17) | ModerationOmniLatest = "omni-moderation-latest"
constant ModerationOmni20240926 (line 18) | ModerationOmni20240926 = "omni-moderation-2024-09-26"
constant ModerationTextStable (line 19) | ModerationTextStable = "text-moderation-stable"
constant ModerationTextLatest (line 20) | ModerationTextLatest = "text-moderation-latest"
constant ModerationText001 (line 22) | ModerationText001 = "text-moderation-001"
type ModerationRequest (line 37) | type ModerationRequest struct
type Result (line 43) | type Result struct
type ResultCategories (line 50) | type ResultCategories struct
type ResultCategoryScores (line 65) | type ResultCategoryScores struct
type ModerationResponse (line 80) | type ModerationResponse struct
method Moderations (line 90) | func (c *Client) Moderations(ctx context.Context, request ModerationRequ...
FILE: moderation_test.go
function TestModerations (line 19) | func TestModerations(t *testing.T) {
function TestModerationsWithDifferentModelOptions (line 31) | func TestModerationsWithDifferentModelOptions(t *testing.T) {
function getModerationModelTestOption (line 57) | func getModerationModelTestOption(model string, expect error) struct {
function handleModerationEndpoint (line 68) | func handleModerationEndpoint(w http.ResponseWriter, r *http.Request) {
function getModerationBody (line 143) | func getModerationBody(r *http.Request) (openai.ModerationRequest, error) {
FILE: openai_test.go
function setupOpenAITestServer (line 8) | func setupOpenAITestServer() (client *openai.Client, server *test.Server...
function setupAzureTestServer (line 19) | func setupAzureTestServer() (client *openai.Client, server *test.ServerT...
function numTokens (line 35) | func numTokens(s string) int {
FILE: ratelimit.go
type RateLimitHeaders (line 10) | type RateLimitHeaders struct
type ResetTime (line 19) | type ResetTime
method String (line 21) | func (r ResetTime) String() string {
method Time (line 25) | func (r ResetTime) Time() time.Time {
function newRateLimitHeaders (line 30) | func newRateLimitHeaders(h http.Header) RateLimitHeaders {
FILE: reasoning_validator.go
type ReasoningValidator (line 32) | type ReasoningValidator struct
method Validate (line 40) | func (v *ReasoningValidator) Validate(request ChatCompletionRequest) e...
method validateReasoningModelParams (line 58) | func (v *ReasoningValidator) validateReasoningModelParams(request Chat...
function NewReasoningValidator (line 35) | func NewReasoningValidator() *ReasoningValidator {
FILE: run.go
type Run (line 10) | type Run struct
type RunStatus (line 44) | type RunStatus
constant RunStatusQueued (line 47) | RunStatusQueued RunStatus = "queued"
constant RunStatusInProgress (line 48) | RunStatusInProgress RunStatus = "in_progress"
constant RunStatusRequiresAction (line 49) | RunStatusRequiresAction RunStatus = "requires_action"
constant RunStatusCancelling (line 50) | RunStatusCancelling RunStatus = "cancelling"
constant RunStatusFailed (line 51) | RunStatusFailed RunStatus = "failed"
constant RunStatusCompleted (line 52) | RunStatusCompleted RunStatus = "completed"
constant RunStatusIncomplete (line 53) | RunStatusIncomplete RunStatus = "incomplete"
constant RunStatusExpired (line 54) | RunStatusExpired RunStatus = "expired"
constant RunStatusCancelled (line 55) | RunStatusCancelled RunStatus = "cancelled"
type RunRequiredAction (line 58) | type RunRequiredAction struct
type RequiredActionType (line 63) | type RequiredActionType
constant RequiredActionTypeSubmitToolOutputs (line 66) | RequiredActionTypeSubmitToolOutputs RequiredActionType = "submit_tool_ou...
type SubmitToolOutputs (line 69) | type SubmitToolOutputs struct
type RunLastError (line 73) | type RunLastError struct
type RunError (line 78) | type RunError
constant RunErrorServerError (line 81) | RunErrorServerError RunError = "server_error"
constant RunErrorRateLimitExceeded (line 82) | RunErrorRateLimitExceeded RunError = "rate_limit_exceeded"
type RunRequest (line 85) | type RunRequest struct
type ThreadTruncationStrategy (line 120) | type ThreadTruncationStrategy struct
type TruncationStrategy (line 128) | type TruncationStrategy
constant TruncationStrategyAuto (line 132) | TruncationStrategyAuto = TruncationStrategy("auto")
constant TruncationStrategyLastMessages (line 134) | TruncationStrategyLastMessages = TruncationStrategy("last_messages")
type ReponseFormat (line 140) | type ReponseFormat struct
type RunModifyRequest (line 144) | type RunModifyRequest struct
type RunList (line 149) | type RunList struct
type SubmitToolOutputsRequest (line 155) | type SubmitToolOutputsRequest struct
type ToolOutput (line 159) | type ToolOutput struct
type CreateThreadAndRunRequest (line 164) | type CreateThreadAndRunRequest struct
type RunStep (line 169) | type RunStep struct
type RunStepStatus (line 189) | type RunStepStatus
constant RunStepStatusInProgress (line 192) | RunStepStatusInProgress RunStepStatus = "in_progress"
constant RunStepStatusCancelling (line 193) | RunStepStatusCancelling RunStepStatus = "cancelled"
constant RunStepStatusFailed (line 194) | RunStepStatusFailed RunStepStatus = "failed"
constant RunStepStatusCompleted (line 195) | RunStepStatusCompleted RunStepStatus = "completed"
constant RunStepStatusExpired (line 196) | RunStepStatusExpired RunStepStatus = "expired"
type RunStepType (line 199) | type RunStepType
constant RunStepTypeMessageCreation (line 202) | RunStepTypeMessageCreation RunStepType = "message_creation"
constant RunStepTypeToolCalls (line 203) | RunStepTypeToolCalls RunStepType = "tool_calls"
type StepDetails (line 206) | type StepDetails struct
type StepDetailsMessageCreation (line 212) | type StepDetailsMessageCreation struct
type RunStepList (line 217) | type RunStepList struct
type Pagination (line 227) | type Pagination struct
method CreateRun (line 235) | func (c *Client) CreateRun(
method RetrieveRun (line 256) | func (c *Client) RetrieveRun(
method ModifyRun (line 276) | func (c *Client) ModifyRun(
method ListRuns (line 298) | func (c *Client) ListRuns(
method SubmitToolOutputs (line 337) | func (c *Client) SubmitToolOutputs(
method CancelRun (line 358) | func (c *Client) CancelRun(
method CreateThreadAndRun (line 377) | func (c *Client) CreateThreadAndRun(
method RetrieveRunStep (line 396) | func (c *Client) RetrieveRunStep(
method ListRunSteps (line 417) | func (c *Client) ListRunSteps(
FILE: run_test.go
function TestRun (line 16) | func TestRun(t *testing.T) {
FILE: speech.go
type SpeechModel (line 8) | type SpeechModel
constant TTSModel1 (line 11) | TTSModel1 SpeechModel = "tts-1"
constant TTSModel1HD (line 12) | TTSModel1HD SpeechModel = "tts-1-hd"
constant TTSModelCanary (line 13) | TTSModelCanary SpeechModel = "canary-tts"
constant TTSModelGPT4oMini (line 14) | TTSModelGPT4oMini SpeechModel = "gpt-4o-mini-tts"
type SpeechVoice (line 17) | type SpeechVoice
constant VoiceAlloy (line 20) | VoiceAlloy SpeechVoice = "alloy"
constant VoiceAsh (line 21) | VoiceAsh SpeechVoice = "ash"
constant VoiceBallad (line 22) | VoiceBallad SpeechVoice = "ballad"
constant VoiceCoral (line 23) | VoiceCoral SpeechVoice = "coral"
constant VoiceEcho (line 24) | VoiceEcho SpeechVoice = "echo"
constant VoiceFable (line 25) | VoiceFable SpeechVoice = "fable"
constant VoiceOnyx (line 26) | VoiceOnyx SpeechVoice = "onyx"
constant VoiceNova (line 27) | VoiceNova SpeechVoice = "nova"
constant VoiceShimmer (line 28) | VoiceShimmer SpeechVoice = "shimmer"
constant VoiceVerse (line 29) | VoiceVerse SpeechVoice = "verse"
type SpeechResponseFormat (line 32) | type SpeechResponseFormat
constant SpeechResponseFormatMp3 (line 35) | SpeechResponseFormatMp3 SpeechResponseFormat = "mp3"
constant SpeechResponseFormatOpus (line 36) | SpeechResponseFormatOpus SpeechResponseFormat = "opus"
constant SpeechResponseFormatAac (line 37) | SpeechResponseFormatAac SpeechResponseFormat = "aac"
constant SpeechResponseFormatFlac (line 38) | SpeechResponseFormatFlac SpeechResponseFormat = "flac"
constant SpeechResponseFormatWav (line 39) | SpeechResponseFormatWav SpeechResponseFormat = "wav"
constant SpeechResponseFormatPcm (line 40) | SpeechResponseFormatPcm SpeechResponseFormat = "pcm"
type CreateSpeechRequest (line 43) | type CreateSpeechRequest struct
method CreateSpeech (line 52) | func (c *Client) CreateSpeech(ctx context.Context, request CreateSpeechR...
FILE: speech_test.go
function TestSpeechIntegration (line 19) | func TestSpeechIntegration(t *testing.T) {
FILE: stream.go
type CompletionStream (line 13) | type CompletionStream struct
method CreateCompletionStream (line 21) | func (c *Client) CreateCompletionStream(
FILE: stream_reader.go
type streamable (line 19) | type streamable interface
type streamReader (line 23) | type streamReader struct
method Recv (line 35) | func (stream *streamReader[T]) Recv() (response T, err error) {
method RecvRaw (line 48) | func (stream *streamReader[T]) RecvRaw() ([]byte, error) {
method processLines (line 57) | func (stream *streamReader[T]) processLines() ([]byte, error) {
method unmarshalError (line 103) | func (stream *streamReader[T]) unmarshalError() (errResp *ErrorResponse) {
method Close (line 117) | func (stream *streamReader[T]) Close() error {
FILE: stream_reader_test.go
type failingUnMarshaller (line 16) | type failingUnMarshaller struct
method Unmarshal (line 18) | func (*failingUnMarshaller) Unmarshal(_ []byte, _ any) error {
function TestStreamReaderReturnsUnmarshalerErrors (line 22) | func TestStreamReaderReturnsUnmarshalerErrors(t *testing.T) {
function TestStreamReaderReturnsErrTooManyEmptyStreamMessages (line 44) | func TestStreamReaderReturnsErrTooManyEmptyStreamMessages(t *testing.T) {
function TestStreamReaderReturnsErrTestErrorAccumulatorWriteFailed (line 55) | func TestStreamReaderReturnsErrTestErrorAccumulatorWriteFailed(t *testin...
function TestStreamReaderRecvRaw (line 67) | func TestStreamReaderRecvRaw(t *testing.T) {
FILE: stream_test.go
function TestCompletionsStreamWrongModel (line 17) | func TestCompletionsStreamWrongModel(t *testing.T) {
function TestCreateCompletionStream (line 34) | func TestCreateCompletionStream(t *testing.T) {
function TestCreateCompletionStreamError (line 106) | func TestCreateCompletionStreamError(t *testing.T) {
function TestCreateCompletionStreamRateLimitError (line 151) | func TestCreateCompletionStreamRateLimitError(t *testing.T) {
function TestCreateCompletionStreamTooManyEmptyStreamMessagesError (line 182) | func TestCreateCompletionStreamTooManyEmptyStreamMessagesError(t *testin...
function TestCreateCompletionStreamUnexpectedTerminatedError (line 228) | func TestCreateCompletionStreamUnexpectedTerminatedError(t *testing.T) {
function TestCreateCompletionStreamBrokenJSONError (line 263) | func TestCreateCompletionStreamBrokenJSONError(t *testing.T) {
function TestCreateCompletionStreamReturnTimeoutError (line 305) | func TestCreateCompletionStreamReturnTimeoutError(t *testing.T) {
function compareResponses (line 330) | func compareResponses(r1, r2 openai.CompletionResponse) bool {
function compareResponseChoices (line 345) | func compareResponseChoices(c1, c2 openai.CompletionChoice) bool {
FILE: thread.go
constant threadsSuffix (line 9) | threadsSuffix = "/threads"
type Thread (line 12) | type Thread struct
type ThreadRequest (line 22) | type ThreadRequest struct
type ToolResources (line 28) | type ToolResources struct
type CodeInterpreterToolResources (line 33) | type CodeInterpreterToolResources struct
type FileSearchToolResources (line 37) | type FileSearchToolResources struct
type ToolResourcesRequest (line 41) | type ToolResourcesRequest struct
type CodeInterpreterToolResourcesRequest (line 46) | type CodeInterpreterToolResourcesRequest struct
type FileSearchToolResourcesRequest (line 50) | type FileSearchToolResourcesRequest struct
type VectorStoreToolResources (line 55) | type VectorStoreToolResources struct
type ChunkingStrategy (line 61) | type ChunkingStrategy struct
type StaticChunkingStrategy (line 66) | type StaticChunkingStrategy struct
type ChunkingStrategyType (line 71) | type ChunkingStrategyType
constant ChunkingStrategyTypeAuto (line 74) | ChunkingStrategyTypeAuto ChunkingStrategyType = "auto"
constant ChunkingStrategyTypeStatic (line 75) | ChunkingStrategyTypeStatic ChunkingStrategyType = "static"
type ModifyThreadRequest (line 78) | type ModifyThreadRequest struct
type ThreadMessageRole (line 83) | type ThreadMessageRole
constant ThreadMessageRoleAssistant (line 86) | ThreadMessageRoleAssistant ThreadMessageRole = "assistant"
constant ThreadMessageRoleUser (line 87) | ThreadMessageRoleUser ThreadMessageRole = "user"
type ThreadMessage (line 90) | type ThreadMessage struct
type ThreadAttachment (line 98) | type ThreadAttachment struct
type ThreadAttachmentTool (line 103) | type ThreadAttachmentTool struct
type ThreadDeleteResponse (line 107) | type ThreadDeleteResponse struct
method CreateThread (line 116) | func (c *Client) CreateThread(ctx context.Context, request ThreadRequest...
method RetrieveThread (line 128) | func (c *Client) RetrieveThread(ctx context.Context, threadID string) (r...
method ModifyThread (line 141) | func (c *Client) ModifyThread(
method DeleteThread (line 158) | func (c *Client) DeleteThread(
FILE: thread_test.go
function TestThread (line 15) | func TestThread(t *testing.T) {
function TestAzureThread (line 98) | func TestAzureThread(t *testing.T) {
FILE: vector_store.go
constant vectorStoresSuffix (line 11) | vectorStoresSuffix = "/vector_stores"
constant vectorStoresFilesSuffix (line 12) | vectorStoresFilesSuffix = "/files"
constant vectorStoresFileBatchesSuffix (line 13) | vectorStoresFileBatchesSuffix = "/file_batches"
type VectorStoreFileCount (line 16) | type VectorStoreFileCount struct
type VectorStore (line 24) | type VectorStore struct
type VectorStoreExpires (line 39) | type VectorStoreExpires struct
type VectorStoreRequest (line 45) | type VectorStoreRequest struct
type VectorStoresList (line 53) | type VectorStoresList struct
type VectorStoreDeleteResponse (line 61) | type VectorStoreDeleteResponse struct
type VectorStoreFile (line 69) | type VectorStoreFile struct
type VectorStoreFileRequest (line 80) | type VectorStoreFileRequest struct
type VectorStoreFilesList (line 84) | type VectorStoreFilesList struct
type VectorStoreFileBatch (line 93) | type VectorStoreFileBatch struct
type VectorStoreFileBatchRequest (line 104) | type VectorStoreFileBatchRequest struct
method CreateVectorStore (line 109) | func (c *Client) CreateVectorStore(ctx context.Context, request VectorSt...
method RetrieveVectorStore (line 123) | func (c *Client) RetrieveVectorStore(
method ModifyVectorStore (line 136) | func (c *Client) ModifyVectorStore(
method DeleteVectorStore (line 150) | func (c *Client) DeleteVectorStore(
method ListVectorStores (line 163) | func (c *Client) ListVectorStores(
method CreateVectorStoreFile (line 196) | func (c *Client) CreateVectorStoreFile(
method RetrieveVectorStoreFile (line 211) | func (c *Client) RetrieveVectorStoreFile(
method DeleteVectorStoreFile (line 225) | func (c *Client) DeleteVectorStoreFile(
method ListVectorStoreFiles (line 239) | func (c *Client) ListVectorStoreFiles(
method CreateVectorStoreFileBatch (line 272) | func (c *Client) CreateVectorStoreFileBatch(
method RetrieveVectorStoreFileBatch (line 287) | func (c *Client) RetrieveVectorStoreFileBatch(
method CancelVectorStoreFileBatch (line 301) | func (c *Client) CancelVectorStoreFileBatch(
method ListVectorStoreFilesInBatch (line 316) | func (c *Client) ListVectorStoreFilesInBatch(
FILE: vector_store_test.go
function TestVectorStore (line 16) | func TestVectorStore(t *testing.T) {
Condensed preview — 105 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (606K chars).
[
{
"path": ".codecov.yml",
"chars": 65,
"preview": "coverage:\n ignore:\n - \"examples/**\"\n - \"internal/test/**\"\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 81,
"preview": "# These are supported funding model platforms\n\ngithub: [sashabaranov, vvatanabe]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 979,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\nYour issue may "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 749,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\nYour"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1295,
"preview": "A similar PR may already be submitted!\nPlease search among the [Pull request](https://github.com/sashabaranov/go-openai/"
},
{
"path": ".github/workflows/close-inactive-issues.yml",
"chars": 753,
"preview": "name: Close inactive issues\non:\n schedule:\n - cron: \"30 1 * * *\"\n\njobs:\n close-issues:\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/integration-tests.yml",
"chars": 449,
"preview": "name: Integration tests\n\non:\n push:\n branches:\n - master\n\njobs:\n integration_tests:\n name: Run integration "
},
{
"path": ".github/workflows/pr.yml",
"chars": 672,
"preview": "name: Sanity check\n\non:\n - push\n - pull_request\n\njobs:\n prcheck:\n name: Sanity check\n runs-on: ubuntu-latest\n "
},
{
"path": ".gitignore",
"chars": 343,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
},
{
"path": ".golangci.yml",
"chars": 3603,
"preview": "version: \"2\"\nlinters:\n default: none\n enable:\n - asciicheck\n - bidichk\n - bodyclose\n - contextcheck\n - "
},
{
"path": "CONTRIBUTING.md",
"chars": 4266,
"preview": "# Contributing Guidelines\n\n## Overview\nThank you for your interest in contributing to the \"Go OpenAI\" project! By follow"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 23974,
"preview": "# Go OpenAI\n[](https://pkg.go.dev/github."
},
{
"path": "api_integration_test.go",
"chars": 9243,
"preview": "//go:build integration\n\npackage openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"gi"
},
{
"path": "api_internal_test.go",
"chars": 4467,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestOpenAIFullURL(t *testing.T) {\n\tcases := []struct {\n\t\tName s"
},
{
"path": "assistant.go",
"chars": 9444,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nconst (\n\tassistantsSuffix = \""
},
{
"path": "assistant_test.go",
"chars": 13642,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-opena"
},
{
"path": "audio.go",
"chars": 6451,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\tutils \"github.com/sashabaranov/go-openai/i"
},
{
"path": "audio_api_test.go",
"chars": 4097,
"preview": "package openai_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"mime\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"path/filepath\""
},
{
"path": "audio_test.go",
"chars": 7215,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/"
},
{
"path": "batch.go",
"chars": 7470,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nconst batchesSuffix = \"/b"
},
{
"path": "batch_test.go",
"chars": 10258,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n"
},
{
"path": "chat.go",
"chars": 19857,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/sashabaranov/go-openai/jsonsche"
},
{
"path": "chat_stream.go",
"chars": 4150,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\ntype ChatCompletionStreamChoiceDelta struct {\n\tContent string "
},
{
"path": "chat_stream_test.go",
"chars": 33714,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gi"
},
{
"path": "chat_test.go",
"chars": 33550,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"tes"
},
{
"path": "client.go",
"chars": 8453,
"preview": "package openai\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tutils \"gi"
},
{
"path": "client_test.go",
"chars": 16709,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/"
},
{
"path": "common.go",
"chars": 1089,
"preview": "package openai\n\n// common.go defines common types used throughout the OpenAI API.\n\n// Usage Represents the total token u"
},
{
"path": "completion.go",
"chars": 10865,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\n// GPT3 Defines the models provided by OpenAI to use when generating\n"
},
{
"path": "completion_test.go",
"chars": 9951,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"tes"
},
{
"path": "config.go",
"chars": 2722,
"preview": "package openai\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nconst (\n\topenaiAPIURLv1 = \"https://api.open"
},
{
"path": "config_test.go",
"chars": 3290,
"preview": "package openai_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc TestGetAzureDeploymentByModel(t *"
},
{
"path": "edits.go",
"chars": 1457,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// EditsRequest represents a request structure for Edits API.\n"
},
{
"path": "edits_test.go",
"chars": 2733,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sash"
},
{
"path": "embeddings.go",
"chars": 11124,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"math\"\n\t\"net/http\""
},
{
"path": "embeddings_test.go",
"chars": 9927,
"preview": "package openai_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"tes"
},
{
"path": "engines.go",
"chars": 1172,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Engine struct represents engine from OpenAPI API.\ntype Engi"
},
{
"path": "engines_test.go",
"chars": 1512,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-op"
},
{
"path": "error.go",
"chars": 2852,
"preview": "package openai\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// APIError provides error information returned by the Op"
},
{
"path": "error_test.go",
"chars": 8418,
"preview": "package openai_test\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc "
},
{
"path": "example_test.go",
"chars": 8089,
"preview": "package openai_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\""
},
{
"path": "examples/README.md",
"chars": 94,
"preview": "To run an example:\n\n```\nexport OPENAI_API_KEY=\"<your key here>\"\ngo run ./example/<target>\n```\n"
},
{
"path": "examples/chatbot/main.go",
"chars": 949,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient :"
},
{
"path": "examples/completion/main.go",
"chars": 451,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient := openai."
},
{
"path": "examples/completion-with-tool/main.go",
"chars": 2768,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai"
},
{
"path": "examples/images/main.go",
"chars": 598,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient := openai."
},
{
"path": "examples/voice-to-text/main.go",
"chars": 654,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tif len("
},
{
"path": "files.go",
"chars": 4132,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n)\n\ntype FileRequest struct {\n\tFileName string `jso"
},
{
"path": "files_api_test.go",
"chars": 5501,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\""
},
{
"path": "files_test.go",
"chars": 4260,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\tutils "
},
{
"path": "fine_tunes.go",
"chars": 7738,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the depr"
},
{
"path": "fine_tunes_test.go",
"chars": 2161,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-op"
},
{
"path": "fine_tuning_job.go",
"chars": 4354,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\ntype FineTuningJob struct {\n\tID string"
},
{
"path": "fine_tuning_job_test.go",
"chars": 3110,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-op"
},
{
"path": "go.mod",
"chars": 50,
"preview": "module github.com/sashabaranov/go-openai\n\ngo 1.18\n"
},
{
"path": "image.go",
"chars": 7848,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\n// Image sizes defined by the OpenAI API.\nc"
},
{
"path": "image_api_test.go",
"chars": 5802,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"t"
},
{
"path": "image_test.go",
"chars": 9804,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\tutils \"github.com/sashabaranov/go-openai/interna"
},
{
"path": "internal/error_accumulator.go",
"chars": 693,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n)\n\ntype ErrorAccumulator interface {\n\tWrite(p []byte) error\n\tBytes() []by"
},
{
"path": "internal/error_accumulator_test.go",
"chars": 1271,
"preview": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov"
},
{
"path": "internal/form_builder.go",
"chars": 2490,
"preview": "package openai\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype FormB"
},
{
"path": "internal/form_builder_test.go",
"chars": 5208,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/sashabaranov/go-ope"
},
{
"path": "internal/marshaller.go",
"chars": 235,
"preview": "package openai\n\nimport (\n\t\"encoding/json\"\n)\n\ntype Marshaller interface {\n\tMarshal(value any) ([]byte, error)\n}\n\ntype JSO"
},
{
"path": "internal/marshaller_test.go",
"chars": 814,
"preview": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov"
},
{
"path": "internal/request_builder.go",
"chars": 946,
"preview": "package openai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype RequestBuilder interface {\n\tBuild(ctx context.Con"
},
{
"path": "internal/request_builder_test.go",
"chars": 2621,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t"
},
{
"path": "internal/test/checks/checks.go",
"chars": 962,
"preview": "package checks\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc NoError(t *testing.T, err error, message ...string) {\n\tt.Helper()\n"
},
{
"path": "internal/test/checks/checks_test.go",
"chars": 454,
"preview": "package checks_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc Te"
},
{
"path": "internal/test/failer.go",
"chars": 390,
"preview": "package test\n\nimport \"errors\"\n\nvar (\n\tErrTestErrorAccumulatorWriteFailed = errors.New(\"test error accumulator failed\")\n)"
},
{
"path": "internal/test/failer_test.go",
"chars": 567,
"preview": "//nolint:testpackage // need access to unexported fields and types for testing\npackage test\n\nimport (\n\t\"errors\"\n\t\"testin"
},
{
"path": "internal/test/helpers.go",
"chars": 1408,
"preview": "package test\n\nimport (\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n)\n\n// Cre"
},
{
"path": "internal/test/helpers_test.go",
"chars": 1389,
"preview": "package test_test\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tinternaltest \"git"
},
{
"path": "internal/test/server.go",
"chars": 1971,
"preview": "package test\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nconst testAPI = \"this-is-my-secur"
},
{
"path": "internal/test/server_test.go",
"chars": 2123,
"preview": "package test_test\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\tinternaltest \"github.com/sashabaranov/go-openai/internal/test"
},
{
"path": "internal/unmarshaler.go",
"chars": 244,
"preview": "package openai\n\nimport (\n\t\"encoding/json\"\n)\n\ntype Unmarshaler interface {\n\tUnmarshal(data []byte, v any) error\n}\n\ntype J"
},
{
"path": "internal/unmarshaler_test.go",
"chars": 868,
"preview": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov"
},
{
"path": "jsonschema/containsref_test.go",
"chars": 1298,
"preview": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// SelfRef struct used "
},
{
"path": "jsonschema/json.go",
"chars": 6687,
"preview": "// Package jsonschema provides very simple functionality for representing a JSON schema as a\n// (nested) struct. This st"
},
{
"path": "jsonschema/json_additional_test.go",
"chars": 2257,
"preview": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// Test Definition.Unma"
},
{
"path": "jsonschema/json_errors_test.go",
"chars": 612,
"preview": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// TestGenerateSchemaFo"
},
{
"path": "jsonschema/json_test.go",
"chars": 12997,
"preview": "package jsonschema_test\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema"
},
{
"path": "jsonschema/validate.go",
"chars": 3147,
"preview": "package jsonschema\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\nfunc CollectDefs(def Definition) map[string]Definition {\n\tres"
},
{
"path": "jsonschema/validate_test.go",
"chars": 11126,
"preview": "package jsonschema_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nfunc Test_Va"
},
{
"path": "messages.go",
"chars": 5876,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nconst (\n\tmessagesSuffix = \"messages\"\n)\n\ntype Messag"
},
{
"path": "messages_test.go",
"chars": 7547,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-op"
},
{
"path": "models.go",
"chars": 2728,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Model struct represents an OpenAPI model.\ntype Model struct"
},
{
"path": "models_test.go",
"chars": 3885,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sash"
},
{
"path": "moderation.go",
"chars": 3913,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n)\n\n// The moderation endpoint is a tool you can use to check w"
},
{
"path": "moderation_test.go",
"chars": 5275,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"ti"
},
{
"path": "openai_test.go",
"chars": 1161,
"preview": "package openai_test\n\nimport (\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n)\n"
},
{
"path": "ratelimit.go",
"chars": 1384,
"preview": "package openai\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// RateLimitHeaders struct represents Openai rate limits head"
},
{
"path": "reasoning_validator.go",
"chars": 3713,
"preview": "package openai\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\nvar (\n\t// Deprecated: use ErrReasoningModelMaxTokensDeprecated instead."
},
{
"path": "run.go",
"chars": 13349,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\ntype Run struct {\n\tID string "
},
{
"path": "run_test.go",
"chars": 5841,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-opena"
},
{
"path": "speech.go",
"chars": 1912,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\ntype SpeechModel string\n\nconst (\n\tTTSModel1 SpeechModel = \"tt"
},
{
"path": "speech_test.go",
"chars": 2507,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"test"
},
{
"path": "stream.go",
"chars": 1224,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n)\n\nvar (\n\tErrTooManyEmptyStreamMessages = errors.New(\"stream h"
},
{
"path": "stream_reader.go",
"chars": 2384,
"preview": "package openai\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\n\tutils \"github.com/sashabaranov/go-openai"
},
{
"path": "stream_reader_test.go",
"chars": 2402,
"preview": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\tutils \"g"
},
{
"path": "stream_test.go",
"chars": 11252,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github"
},
{
"path": "thread.go",
"chars": 4747,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\nconst (\n\tthreadsSuffix = \"/threads\"\n)\n\ntype Thread struct {\n\tID "
},
{
"path": "thread_test.go",
"chars": 4488,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\topenai \"github.com/sashabarano"
},
{
"path": "vector_store.go",
"chars": 10509,
"preview": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nconst (\n\tvectorStoresSuffix = \"/vector_s"
},
{
"path": "vector_store_test.go",
"chars": 10237,
"preview": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-opena"
}
]
About this extraction
This page contains the full source code of the sashabaranov/go-openai GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 105 files (529.2 KB), approximately 144.9k tokens, and a symbol index with 970 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.