[
  {
    "path": ".codecov.yml",
    "content": "coverage:\n  ignore:\n    - \"examples/**\"\n    - \"internal/test/**\"\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [sashabaranov, vvatanabe]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\nYour issue may already be reported!\nPlease search on the [issue tracker](https://github.com/sashabaranov/go-openai/issues) before creating one.\n\n**Describe the bug**\nA clear and concise description of what the bug is. If it's an API-related bug, please provide relevant endpoint(s).\n\n**To Reproduce**\nSteps to reproduce the behavior, including any relevant code snippets.\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots/Logs**\nIf applicable, add screenshots to help explain your problem. For non-graphical issues, please provide any relevant logs or stack traces.\n\n**Environment (please complete the following information):**\n - go-openai version: [e.g. v1.12.0]\n - Go version: [e.g. 1.18]\n - OpenAI API version: [e.g. v1]\n - OS: [e.g. Ubuntu 20.04]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\nYour issue may already be reported!\nPlease search on the [issue tracker](https://github.com/sashabaranov/go-openai/issues) before creating one.\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "A similar PR may already be submitted!\nPlease search among the [Pull request](https://github.com/sashabaranov/go-openai/pulls) before creating one.\n\nIf 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.\n\nThanks for submitting a pull request! Please provide enough information so that others can review your pull request.\n\n**Describe the change**\nPlease provide a clear and concise description of the changes you're proposing. Explain what problem it solves or what feature it adds.\n\n**Provide OpenAI documentation link**\nProvide a relevant API doc from https://platform.openai.com/docs/api-reference\n\n**Describe your solution**\nDescribe 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.\n\n**Tests**\nBriefly describe how you have tested these changes. If possible — please add integration tests.\n\n**Additional context**\nAdd 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.\n\nIssue: #XXXX\n"
  },
  {
    "path": ".github/workflows/close-inactive-issues.yml",
    "content": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v9\n        with:\n          days-before-issue-stale: 30\n          days-before-issue-close: 14\n          stale-issue-label: \"stale\"\n          exempt-issue-labels: 'bug,enhancement'\n          stale-issue-message: \"This issue is stale because it has been open for 30 days with no activity.\"\n          close-issue-message: \"This issue was closed because it has been inactive for 14 days since being marked as stale.\"\n          days-before-pr-stale: -1\n          days-before-pr-close: -1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/integration-tests.yml",
    "content": "name: Integration tests\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  integration_tests:\n    name: Run integration tests\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: '1.21'\n    - name: Run integration tests\n      env:\n        OPENAI_TOKEN: ${{ secrets.OPENAI_TOKEN }}\n      run: go test -v -tags=integration ./api_integration_test.go\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: Sanity check\n\non:\n  - push\n  - pull_request\n\njobs:\n  prcheck:\n    name: Sanity check\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: '1.24'\n    - name: Run vet\n      run: |\n        go vet -stdversion ./...\n    - name: Run golangci-lint\n      uses: golangci/golangci-lint-action@v7\n      with:\n        version: v2.1.5\n    - name: Run tests\n      run: go test -race -covermode=atomic -coverprofile=coverage.out -v ./...\n    - name: Upload coverage reports to Codecov\n      uses: codecov/codecov-action@v5\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Auth token for tests\n.openai-token\n.idea\n\n# Generated by tests\ntest.mp3"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nlinters:\n  default: none\n  enable:\n    - asciicheck\n    - bidichk\n    - bodyclose\n    - contextcheck\n    - cyclop\n    - dupl\n    - durationcheck\n    - errcheck\n    - errname\n    - errorlint\n    - exhaustive\n    - forbidigo\n    - funlen\n    - gochecknoinits\n    - gocognit\n    - goconst\n    - gocritic\n    - gocyclo\n    - godot\n    - gomoddirectives\n    - gomodguard\n    - goprintffuncname\n    - gosec\n    - govet\n    - ineffassign\n    - lll\n    - makezero\n    - mnd\n    - nestif\n    - nilerr\n    - nilnil\n    - nolintlint\n    - nosprintfhostport\n    - predeclared\n    - promlinter\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - staticcheck\n    - testpackage\n    - tparallel\n    - unconvert\n    - unparam\n    - unused\n    - usetesting\n    - wastedassign\n    - whitespace\n  settings:\n    cyclop:\n      max-complexity: 30\n      package-average: 10\n    errcheck:\n      check-type-assertions: true\n    funlen:\n      lines: 100\n      statements: 50\n    gocognit:\n      min-complexity: 20\n    gocritic:\n      settings:\n        captLocal:\n          paramsOnly: false\n        underef:\n          skipRecvDeref: false\n    gomodguard:\n      blocked:\n        modules:\n          - github.com/golang/protobuf:\n              recommendations:\n                - google.golang.org/protobuf\n              reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules\n          - github.com/satori/go.uuid:\n              recommendations:\n                - github.com/google/uuid\n              reason: satori's package is not maintained\n          - github.com/gofrs/uuid:\n              recommendations:\n                - github.com/google/uuid\n              reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw'\n    govet:\n      disable:\n        - fieldalignment\n      enable-all: true\n      settings:\n        shadow:\n          strict: true\n    mnd:\n      ignored-functions:\n        - os.Chmod\n        - os.Mkdir\n        - os.MkdirAll\n        - os.OpenFile\n        - os.WriteFile\n        - prometheus.ExponentialBuckets\n        - prometheus.ExponentialBucketsRange\n        - prometheus.LinearBuckets\n        - strconv.FormatFloat\n        - strconv.FormatInt\n        - strconv.FormatUint\n        - strconv.ParseFloat\n        - strconv.ParseInt\n        - strconv.ParseUint\n    nakedret:\n      max-func-lines: 0\n    nolintlint:\n      require-explanation: true\n      require-specific: true\n      allow-no-explanation:\n        - funlen\n        - gocognit\n        - lll\n    rowserrcheck:\n      packages:\n        - github.com/jmoiron/sqlx\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - forbidigo\n          - mnd\n          - revive\n        path : ^examples/.*\\.go$\n      - linters:\n          - lll\n        source: ^//\\s*go:generate\\s\n      - linters:\n          - godot\n        source: (noinspection|TODO)\n      - linters:\n          - gocritic\n        source: //noinspection\n      - linters:\n          - errorlint\n        source: ^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {\n      - linters:\n          - bodyclose\n          - dupl\n          - funlen\n          - goconst\n          - gosec\n          - noctx\n          - wrapcheck\n          - staticcheck\n        path: _test\\.go\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nissues:\n  max-same-issues: 50\nformatters:\n  enable:\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\n## Overview\nThank 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.\n\n## Reporting Bugs\nIf 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.\n\n## Suggesting Features\nIf 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.\n\n## Reporting Vulnerabilities\nIf 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.\n\n## Questions for Users\nIf you have questions, please utilize [StackOverflow](https://stackoverflow.com/) or the [GitHub Discussions page](https://github.com/sashabaranov/go-openai/discussions).\n\n## Contributing Code\nThere might already be a similar pull requests submitted! Please search for [pull requests](https://github.com/sashabaranov/go-openai/pulls) before creating one.\n\n### Requirements for Merging a Pull Request\n\nThe requirements to accept a pull request are as follows:\n\n- Features not provided by the OpenAI API will not be accepted.\n- The functionality of the feature must match that of the official OpenAI API.\n- All pull requests should be written in Go according to common conventions, formatted with `goimports`, and free of warnings from tools like `golangci-lint`.\n- Include tests and ensure all tests pass.\n- Maintain test coverage without any reduction.\n- All pull requests require approval from at least one Go OpenAI maintainer.\n\n**Note:**  \nThe merging method for pull requests in this repository is squash merge.\n\n### Creating a Pull Request\n- Fork the repository.\n- Create a new branch and commit your changes.\n- Push that branch to GitHub.\n- Start a new Pull Request on GitHub. (Please use the pull request template to provide detailed information.)\n\n**Note:**  \nIf your changes introduce breaking changes, please prefix your pull request title with \"[BREAKING_CHANGES]\".\n\n### Code Style\nIn 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`.\n\n**Run goimports:**\n```\ngo install golang.org/x/tools/cmd/goimports@latest\n```\n\n```\ngoimports -w .\n```\n\n**Run golangci-lint:**\n```\ngo install github.com/golangci/golangci-lint/cmd/golangci-lint@latest\n```\n\n```\ngolangci-lint run --out-format=github-actions\n```\n\n### Unit Test\nPlease create or update tests relevant to your changes. Ensure all tests run successfully to verify that your modifications do not adversely affect other functionalities.\n\n**Run test:**\n```\ngo test -v ./...\n```\n\n### Integration Test\nIntegration 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.\n\n**Notes:**\nThese tests send real network traffic to the OpenAI API and may reach rate limits. Temporary network problems may also cause the test to fail.\n\n**Run integration test:**\n```\nOPENAI_TOKEN=XXX go test -v -tags=integration ./api_integration_test.go\n```\n\nIf the `OPENAI_TOKEN` environment variable is not available, integration tests will be skipped.\n\n---\n\nWe wholeheartedly welcome your active participation. Let's build an amazing project together!\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Go OpenAI\n[![Go Reference](https://pkg.go.dev/badge/github.com/sashabaranov/go-openai.svg)](https://pkg.go.dev/github.com/sashabaranov/go-openai)\n[![Go Report Card](https://goreportcard.com/badge/github.com/sashabaranov/go-openai)](https://goreportcard.com/report/github.com/sashabaranov/go-openai)\n[![codecov](https://codecov.io/gh/sashabaranov/go-openai/branch/master/graph/badge.svg?token=bCbIfHLIsW)](https://codecov.io/gh/sashabaranov/go-openai)\n\nThis library provides unofficial Go clients for [OpenAI API](https://platform.openai.com/). We support: \n\n* ChatGPT 4o, o1\n* GPT-3, GPT-4\n* DALL·E 2, DALL·E 3, GPT Image 1\n* Whisper\n\n## Installation\n\n```\ngo get github.com/sashabaranov/go-openai\n```\nCurrently, go-openai requires Go version 1.18 or greater.\n\n\n## Usage\n\n### ChatGPT example usage:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient := openai.NewClient(\"your token\")\n\tresp, err := client.CreateChatCompletion(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(resp.Choices[0].Message.Content)\n}\n\n```\n\n### Getting an OpenAI API Key:\n\n1. Visit the OpenAI website at [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys).\n2. If you don't have an account, click on \"Sign Up\" to create one. If you do, click \"Log In\".\n3. Once logged in, navigate to your API key management page.\n4. Click on \"Create new secret key\".\n5. Enter a name for your new key, then click \"Create secret key\".\n6. Your new API key will be displayed. Use this key to interact with the OpenAI API.\n\n**Note:** Your API key is sensitive information. Do not share it with anyone.\n\n### Other examples:\n\n<details>\n<summary>ChatGPT streaming completion</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\treq := openai.ChatCompletionRequest{\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMaxTokens: 20,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Lorem ipsum\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t}\n\tstream, err := c.CreateChatCompletionStream(ctx, req)\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletionStream error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\tfmt.Printf(\"Stream response: \")\n\tfor {\n\t\tresponse, err := stream.Recv()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tfmt.Println(\"\\nStream finished\")\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"\\nStream error: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(response.Choices[0].Delta.Content)\n\t}\n}\n```\n</details>\n\n<details>\n<summary>GPT-3 completion</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\treq := openai.CompletionRequest{\n\t\tModel:     openai.GPT3Babbage002,\n\t\tMaxTokens: 5,\n\t\tPrompt:    \"Lorem ipsum\",\n\t}\n\tresp, err := c.CreateCompletion(ctx, req)\n\tif err != nil {\n\t\tfmt.Printf(\"Completion error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Choices[0].Text)\n}\n```\n</details>\n\n<details>\n<summary>GPT-3 streaming completion</summary>\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\treq := openai.CompletionRequest{\n\t\tModel:     openai.GPT3Babbage002,\n\t\tMaxTokens: 5,\n\t\tPrompt:    \"Lorem ipsum\",\n\t\tStream:    true,\n\t}\n\tstream, err := c.CreateCompletionStream(ctx, req)\n\tif err != nil {\n\t\tfmt.Printf(\"CompletionStream error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\tfor {\n\t\tresponse, err := stream.Recv()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tfmt.Println(\"Stream finished\")\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Stream error: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\n\t\tfmt.Printf(\"Stream response: %v\\n\", response)\n\t}\n}\n```\n</details>\n\n<details>\n<summary>Audio Speech-To-Text</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\treq := openai.AudioRequest{\n\t\tModel:    openai.Whisper1,\n\t\tFilePath: \"recording.mp3\",\n\t}\n\tresp, err := c.CreateTranscription(ctx, req)\n\tif err != nil {\n\t\tfmt.Printf(\"Transcription error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Text)\n}\n```\n</details>\n\n<details>\n<summary>Audio Captions</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(os.Getenv(\"OPENAI_KEY\"))\n\n\treq := openai.AudioRequest{\n\t\tModel:    openai.Whisper1,\n\t\tFilePath: os.Args[1],\n\t\tFormat:   openai.AudioResponseFormatSRT,\n\t}\n\tresp, err := c.CreateTranscription(context.Background(), req)\n\tif err != nil {\n\t\tfmt.Printf(\"Transcription error: %v\\n\", err)\n\t\treturn\n\t}\n\tf, err := os.Create(os.Args[1] + \".srt\")\n\tif err != nil {\n\t\tfmt.Printf(\"Could not open file: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\tif _, err := f.WriteString(resp.Text); err != nil {\n\t\tfmt.Printf(\"Error writing to file: %v\\n\", err)\n\t\treturn\n\t}\n}\n```\n</details>\n\n<details>\n<summary>DALL-E 2 image generation</summary>\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"image/png\"\n\t\"os\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\t// Sample image by link\n\treqUrl := openai.ImageRequest{\n\t\tPrompt:         \"Parrot on a skateboard performs a trick, cartoon style, natural light, high detail\",\n\t\tSize:           openai.CreateImageSize256x256,\n\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t\tN:              1,\n\t}\n\n\trespUrl, err := c.CreateImage(ctx, reqUrl)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(respUrl.Data[0].URL)\n\n\t// Example image as base64\n\treqBase64 := openai.ImageRequest{\n\t\tPrompt:         \"Portrait of a humanoid parrot in a classic costume, high detail, realistic light, unreal engine\",\n\t\tSize:           openai.CreateImageSize256x256,\n\t\tResponseFormat: openai.CreateImageResponseFormatB64JSON,\n\t\tN:              1,\n\t}\n\n\trespBase64, err := c.CreateImage(ctx, reqBase64)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation error: %v\\n\", err)\n\t\treturn\n\t}\n\n\timgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON)\n\tif err != nil {\n\t\tfmt.Printf(\"Base64 decode error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tr := bytes.NewReader(imgBytes)\n\timgData, err := png.Decode(r)\n\tif err != nil {\n\t\tfmt.Printf(\"PNG decode error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfile, err := os.Create(\"example.png\")\n\tif err != nil {\n\t\tfmt.Printf(\"File creation error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\tif err := png.Encode(file, imgData); err != nil {\n\t\tfmt.Printf(\"PNG encode error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(\"The image was saved as example.png\")\n}\n\n```\n</details>\n\n<details>\n<summary>GPT Image 1 image generation</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tc := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\treq := openai.ImageRequest{\n\t\tPrompt:            \"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.\",\n\t\tBackground:        openai.CreateImageBackgroundOpaque,\n\t\tModel:             openai.CreateImageModelGptImage1,\n\t\tSize:              openai.CreateImageSize1024x1024,\n\t\tN:                 1,\n\t\tQuality:           openai.CreateImageQualityLow,\n\t\tOutputCompression: 100,\n\t\tOutputFormat:      openai.CreateImageOutputFormatJPEG,\n\t\t// Moderation: \t\t openai.CreateImageModerationLow,\n\t\t// User: \t\t\t\t\t \"\",\n\t}\n\n\tresp, err := c.CreateImage(ctx, req)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation Image generation with GPT Image 1error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(\"Image Base64:\", resp.Data[0].B64JSON)\n\n\t// Decode the base64 data\n\timgBytes, err := base64.StdEncoding.DecodeString(resp.Data[0].B64JSON)\n\tif err != nil {\n\t\tfmt.Printf(\"Base64 decode error: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// Write image to file\n\toutputPath := \"generated_image.jpg\"\n\terr = os.WriteFile(outputPath, imgBytes, 0644)\n\tif err != nil {\n\t\tfmt.Printf(\"Failed to write image file: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"The image was saved as %s\\n\", outputPath)\n}\n```\n</details>\n\n<details>\n<summary>Configuring proxy</summary>\n\n```go\nconfig := openai.DefaultConfig(\"token\")\nproxyUrl, err := url.Parse(\"http://localhost:{port}\")\nif err != nil {\n\tpanic(err)\n}\ntransport := &http.Transport{\n\tProxy: http.ProxyURL(proxyUrl),\n}\nconfig.HTTPClient = &http.Client{\n\tTransport: transport,\n}\n\nc := openai.NewClientWithConfig(config)\n```\n\nSee also: https://pkg.go.dev/github.com/sashabaranov/go-openai#ClientConfig\n</details>\n\n<details>\n<summary>ChatGPT support context</summary>\n\n```go\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient := openai.NewClient(\"your token\")\n\tmessages := make([]openai.ChatCompletionMessage, 0)\n\treader := bufio.NewReader(os.Stdin)\n\tfmt.Println(\"Conversation\")\n\tfmt.Println(\"---------------------\")\n\n\tfor {\n\t\tfmt.Print(\"-> \")\n\t\ttext, _ := reader.ReadString('\\n')\n\t\t// convert CRLF to LF\n\t\ttext = strings.Replace(text, \"\\n\", \"\", -1)\n\t\tmessages = append(messages, openai.ChatCompletionMessage{\n\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\tContent: text,\n\t\t})\n\n\t\tresp, err := client.CreateChatCompletion(\n\t\t\tcontext.Background(),\n\t\t\topenai.ChatCompletionRequest{\n\t\t\t\tModel:    openai.GPT3Dot5Turbo,\n\t\t\t\tMessages: messages,\n\t\t\t},\n\t\t)\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tcontent := resp.Choices[0].Message.Content\n\t\tmessages = append(messages, openai.ChatCompletionMessage{\n\t\t\tRole:    openai.ChatMessageRoleAssistant,\n\t\t\tContent: content,\n\t\t})\n\t\tfmt.Println(content)\n\t}\n}\n```\n</details>\n\n<details>\n<summary>Azure OpenAI ChatGPT</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tconfig := openai.DefaultAzureConfig(\"your Azure OpenAI Key\", \"https://your Azure OpenAI Endpoint\")\n\t// If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function\n\t// config.AzureModelMapperFunc = func(model string) string {\n\t// \tazureModelMapping := map[string]string{\n\t// \t\t\"gpt-3.5-turbo\": \"your gpt-3.5-turbo deployment name\",\n\t// \t}\n\t// \treturn azureModelMapping[model]\n\t// }\n\n\tclient := openai.NewClientWithConfig(config)\n\tresp, err := client.CreateChatCompletion(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello Azure OpenAI!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(resp.Choices[0].Message.Content)\n}\n\n```\n</details>\n\n<details>\n<summary>Embedding Semantic Similarity</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\topenai \"github.com/sashabaranov/go-openai\"\n\n)\n\nfunc main() {\n\tclient := openai.NewClient(\"your-token\")\n\n\t// Create an EmbeddingRequest for the user query\n\tqueryReq := openai.EmbeddingRequest{\n\t\tInput: []string{\"How many chucks would a woodchuck chuck\"},\n\t\tModel: openai.AdaEmbeddingV2,\n\t}\n\n\t// Create an embedding for the user query\n\tqueryResponse, err := client.CreateEmbeddings(context.Background(), queryReq)\n\tif err != nil {\n\t\tlog.Fatal(\"Error creating query embedding:\", err)\n\t}\n\n\t// Create an EmbeddingRequest for the target text\n\ttargetReq := openai.EmbeddingRequest{\n\t\tInput: []string{\"How many chucks would a woodchuck chuck if the woodchuck could chuck wood\"},\n\t\tModel: openai.AdaEmbeddingV2,\n\t}\n\n\t// Create an embedding for the target text\n\ttargetResponse, err := client.CreateEmbeddings(context.Background(), targetReq)\n\tif err != nil {\n\t\tlog.Fatal(\"Error creating target embedding:\", err)\n\t}\n\n\t// Now that we have the embeddings for the user query and the target text, we\n\t// can calculate their similarity.\n\tqueryEmbedding := queryResponse.Data[0]\n\ttargetEmbedding := targetResponse.Data[0]\n\n\tsimilarity, err := queryEmbedding.DotProduct(&targetEmbedding)\n\tif err != nil {\n\t\tlog.Fatal(\"Error calculating dot product:\", err)\n\t}\n\n\tlog.Printf(\"The similarity score between the query and the target is %f\", similarity)\n}\n\n```\n</details>\n\n<details>\n<summary>Azure OpenAI Embeddings</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\n\tconfig := openai.DefaultAzureConfig(\"your Azure OpenAI Key\", \"https://your Azure OpenAI Endpoint\")\n\tconfig.APIVersion = \"2023-05-15\" // optional update to latest API version\n\n\t//If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function\n\t//config.AzureModelMapperFunc = func(model string) string {\n\t//    azureModelMapping := map[string]string{\n\t//        \"gpt-3.5-turbo\":\"your gpt-3.5-turbo deployment name\",\n\t//    }\n\t//    return azureModelMapping[model]\n\t//}\n\n\tinput := \"Text to vectorize\"\n\n\tclient := openai.NewClientWithConfig(config)\n\tresp, err := client.CreateEmbeddings(\n\t\tcontext.Background(),\n\t\topenai.EmbeddingRequest{\n\t\t\tInput: []string{input},\n\t\t\tModel: openai.AdaEmbeddingV2,\n\t\t})\n\n\tif err != nil {\n\t\tfmt.Printf(\"CreateEmbeddings error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tvectors := resp.Data[0].Embedding // []float32 with 1536 dimensions\n\n\tfmt.Println(vectors[:10], \"...\", vectors[len(vectors)-10:])\n}\n```\n</details>\n\n<details>\n<summary>JSON Schema for function calling</summary>\n\nIt 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)).\n\nIn 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.\n\nThe developer documents give this JSON schema definition as an example:\n\n```json\n{\n  \"name\":\"get_current_weather\",\n  \"description\":\"Get the current weather in a given location\",\n  \"parameters\":{\n    \"type\":\"object\",\n    \"properties\":{\n        \"location\":{\n          \"type\":\"string\",\n          \"description\":\"The city and state, e.g. San Francisco, CA\"\n        },\n        \"unit\":{\n          \"type\":\"string\",\n          \"enum\":[\n              \"celsius\",\n              \"fahrenheit\"\n          ]\n        }\n    },\n    \"required\":[\n        \"location\"\n    ]\n  }\n}\n```\n\nUsing the `jsonschema` package, this schema could be created using structs as such:\n\n```go\nFunctionDefinition{\n  Name: \"get_current_weather\",\n  Parameters: jsonschema.Definition{\n    Type: jsonschema.Object,\n    Properties: map[string]jsonschema.Definition{\n      \"location\": {\n        Type: jsonschema.String,\n        Description: \"The city and state, e.g. San Francisco, CA\",\n      },\n      \"unit\": {\n        Type: jsonschema.String,\n        Enum: []string{\"celsius\", \"fahrenheit\"},\n      },\n    },\n    Required: []string{\"location\"},\n  },\n}\n```\n\nThe `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).\n</details>\n\n<details>\n<summary>Error handling</summary>\n\nOpen-AI maintains clear documentation on how to [handle API errors](https://platform.openai.com/docs/guides/error-codes/api-errors)\n\nexample:\n```\ne := &openai.APIError{}\nif errors.As(err, &e) {\n  switch e.HTTPStatusCode {\n    case 401:\n      // invalid auth or key (do not retry)\n    case 429:\n      // rate limiting or engine overload (wait and retry) \n    case 500:\n      // openai server error (retry)\n    default:\n      // unhandled\n  }\n}\n\n```\n</details>\n\n<details>\n<summary>Fine Tune Model</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc main() {\n\tclient := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\t// create a .jsonl file with your training data for conversational model\n\t// {\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n\t// {\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n\t// {\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n\n\t// chat models are trained using the following file format:\n\t// {\"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.\"}]}\n\t// {\"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?\"}]}\n\t// {\"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.\"}]}\n\n\t// you can use openai cli tool to validate the data\n\t// For more info - https://platform.openai.com/docs/guides/fine-tuning\n\n\tfile, err := client.CreateFile(ctx, openai.FileRequest{\n\t\tFilePath: \"training_prepared.jsonl\",\n\t\tPurpose:  \"fine-tune\",\n\t})\n\tif err != nil {\n\t\tfmt.Printf(\"Upload JSONL file error: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// create a fine tuning job\n\t// 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)\n\t// use below get method to know the status of your model\n\tfineTuningJob, err := client.CreateFineTuningJob(ctx, openai.FineTuningJobRequest{\n\t\tTrainingFile: file.ID,\n\t\tModel:        \"davinci-002\", // gpt-3.5-turbo-0613, babbage-002.\n\t})\n\tif err != nil {\n\t\tfmt.Printf(\"Creating new fine tune model error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfineTuningJob, err = client.RetrieveFineTuningJob(ctx, fineTuningJob.ID)\n\tif err != nil {\n\t\tfmt.Printf(\"Getting fine tune model error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(fineTuningJob.FineTunedModel)\n\n\t// once the status of fineTuningJob is `succeeded`, you can use your fine tune model in Completion Request or Chat Completion Request\n\n\t// resp, err := client.CreateCompletion(ctx, openai.CompletionRequest{\n\t//\t Model:  fineTuningJob.FineTunedModel,\n\t//\t Prompt: \"your prompt\",\n\t// })\n\t// if err != nil {\n\t//\t fmt.Printf(\"Create completion error %v\\n\", err)\n\t//\t return\n\t// }\n\t//\n\t// fmt.Println(resp.Choices[0].Text)\n}\n```\n</details>\n\n<details>\n<summary>Structured Outputs</summary>\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nfunc main() {\n\tclient := openai.NewClient(\"your token\")\n\tctx := context.Background()\n\n\ttype Result struct {\n\t\tSteps []struct {\n\t\t\tExplanation string `json:\"explanation\"`\n\t\t\tOutput      string `json:\"output\"`\n\t\t} `json:\"steps\"`\n\t\tFinalAnswer string `json:\"final_answer\"`\n\t}\n\tvar result Result\n\tschema, err := jsonschema.GenerateSchemaForType(result)\n\tif err != nil {\n\t\tlog.Fatalf(\"GenerateSchemaForType error: %v\", err)\n\t}\n\tresp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{\n\t\tModel: openai.GPT4oMini,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleSystem,\n\t\t\t\tContent: \"You are a helpful math tutor. Guide the user through the solution step by step.\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"how can I solve 8x + 7 = -23\",\n\t\t\t},\n\t\t},\n\t\tResponseFormat: &openai.ChatCompletionResponseFormat{\n\t\t\tType: openai.ChatCompletionResponseFormatTypeJSONSchema,\n\t\t\tJSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{\n\t\t\t\tName:   \"math_reasoning\",\n\t\t\t\tSchema: schema,\n\t\t\t\tStrict: true,\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"CreateChatCompletion error: %v\", err)\n\t}\n\terr = schema.Unmarshal(resp.Choices[0].Message.Content, &result)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unmarshal schema error: %v\", err)\n\t}\n\tfmt.Println(result)\n}\n```\n</details>\nSee the `examples/` folder for more.\n\n## Frequently Asked Questions\n\n### Why don't we get the same answer when specifying a temperature field of 0 and asking the same question?\n\nEven when specifying a temperature field of 0, it doesn't guarantee that you'll always get the same response. Several factors come into play.\n\n1. 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.\n2. 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.\n\nDue to the factors mentioned above, different answers may be returned even for the same question.\n\n**Workarounds:**\n1. 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.\n2. 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.\n3. 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.\n\nBy adopting these strategies, you can expect more consistent results.\n\n**Related Issues:**  \n[omitempty option of request struct will generate incorrect request when parameter is 0.](https://github.com/sashabaranov/go-openai/issues/9)\n\n### Does Go OpenAI provide a method to count tokens?\n\nNo, 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.\n\nFor counting tokens, you might find the following links helpful:  \n- [Counting Tokens For Chat API Calls](https://github.com/pkoukk/tiktoken-go#counting-tokens-for-chat-api-calls)\n- [How to count tokens with tiktoken](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n**Related Issues:**  \n[Is it possible to join the implementation of GPT3 Tokenizer](https://github.com/sashabaranov/go-openai/issues/62)\n\n## Contributing\n\nBy 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.\n\n## Thank you\n\nWe 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:\n- [Carson Kahn](https://carsonkahn.com) of [Spindle AI](https://spindleai.com)\n\nTo 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!\n"
  },
  {
    "path": "api_integration_test.go",
    "content": "//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\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nfunc TestAPI(t *testing.T) {\n\tapiToken := os.Getenv(\"OPENAI_TOKEN\")\n\tif apiToken == \"\" {\n\t\tt.Skip(\"Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.\")\n\t}\n\n\tvar err error\n\tc := openai.NewClient(apiToken)\n\tctx := context.Background()\n\t_, err = c.ListEngines(ctx)\n\tchecks.NoError(t, err, \"ListEngines error\")\n\n\t_, err = c.GetEngine(ctx, openai.GPT3Davinci002)\n\tchecks.NoError(t, err, \"GetEngine error\")\n\n\tfileRes, err := c.ListFiles(ctx)\n\tchecks.NoError(t, err, \"ListFiles error\")\n\n\tif len(fileRes.Files) > 0 {\n\t\t_, err = c.GetFile(ctx, fileRes.Files[0].ID)\n\t\tchecks.NoError(t, err, \"GetFile error\")\n\t} // else skip\n\n\tembeddingReq := openai.EmbeddingRequest{\n\t\tInput: []string{\n\t\t\t\"The food was delicious and the waiter\",\n\t\t\t\"Other examples of embedding request\",\n\t\t},\n\t\tModel: openai.AdaEmbeddingV2,\n\t}\n\t_, err = c.CreateEmbeddings(ctx, embeddingReq)\n\tchecks.NoError(t, err, \"Embedding error\")\n\n\t_, err = c.CreateChatCompletion(\n\t\tctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\n\tchecks.NoError(t, err, \"CreateChatCompletion (without name) returned error\")\n\n\t_, err = c.CreateChatCompletion(\n\t\tctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tName:    \"John_Doe\",\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateChatCompletion (with name) returned error\")\n\n\t_, err = c.CreateChatCompletion(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"What is the weather like in Boston?\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefinition{{\n\t\t\t\tName: \"get_current_weather\",\n\t\t\t\tParameters: jsonschema.Definition{\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"location\": {\n\t\t\t\t\t\t\tType:        jsonschema.String,\n\t\t\t\t\t\t\tDescription: \"The city and state, e.g. San Francisco, CA\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"unit\": {\n\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\tEnum: []string{\"celsius\", \"fahrenheit\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tRequired: []string{\"location\"},\n\t\t\t\t},\n\t\t\t}},\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateChatCompletion (with functions) returned error\")\n}\n\nfunc TestCompletionStream(t *testing.T) {\n\tapiToken := os.Getenv(\"OPENAI_TOKEN\")\n\tif apiToken == \"\" {\n\t\tt.Skip(\"Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.\")\n\t}\n\n\tc := openai.NewClient(apiToken)\n\tctx := context.Background()\n\n\tstream, err := c.CreateCompletionStream(ctx, openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     openai.GPT3Babbage002,\n\t\tMaxTokens: 5,\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\tcounter := 0\n\tfor {\n\t\t_, err = stream.Recv()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.Errorf(\"Stream error: %v\", err)\n\t\t} else {\n\t\t\tcounter++\n\t\t}\n\t}\n\tif counter == 0 {\n\t\tt.Error(\"Stream did not return any responses\")\n\t}\n}\n\nfunc TestAPIError(t *testing.T) {\n\tapiToken := os.Getenv(\"OPENAI_TOKEN\")\n\tif apiToken == \"\" {\n\t\tt.Skip(\"Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.\")\n\t}\n\n\tvar err error\n\tc := openai.NewClient(apiToken + \"_invalid\")\n\tctx := context.Background()\n\t_, err = c.ListEngines(ctx)\n\tchecks.HasError(t, err, \"ListEngines should fail with an invalid key\")\n\n\tvar apiErr *openai.APIError\n\tif !errors.As(err, &apiErr) {\n\t\tt.Fatalf(\"Error is not an APIError: %+v\", err)\n\t}\n\n\tif apiErr.HTTPStatusCode != 401 {\n\t\tt.Fatalf(\"Unexpected API error status code: %d\", apiErr.HTTPStatusCode)\n\t}\n\n\tswitch v := apiErr.Code.(type) {\n\tcase string:\n\t\tif v != \"invalid_api_key\" {\n\t\t\tt.Fatalf(\"Unexpected API error code: %s\", v)\n\t\t}\n\tdefault:\n\t\tt.Fatalf(\"Unexpected API error code type: %T\", v)\n\t}\n\n\tif apiErr.Error() == \"\" {\n\t\tt.Fatal(\"Empty error message occurred\")\n\t}\n}\n\nfunc TestChatCompletionResponseFormat_JSONSchema(t *testing.T) {\n\tapiToken := os.Getenv(\"OPENAI_TOKEN\")\n\tif apiToken == \"\" {\n\t\tt.Skip(\"Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.\")\n\t}\n\n\tvar err error\n\tc := openai.NewClient(apiToken)\n\tctx := context.Background()\n\n\ttype MyStructuredResponse struct {\n\t\tPascalCase string `json:\"pascal_case\" required:\"true\" description:\"PascalCase\"`\n\t\tCamelCase  string `json:\"camel_case\" required:\"true\" description:\"CamelCase\"`\n\t\tKebabCase  string `json:\"kebab_case\" required:\"true\" description:\"KebabCase\"`\n\t\tSnakeCase  string `json:\"snake_case\" required:\"true\" description:\"SnakeCase\"`\n\t}\n\tvar result MyStructuredResponse\n\tschema, err := jsonschema.GenerateSchemaForType(result)\n\tif err != nil {\n\t\tt.Fatal(\"CreateChatCompletion (use json_schema response) GenerateSchemaForType error\")\n\t}\n\tresp, err := c.CreateChatCompletion(\n\t\tctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT4oMini,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole: openai.ChatMessageRoleSystem,\n\t\t\t\t\tContent: \"Please enter a string, and we will convert it into the following naming conventions:\" +\n\t\t\t\t\t\t\"1. PascalCase: Each word starts with an uppercase letter, with no spaces or separators.\" +\n\t\t\t\t\t\t\"2. CamelCase: The first word starts with a lowercase letter, \" +\n\t\t\t\t\t\t\"and subsequent words start with an uppercase letter, with no spaces or separators.\" +\n\t\t\t\t\t\t\"3. KebabCase: All letters are lowercase, with words separated by hyphens `-`.\" +\n\t\t\t\t\t\t\"4. SnakeCase: All letters are lowercase, with words separated by underscores `_`.\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello World\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tResponseFormat: &openai.ChatCompletionResponseFormat{\n\t\t\t\tType: openai.ChatCompletionResponseFormatTypeJSONSchema,\n\t\t\t\tJSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{\n\t\t\t\t\tName:   \"cases\",\n\t\t\t\t\tSchema: schema,\n\t\t\t\t\tStrict: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateChatCompletion (use json_schema response) returned error\")\n\tif err == nil {\n\t\terr = schema.Unmarshal(resp.Choices[0].Message.Content, &result)\n\t\tchecks.NoError(t, err, \"CreateChatCompletion (use json_schema response) unmarshal error\")\n\t}\n}\n\nfunc TestChatCompletionStructuredOutputsFunctionCalling(t *testing.T) {\n\tapiToken := os.Getenv(\"OPENAI_TOKEN\")\n\tif apiToken == \"\" {\n\t\tt.Skip(\"Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.\")\n\t}\n\n\tvar err error\n\tc := openai.NewClient(apiToken)\n\tctx := context.Background()\n\n\tresp, err := c.CreateChatCompletion(\n\t\tctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT4oMini,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole: openai.ChatMessageRoleSystem,\n\t\t\t\t\tContent: \"Please enter a string, and we will convert it into the following naming conventions:\" +\n\t\t\t\t\t\t\"1. PascalCase: Each word starts with an uppercase letter, with no spaces or separators.\" +\n\t\t\t\t\t\t\"2. CamelCase: The first word starts with a lowercase letter, \" +\n\t\t\t\t\t\t\"and subsequent words start with an uppercase letter, with no spaces or separators.\" +\n\t\t\t\t\t\t\"3. KebabCase: All letters are lowercase, with words separated by hyphens `-`.\" +\n\t\t\t\t\t\t\"4. SnakeCase: All letters are lowercase, with words separated by underscores `_`.\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello World\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tTools: []openai.Tool{\n\t\t\t\t{\n\t\t\t\t\tType: openai.ToolTypeFunction,\n\t\t\t\t\tFunction: &openai.FunctionDefinition{\n\t\t\t\t\t\tName:   \"display_cases\",\n\t\t\t\t\t\tStrict: true,\n\t\t\t\t\t\tParameters: &jsonschema.Definition{\n\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\"PascalCase\": {\n\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"CamelCase\": {\n\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"KebabCase\": {\n\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"SnakeCase\": {\n\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRequired:             []string{\"PascalCase\", \"CamelCase\", \"KebabCase\", \"SnakeCase\"},\n\t\t\t\t\t\t\tAdditionalProperties: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tToolChoice: openai.ToolChoice{\n\t\t\t\tType: openai.ToolTypeFunction,\n\t\t\t\tFunction: openai.ToolFunction{\n\t\t\t\t\tName: \"display_cases\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateChatCompletion (use structured outputs response) returned error\")\n\tvar result = make(map[string]string)\n\terr = json.Unmarshal([]byte(resp.Choices[0].Message.ToolCalls[0].Function.Arguments), &result)\n\tchecks.NoError(t, err, \"CreateChatCompletion (use structured outputs response) unmarshal error\")\n\tfor _, key := range []string{\"PascalCase\", \"CamelCase\", \"KebabCase\", \"SnakeCase\"} {\n\t\tif _, ok := result[key]; !ok {\n\t\t\tt.Errorf(\"key:%s does not exist.\", key)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "api_internal_test.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestOpenAIFullURL(t *testing.T) {\n\tcases := []struct {\n\t\tName   string\n\t\tSuffix string\n\t\tExpect string\n\t}{\n\t\t{\n\t\t\t\"ChatCompletionsURL\",\n\t\t\t\"/chat/completions\",\n\t\t\t\"https://api.openai.com/v1/chat/completions\",\n\t\t},\n\t\t{\n\t\t\t\"CompletionsURL\",\n\t\t\t\"/completions\",\n\t\t\t\"https://api.openai.com/v1/completions\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.Name, func(t *testing.T) {\n\t\t\taz := DefaultConfig(\"dummy\")\n\t\t\tcli := NewClientWithConfig(az)\n\t\t\tactual := cli.fullURL(c.Suffix)\n\t\t\tif actual != c.Expect {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\", c.Expect, actual)\n\t\t\t}\n\t\t\tt.Logf(\"Full URL: %s\", actual)\n\t\t})\n\t}\n}\n\nfunc TestRequestAuthHeader(t *testing.T) {\n\tcases := []struct {\n\t\tName      string\n\t\tAPIType   APIType\n\t\tHeaderKey string\n\t\tToken     string\n\t\tOrgID     string\n\t\tExpect    string\n\t}{\n\t\t{\n\t\t\t\"OpenAIDefault\",\n\t\t\t\"\",\n\t\t\t\"Authorization\",\n\t\t\t\"dummy-token-openai\",\n\t\t\t\"\",\n\t\t\t\"Bearer dummy-token-openai\",\n\t\t},\n\t\t{\n\t\t\t\"OpenAIOrg\",\n\t\t\tAPITypeOpenAI,\n\t\t\t\"Authorization\",\n\t\t\t\"dummy-token-openai\",\n\t\t\t\"dummy-org-openai\",\n\t\t\t\"Bearer dummy-token-openai\",\n\t\t},\n\t\t{\n\t\t\t\"OpenAI\",\n\t\t\tAPITypeOpenAI,\n\t\t\t\"Authorization\",\n\t\t\t\"dummy-token-openai\",\n\t\t\t\"\",\n\t\t\t\"Bearer dummy-token-openai\",\n\t\t},\n\t\t{\n\t\t\t\"AzureAD\",\n\t\t\tAPITypeAzureAD,\n\t\t\t\"Authorization\",\n\t\t\t\"dummy-token-azure\",\n\t\t\t\"\",\n\t\t\t\"Bearer dummy-token-azure\",\n\t\t},\n\t\t{\n\t\t\t\"Azure\",\n\t\t\tAPITypeAzure,\n\t\t\tAzureAPIKeyHeader,\n\t\t\t\"dummy-api-key-here\",\n\t\t\t\"\",\n\t\t\t\"dummy-api-key-here\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.Name, func(t *testing.T) {\n\t\t\taz := DefaultConfig(c.Token)\n\t\t\taz.APIType = c.APIType\n\t\t\taz.OrgID = c.OrgID\n\n\t\t\tcli := NewClientWithConfig(az)\n\t\t\treq, err := cli.newRequest(context.Background(), \"POST\", \"/chat/completions\")\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Failed to create request: %v\", err)\n\t\t\t}\n\t\t\tactual := req.Header.Get(c.HeaderKey)\n\t\t\tif actual != c.Expect {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\", c.Expect, actual)\n\t\t\t}\n\t\t\tt.Logf(\"%s: %s\", c.HeaderKey, actual)\n\t\t})\n\t}\n}\n\nfunc TestAzureFullURL(t *testing.T) {\n\tcases := []struct {\n\t\tName             string\n\t\tBaseURL          string\n\t\tAzureModelMapper map[string]string\n\t\tSuffix           string\n\t\tModel            string\n\t\tExpect           string\n\t}{\n\t\t{\n\t\t\t\"AzureBaseURLWithSlashAutoStrip\",\n\t\t\t\"https://httpbin.org/\",\n\t\t\tnil,\n\t\t\t\"/chat/completions\",\n\t\t\t\"chatgpt-demo\",\n\t\t\t\"https://httpbin.org/\" +\n\t\t\t\t\"openai/deployments/chatgpt-demo\" +\n\t\t\t\t\"/chat/completions?api-version=2023-05-15\",\n\t\t},\n\t\t{\n\t\t\t\"AzureBaseURLWithoutSlashOK\",\n\t\t\t\"https://httpbin.org\",\n\t\t\tnil,\n\t\t\t\"/chat/completions\",\n\t\t\t\"chatgpt-demo\",\n\t\t\t\"https://httpbin.org/\" +\n\t\t\t\t\"openai/deployments/chatgpt-demo\" +\n\t\t\t\t\"/chat/completions?api-version=2023-05-15\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\t\"https://httpbin.org\",\n\t\t\tnil,\n\t\t\t\"/assistants?limit=10\",\n\t\t\t\"chatgpt-demo\",\n\t\t\t\"https://httpbin.org/openai/assistants?api-version=2023-05-15&limit=10\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.Name, func(t *testing.T) {\n\t\t\taz := DefaultAzureConfig(\"dummy\", c.BaseURL)\n\t\t\tcli := NewClientWithConfig(az)\n\t\t\t// /openai/deployments/{engine}/chat/completions?api-version={api_version}\n\t\t\tactual := cli.fullURL(c.Suffix, withModel(c.Model))\n\t\t\tif actual != c.Expect {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\", c.Expect, actual)\n\t\t\t}\n\t\t\tt.Logf(\"Full URL: %s\", actual)\n\t\t})\n\t}\n}\n\nfunc TestCloudflareAzureFullURL(t *testing.T) {\n\tcases := []struct {\n\t\tName    string\n\t\tBaseURL string\n\t\tSuffix  string\n\t\tExpect  string\n\t}{\n\t\t{\n\t\t\t\"CloudflareAzureBaseURLWithSlashAutoStrip\",\n\t\t\t\"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo/\",\n\t\t\t\"/chat/completions\",\n\t\t\t\"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo/\" +\n\t\t\t\t\"chat/completions?api-version=2023-05-15\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\t\"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo\",\n\t\t\t\"/assistants?limit=10\",\n\t\t\t\"https://gateway.ai.cloudflare.com/v1/dnekeim2i39dmm4mldemakiem3i4mkw3/demo/azure-openai/resource/chatgpt-demo\" +\n\t\t\t\t\"/assistants?api-version=2023-05-15&limit=10\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.Name, func(t *testing.T) {\n\t\t\taz := DefaultAzureConfig(\"dummy\", c.BaseURL)\n\t\t\taz.APIType = APITypeCloudflareAzure\n\n\t\t\tcli := NewClientWithConfig(az)\n\n\t\t\tactual := cli.fullURL(c.Suffix)\n\t\t\tif actual != c.Expect {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\", c.Expect, actual)\n\t\t\t}\n\t\t\tt.Logf(\"Full URL: %s\", actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "assistant.go",
    "content": "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      = \"/assistants\"\n\tassistantsFilesSuffix = \"/files\"\n)\n\ntype Assistant struct {\n\tID             string                 `json:\"id\"`\n\tObject         string                 `json:\"object\"`\n\tCreatedAt      int64                  `json:\"created_at\"`\n\tName           *string                `json:\"name,omitempty\"`\n\tDescription    *string                `json:\"description,omitempty\"`\n\tModel          string                 `json:\"model\"`\n\tInstructions   *string                `json:\"instructions,omitempty\"`\n\tTools          []AssistantTool        `json:\"tools\"`\n\tToolResources  *AssistantToolResource `json:\"tool_resources,omitempty\"`\n\tFileIDs        []string               `json:\"file_ids,omitempty\"` // Deprecated in v2\n\tMetadata       map[string]any         `json:\"metadata,omitempty\"`\n\tTemperature    *float32               `json:\"temperature,omitempty\"`\n\tTopP           *float32               `json:\"top_p,omitempty\"`\n\tResponseFormat any                    `json:\"response_format,omitempty\"`\n\n\thttpHeader\n}\n\ntype AssistantToolType string\n\nconst (\n\tAssistantToolTypeCodeInterpreter AssistantToolType = \"code_interpreter\"\n\tAssistantToolTypeRetrieval       AssistantToolType = \"retrieval\"\n\tAssistantToolTypeFunction        AssistantToolType = \"function\"\n\tAssistantToolTypeFileSearch      AssistantToolType = \"file_search\"\n)\n\ntype AssistantTool struct {\n\tType     AssistantToolType   `json:\"type\"`\n\tFunction *FunctionDefinition `json:\"function,omitempty\"`\n}\n\ntype AssistantToolFileSearch struct {\n\tVectorStoreIDs []string `json:\"vector_store_ids\"`\n}\n\ntype AssistantToolCodeInterpreter struct {\n\tFileIDs []string `json:\"file_ids\"`\n}\n\ntype AssistantToolResource struct {\n\tFileSearch      *AssistantToolFileSearch      `json:\"file_search,omitempty\"`\n\tCodeInterpreter *AssistantToolCodeInterpreter `json:\"code_interpreter,omitempty\"`\n}\n\n// AssistantRequest provides the assistant request parameters.\n// When modifying the tools the API functions as the following:\n// If Tools is undefined, no changes are made to the Assistant's tools.\n// If Tools is empty slice it will effectively delete all of the Assistant's tools.\n// If Tools is populated, it will replace all of the existing Assistant's tools with the provided tools.\ntype AssistantRequest struct {\n\tModel          string                 `json:\"model\"`\n\tName           *string                `json:\"name,omitempty\"`\n\tDescription    *string                `json:\"description,omitempty\"`\n\tInstructions   *string                `json:\"instructions,omitempty\"`\n\tTools          []AssistantTool        `json:\"-\"`\n\tFileIDs        []string               `json:\"file_ids,omitempty\"`\n\tMetadata       map[string]any         `json:\"metadata,omitempty\"`\n\tToolResources  *AssistantToolResource `json:\"tool_resources,omitempty\"`\n\tResponseFormat any                    `json:\"response_format,omitempty\"`\n\tTemperature    *float32               `json:\"temperature,omitempty\"`\n\tTopP           *float32               `json:\"top_p,omitempty\"`\n}\n\n// MarshalJSON provides a custom marshaller for the assistant request to handle the API use cases\n// If Tools is nil, the field is omitted from the JSON.\n// If Tools is an empty slice, it's included in the JSON as an empty array ([]).\n// If Tools is populated, it's included in the JSON with the elements.\nfunc (a AssistantRequest) MarshalJSON() ([]byte, error) {\n\ttype Alias AssistantRequest\n\tassistantAlias := &struct {\n\t\tTools *[]AssistantTool `json:\"tools,omitempty\"`\n\t\t*Alias\n\t}{\n\t\tAlias: (*Alias)(&a),\n\t}\n\n\tif a.Tools != nil {\n\t\tassistantAlias.Tools = &a.Tools\n\t}\n\n\treturn json.Marshal(assistantAlias)\n}\n\n// AssistantsList is a list of assistants.\ntype AssistantsList struct {\n\tAssistants []Assistant `json:\"data\"`\n\tLastID     *string     `json:\"last_id\"`\n\tFirstID    *string     `json:\"first_id\"`\n\tHasMore    bool        `json:\"has_more\"`\n\thttpHeader\n}\n\ntype AssistantDeleteResponse struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\ntype AssistantFile struct {\n\tID          string `json:\"id\"`\n\tObject      string `json:\"object\"`\n\tCreatedAt   int64  `json:\"created_at\"`\n\tAssistantID string `json:\"assistant_id\"`\n\n\thttpHeader\n}\n\ntype AssistantFileRequest struct {\n\tFileID string `json:\"file_id\"`\n}\n\ntype AssistantFilesList struct {\n\tAssistantFiles []AssistantFile `json:\"data\"`\n\n\thttpHeader\n}\n\n// CreateAssistant creates a new assistant.\nfunc (c *Client) CreateAssistant(ctx context.Context, request AssistantRequest) (response Assistant, err error) {\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(assistantsSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveAssistant retrieves an assistant.\nfunc (c *Client) RetrieveAssistant(\n\tctx context.Context,\n\tassistantID string,\n) (response Assistant, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", assistantsSuffix, assistantID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ModifyAssistant modifies an assistant.\nfunc (c *Client) ModifyAssistant(\n\tctx context.Context,\n\tassistantID string,\n\trequest AssistantRequest,\n) (response Assistant, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", assistantsSuffix, assistantID)\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// DeleteAssistant deletes an assistant.\nfunc (c *Client) DeleteAssistant(\n\tctx context.Context,\n\tassistantID string,\n) (response AssistantDeleteResponse, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", assistantsSuffix, assistantID)\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ListAssistants Lists the currently available assistants.\nfunc (c *Client) ListAssistants(\n\tctx context.Context,\n\tlimit *int,\n\torder *string,\n\tafter *string,\n\tbefore *string,\n) (response AssistantsList, err error) {\n\turlValues := url.Values{}\n\tif limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *limit))\n\t}\n\tif order != nil {\n\t\turlValues.Add(\"order\", *order)\n\t}\n\tif after != nil {\n\t\turlValues.Add(\"after\", *after)\n\t}\n\tif before != nil {\n\t\turlValues.Add(\"before\", *before)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s%s\", assistantsSuffix, encodedValues)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CreateAssistantFile creates a new assistant file.\nfunc (c *Client) CreateAssistantFile(\n\tctx context.Context,\n\tassistantID string,\n\trequest AssistantFileRequest,\n) (response AssistantFile, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s\", assistantsSuffix, assistantID, assistantsFilesSuffix)\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveAssistantFile retrieves an assistant file.\nfunc (c *Client) RetrieveAssistantFile(\n\tctx context.Context,\n\tassistantID string,\n\tfileID string,\n) (response AssistantFile, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s\", assistantsSuffix, assistantID, assistantsFilesSuffix, fileID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// DeleteAssistantFile deletes an existing file.\nfunc (c *Client) DeleteAssistantFile(\n\tctx context.Context,\n\tassistantID string,\n\tfileID string,\n) (err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s\", assistantsSuffix, assistantID, assistantsFilesSuffix, fileID)\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, nil)\n\treturn\n}\n\n// ListAssistantFiles Lists the currently available files for an assistant.\nfunc (c *Client) ListAssistantFiles(\n\tctx context.Context,\n\tassistantID string,\n\tlimit *int,\n\torder *string,\n\tafter *string,\n\tbefore *string,\n) (response AssistantFilesList, err error) {\n\turlValues := url.Values{}\n\tif limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *limit))\n\t}\n\tif order != nil {\n\t\turlValues.Add(\"order\", *order)\n\t}\n\tif after != nil {\n\t\turlValues.Add(\"after\", *after)\n\t}\n\tif before != nil {\n\t\turlValues.Add(\"before\", *before)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s/%s%s%s\", assistantsSuffix, assistantID, assistantsFilesSuffix, encodedValues)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "assistant_test.go",
    "content": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n)\n\n// TestAssistant Tests the assistant endpoint of the API using the mocked server.\nfunc TestAssistant(t *testing.T) {\n\tassistantID := \"asst_abc123\"\n\tassistantName := \"Ambrogio\"\n\tassistantDescription := \"Ambrogio is a friendly assistant.\"\n\tassistantInstructions := `You are a personal math tutor. \nWhen asked a question, write and run Python code to answer the question.`\n\tassistantFileID := \"file-wB6RM6wHdA49HfS2DJ9fEyrH\"\n\tlimit := 20\n\torder := \"desc\"\n\tafter := \"asst_abc122\"\n\tbefore := \"asst_abc124\"\n\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/v1/assistants/\"+assistantID+\"/files/\"+assistantFileID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFile{\n\t\t\t\t\tID:          assistantFileID,\n\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodDelete {\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\tid: \"file-wB6RM6wHdA49HfS2DJ9fEyrH\",\n\t\t\t\t\tobject: \"assistant.file.deleted\",\n\t\t\t\t\tdeleted: true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/assistants/\"+assistantID+\"/files\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFilesList{\n\t\t\t\t\tAssistantFiles: []openai.AssistantFile{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:          assistantFileID,\n\t\t\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodPost {\n\t\t\t\tvar request openai.AssistantFileRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFile{\n\t\t\t\t\tID:          request.FileID,\n\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/assistants/\"+assistantID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         &assistantName,\n\t\t\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\t\t\tDescription:  &assistantDescription,\n\t\t\t\t\tInstructions: &assistantInstructions,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodPost:\n\t\t\t\tvar request openai.Assistant\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         request.Name,\n\t\t\t\t\tModel:        request.Model,\n\t\t\t\t\tDescription:  request.Description,\n\t\t\t\t\tInstructions: request.Instructions,\n\t\t\t\t\tTools:        request.Tools,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\t\"id\": \"asst_abc123\",\n\t\t\t\t\t\"object\": \"assistant.deleted\",\n\t\t\t\t\t\"deleted\": true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/assistants\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.AssistantRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         request.Name,\n\t\t\t\t\tModel:        request.Model,\n\t\t\t\t\tDescription:  request.Description,\n\t\t\t\t\tInstructions: request.Instructions,\n\t\t\t\t\tTools:        request.Tools,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantsList{\n\t\t\t\t\tLastID:  &assistantID,\n\t\t\t\t\tFirstID: &assistantID,\n\t\t\t\t\tAssistants: []openai.Assistant{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:           assistantID,\n\t\t\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\t\t\tName:         &assistantName,\n\t\t\t\t\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\t\t\t\t\tDescription:  &assistantDescription,\n\t\t\t\t\t\t\tInstructions: &assistantInstructions,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\tt.Run(\"create_assistant\", func(t *testing.T) {\n\t\t_, err := client.CreateAssistant(ctx, openai.AssistantRequest{\n\t\t\tName:         &assistantName,\n\t\t\tDescription:  &assistantDescription,\n\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\tInstructions: &assistantInstructions,\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateAssistant error\")\n\t})\n\n\tt.Run(\"retrieve_assistant\", func(t *testing.T) {\n\t\t_, err := client.RetrieveAssistant(ctx, assistantID)\n\t\tchecks.NoError(t, err, \"RetrieveAssistant error\")\n\t})\n\n\tt.Run(\"delete_assistant\", func(t *testing.T) {\n\t\t_, err := client.DeleteAssistant(ctx, assistantID)\n\t\tchecks.NoError(t, err, \"DeleteAssistant error\")\n\t})\n\n\tt.Run(\"list_assistant\", func(t *testing.T) {\n\t\t_, err := client.ListAssistants(ctx, &limit, &order, &after, &before)\n\t\tchecks.NoError(t, err, \"ListAssistants error\")\n\t})\n\n\tt.Run(\"create_assistant_file\", func(t *testing.T) {\n\t\t_, err := client.CreateAssistantFile(ctx, assistantID, openai.AssistantFileRequest{\n\t\t\tFileID: assistantFileID,\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateAssistantFile error\")\n\t})\n\n\tt.Run(\"list_assistant_files\", func(t *testing.T) {\n\t\t_, err := client.ListAssistantFiles(ctx, assistantID, &limit, &order, &after, &before)\n\t\tchecks.NoError(t, err, \"ListAssistantFiles error\")\n\t})\n\n\tt.Run(\"retrieve_assistant_file\", func(t *testing.T) {\n\t\t_, err := client.RetrieveAssistantFile(ctx, assistantID, assistantFileID)\n\t\tchecks.NoError(t, err, \"RetrieveAssistantFile error\")\n\t})\n\n\tt.Run(\"delete_assistant_file\", func(t *testing.T) {\n\t\terr := client.DeleteAssistantFile(ctx, assistantID, assistantFileID)\n\t\tchecks.NoError(t, err, \"DeleteAssistantFile error\")\n\t})\n\n\tt.Run(\"modify_assistant_no_tools\", func(t *testing.T) {\n\t\tassistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{\n\t\t\tName:         &assistantName,\n\t\t\tDescription:  &assistantDescription,\n\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\tInstructions: &assistantInstructions,\n\t\t})\n\t\tchecks.NoError(t, err, \"ModifyAssistant error\")\n\n\t\tif assistant.Tools != nil {\n\t\t\tt.Errorf(\"expected nil got %v\", assistant.Tools)\n\t\t}\n\t})\n\n\tt.Run(\"modify_assistant_with_tools\", func(t *testing.T) {\n\t\tassistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{\n\t\t\tName:         &assistantName,\n\t\t\tDescription:  &assistantDescription,\n\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\tInstructions: &assistantInstructions,\n\t\t\tTools:        []openai.AssistantTool{{Type: openai.AssistantToolTypeFunction}},\n\t\t})\n\t\tchecks.NoError(t, err, \"ModifyAssistant error\")\n\n\t\tif assistant.Tools == nil || len(assistant.Tools) != 1 {\n\t\t\tt.Errorf(\"expected a slice got %v\", assistant.Tools)\n\t\t}\n\t})\n\n\tt.Run(\"modify_assistant_empty_tools\", func(t *testing.T) {\n\t\tassistant, err := client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{\n\t\t\tName:         &assistantName,\n\t\t\tDescription:  &assistantDescription,\n\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\tInstructions: &assistantInstructions,\n\t\t\tTools:        make([]openai.AssistantTool, 0),\n\t\t})\n\n\t\tchecks.NoError(t, err, \"ModifyAssistant error\")\n\n\t\tif assistant.Tools == nil {\n\t\t\tt.Errorf(\"expected a slice got %v\", assistant.Tools)\n\t\t}\n\t})\n}\n\nfunc TestAzureAssistant(t *testing.T) {\n\tassistantID := \"asst_abc123\"\n\tassistantName := \"Ambrogio\"\n\tassistantDescription := \"Ambrogio is a friendly assistant.\"\n\tassistantInstructions := `You are a personal math tutor. \nWhen asked a question, write and run Python code to answer the question.`\n\tassistantFileID := \"file-wB6RM6wHdA49HfS2DJ9fEyrH\"\n\tlimit := 20\n\torder := \"desc\"\n\tafter := \"asst_abc122\"\n\tbefore := \"asst_abc124\"\n\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/openai/assistants/\"+assistantID+\"/files/\"+assistantFileID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFile{\n\t\t\t\t\tID:          assistantFileID,\n\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodDelete {\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\tid: \"file-wB6RM6wHdA49HfS2DJ9fEyrH\",\n\t\t\t\t\tobject: \"assistant.file.deleted\",\n\t\t\t\t\tdeleted: true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/openai/assistants/\"+assistantID+\"/files\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFilesList{\n\t\t\t\t\tAssistantFiles: []openai.AssistantFile{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:          assistantFileID,\n\t\t\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodPost {\n\t\t\t\tvar request openai.AssistantFileRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantFile{\n\t\t\t\t\tID:          request.FileID,\n\t\t\t\t\tObject:      \"assistant.file\",\n\t\t\t\t\tCreatedAt:   1234567890,\n\t\t\t\t\tAssistantID: assistantID,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/openai/assistants/\"+assistantID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         &assistantName,\n\t\t\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\t\t\tDescription:  &assistantDescription,\n\t\t\t\t\tInstructions: &assistantInstructions,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodPost:\n\t\t\t\tvar request openai.AssistantRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         request.Name,\n\t\t\t\t\tModel:        request.Model,\n\t\t\t\t\tDescription:  request.Description,\n\t\t\t\t\tInstructions: request.Instructions,\n\t\t\t\t\tTools:        request.Tools,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\t\"id\": \"asst_abc123\",\n\t\t\t\t\t\"object\": \"assistant.deleted\",\n\t\t\t\t\t\"deleted\": true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/openai/assistants\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.AssistantRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Assistant{\n\t\t\t\t\tID:           assistantID,\n\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\tName:         request.Name,\n\t\t\t\t\tModel:        request.Model,\n\t\t\t\t\tDescription:  request.Description,\n\t\t\t\t\tInstructions: request.Instructions,\n\t\t\t\t\tTools:        request.Tools,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.AssistantsList{\n\t\t\t\t\tLastID:  &assistantID,\n\t\t\t\t\tFirstID: &assistantID,\n\t\t\t\t\tAssistants: []openai.Assistant{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:           assistantID,\n\t\t\t\t\t\t\tObject:       \"assistant\",\n\t\t\t\t\t\t\tCreatedAt:    1234567890,\n\t\t\t\t\t\t\tName:         &assistantName,\n\t\t\t\t\t\t\tModel:        openai.GPT4TurboPreview,\n\t\t\t\t\t\t\tDescription:  &assistantDescription,\n\t\t\t\t\t\t\tInstructions: &assistantInstructions,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.CreateAssistant(ctx, openai.AssistantRequest{\n\t\tName:         &assistantName,\n\t\tDescription:  &assistantDescription,\n\t\tModel:        openai.GPT4TurboPreview,\n\t\tInstructions: &assistantInstructions,\n\t})\n\tchecks.NoError(t, err, \"CreateAssistant error\")\n\n\t_, err = client.RetrieveAssistant(ctx, assistantID)\n\tchecks.NoError(t, err, \"RetrieveAssistant error\")\n\n\t_, err = client.ModifyAssistant(ctx, assistantID, openai.AssistantRequest{\n\t\tName:         &assistantName,\n\t\tDescription:  &assistantDescription,\n\t\tModel:        openai.GPT4TurboPreview,\n\t\tInstructions: &assistantInstructions,\n\t})\n\tchecks.NoError(t, err, \"ModifyAssistant error\")\n\n\t_, err = client.DeleteAssistant(ctx, assistantID)\n\tchecks.NoError(t, err, \"DeleteAssistant error\")\n\n\t_, err = client.ListAssistants(ctx, &limit, &order, &after, &before)\n\tchecks.NoError(t, err, \"ListAssistants error\")\n\n\t_, err = client.CreateAssistantFile(ctx, assistantID, openai.AssistantFileRequest{\n\t\tFileID: assistantFileID,\n\t})\n\tchecks.NoError(t, err, \"CreateAssistantFile error\")\n\n\t_, err = client.ListAssistantFiles(ctx, assistantID, &limit, &order, &after, &before)\n\tchecks.NoError(t, err, \"ListAssistantFiles error\")\n\n\t_, err = client.RetrieveAssistantFile(ctx, assistantID, assistantFileID)\n\tchecks.NoError(t, err, \"RetrieveAssistantFile error\")\n\n\terr = client.DeleteAssistantFile(ctx, assistantID, assistantFileID)\n\tchecks.NoError(t, err, \"DeleteAssistantFile error\")\n}\n"
  },
  {
    "path": "audio.go",
    "content": "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/internal\"\n)\n\n// Whisper Defines the models provided by OpenAI to use when processing audio with OpenAI.\nconst (\n\tWhisper1 = \"whisper-1\"\n)\n\n// Response formats; Whisper uses AudioResponseFormatJSON by default.\ntype AudioResponseFormat string\n\nconst (\n\tAudioResponseFormatJSON        AudioResponseFormat = \"json\"\n\tAudioResponseFormatText        AudioResponseFormat = \"text\"\n\tAudioResponseFormatSRT         AudioResponseFormat = \"srt\"\n\tAudioResponseFormatVerboseJSON AudioResponseFormat = \"verbose_json\"\n\tAudioResponseFormatVTT         AudioResponseFormat = \"vtt\"\n)\n\ntype TranscriptionTimestampGranularity string\n\nconst (\n\tTranscriptionTimestampGranularityWord    TranscriptionTimestampGranularity = \"word\"\n\tTranscriptionTimestampGranularitySegment TranscriptionTimestampGranularity = \"segment\"\n)\n\n// AudioRequest represents a request structure for audio API.\ntype AudioRequest struct {\n\tModel string\n\n\t// FilePath is either an existing file in your filesystem or a filename representing the contents of Reader.\n\tFilePath string\n\n\t// Reader is an optional io.Reader when you do not want to use an existing file.\n\tReader io.Reader\n\n\tPrompt                 string\n\tTemperature            float32\n\tLanguage               string // Only for transcription.\n\tFormat                 AudioResponseFormat\n\tTimestampGranularities []TranscriptionTimestampGranularity // Only for transcription.\n}\n\n// AudioResponse represents a response structure for audio API.\ntype AudioResponse struct {\n\tTask     string  `json:\"task\"`\n\tLanguage string  `json:\"language\"`\n\tDuration float64 `json:\"duration\"`\n\tSegments []struct {\n\t\tID               int     `json:\"id\"`\n\t\tSeek             int     `json:\"seek\"`\n\t\tStart            float64 `json:\"start\"`\n\t\tEnd              float64 `json:\"end\"`\n\t\tText             string  `json:\"text\"`\n\t\tTokens           []int   `json:\"tokens\"`\n\t\tTemperature      float64 `json:\"temperature\"`\n\t\tAvgLogprob       float64 `json:\"avg_logprob\"`\n\t\tCompressionRatio float64 `json:\"compression_ratio\"`\n\t\tNoSpeechProb     float64 `json:\"no_speech_prob\"`\n\t\tTransient        bool    `json:\"transient\"`\n\t} `json:\"segments\"`\n\tWords []struct {\n\t\tWord  string  `json:\"word\"`\n\t\tStart float64 `json:\"start\"`\n\t\tEnd   float64 `json:\"end\"`\n\t} `json:\"words\"`\n\tText string `json:\"text\"`\n\n\thttpHeader\n}\n\ntype audioTextResponse struct {\n\tText string `json:\"text\"`\n\n\thttpHeader\n}\n\nfunc (r *audioTextResponse) ToAudioResponse() AudioResponse {\n\treturn AudioResponse{\n\t\tText:       r.Text,\n\t\thttpHeader: r.httpHeader,\n\t}\n}\n\n// CreateTranscription — API call to create a transcription. Returns transcribed text.\nfunc (c *Client) CreateTranscription(\n\tctx context.Context,\n\trequest AudioRequest,\n) (response AudioResponse, err error) {\n\treturn c.callAudioAPI(ctx, request, \"transcriptions\")\n}\n\n// CreateTranslation — API call to translate audio into English.\nfunc (c *Client) CreateTranslation(\n\tctx context.Context,\n\trequest AudioRequest,\n) (response AudioResponse, err error) {\n\treturn c.callAudioAPI(ctx, request, \"translations\")\n}\n\n// callAudioAPI — API call to an audio endpoint.\nfunc (c *Client) callAudioAPI(\n\tctx context.Context,\n\trequest AudioRequest,\n\tendpointSuffix string,\n) (response AudioResponse, err error) {\n\tvar formBody bytes.Buffer\n\tbuilder := c.createFormBuilder(&formBody)\n\n\tif err = audioMultipartForm(request, builder); err != nil {\n\t\treturn AudioResponse{}, err\n\t}\n\n\turlSuffix := fmt.Sprintf(\"/audio/%s\", endpointSuffix)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(&formBody),\n\t\twithContentType(builder.FormDataContentType()),\n\t)\n\tif err != nil {\n\t\treturn AudioResponse{}, err\n\t}\n\n\tif request.HasJSONResponse() {\n\t\terr = c.sendRequest(req, &response)\n\t} else {\n\t\tvar textResponse audioTextResponse\n\t\terr = c.sendRequest(req, &textResponse)\n\t\tresponse = textResponse.ToAudioResponse()\n\t}\n\tif err != nil {\n\t\treturn AudioResponse{}, err\n\t}\n\treturn\n}\n\n// HasJSONResponse returns true if the response format is JSON.\nfunc (r AudioRequest) HasJSONResponse() bool {\n\treturn r.Format == \"\" || r.Format == AudioResponseFormatJSON || r.Format == AudioResponseFormatVerboseJSON\n}\n\n// audioMultipartForm creates a form with audio file contents and the name of the model to use for\n// audio processing.\nfunc audioMultipartForm(request AudioRequest, b utils.FormBuilder) error {\n\terr := createFileField(request, b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = b.WriteField(\"model\", request.Model)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"writing model name: %w\", err)\n\t}\n\n\t// Create a form field for the prompt (if provided)\n\tif request.Prompt != \"\" {\n\t\terr = b.WriteField(\"prompt\", request.Prompt)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"writing prompt: %w\", err)\n\t\t}\n\t}\n\n\t// Create a form field for the format (if provided)\n\tif request.Format != \"\" {\n\t\terr = b.WriteField(\"response_format\", string(request.Format))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"writing format: %w\", err)\n\t\t}\n\t}\n\n\t// Create a form field for the temperature (if provided)\n\tif request.Temperature != 0 {\n\t\terr = b.WriteField(\"temperature\", fmt.Sprintf(\"%.2f\", request.Temperature))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"writing temperature: %w\", err)\n\t\t}\n\t}\n\n\t// Create a form field for the language (if provided)\n\tif request.Language != \"\" {\n\t\terr = b.WriteField(\"language\", request.Language)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"writing language: %w\", err)\n\t\t}\n\t}\n\n\tif len(request.TimestampGranularities) > 0 {\n\t\tfor _, tg := range request.TimestampGranularities {\n\t\t\terr = b.WriteField(\"timestamp_granularities[]\", string(tg))\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"writing timestamp_granularities[]: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Close the multipart writer\n\treturn b.Close()\n}\n\n// createFileField creates the \"file\" form field from either an existing file or by using the reader.\nfunc createFileField(request AudioRequest, b utils.FormBuilder) error {\n\tif request.Reader != nil {\n\t\terr := b.CreateFormFileReader(\"file\", request.Reader, request.FilePath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"creating form using reader: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tf, err := os.Open(request.FilePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"opening audio file: %w\", err)\n\t}\n\tdefer f.Close()\n\n\terr = b.CreateFormFile(\"file\", f)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating form file: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "audio_api_test.go",
    "content": "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\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\n// TestAudio Tests the transcription and translation endpoints of the API using the mocked server.\nfunc TestAudio(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/audio/transcriptions\", handleAudioEndpoint)\n\tserver.RegisterHandler(\"/v1/audio/translations\", handleAudioEndpoint)\n\n\ttestcases := []struct {\n\t\tname     string\n\t\tcreateFn func(context.Context, openai.AudioRequest) (openai.AudioResponse, error)\n\t}{\n\t\t{\n\t\t\t\"transcribe\",\n\t\t\tclient.CreateTranscription,\n\t\t},\n\t\t{\n\t\t\t\"translate\",\n\t\t\tclient.CreateTranslation,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tpath := filepath.Join(t.TempDir(), \"fake.mp3\")\n\t\t\ttest.CreateTestFile(t, path)\n\n\t\t\treq := openai.AudioRequest{\n\t\t\t\tFilePath: path,\n\t\t\t\tModel:    \"whisper-3\",\n\t\t\t}\n\t\t\t_, err := tc.createFn(ctx, req)\n\t\t\tchecks.NoError(t, err, \"audio API error\")\n\t\t})\n\n\t\tt.Run(tc.name+\" (with reader)\", func(t *testing.T) {\n\t\t\treq := openai.AudioRequest{\n\t\t\t\tFilePath: \"fake.webm\",\n\t\t\t\tReader:   bytes.NewBuffer([]byte(`some webm binary data`)),\n\t\t\t\tModel:    \"whisper-3\",\n\t\t\t}\n\t\t\t_, err := tc.createFn(ctx, req)\n\t\t\tchecks.NoError(t, err, \"audio API error\")\n\t\t})\n\t}\n}\n\nfunc TestAudioWithOptionalArgs(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/audio/transcriptions\", handleAudioEndpoint)\n\tserver.RegisterHandler(\"/v1/audio/translations\", handleAudioEndpoint)\n\n\ttestcases := []struct {\n\t\tname     string\n\t\tcreateFn func(context.Context, openai.AudioRequest) (openai.AudioResponse, error)\n\t}{\n\t\t{\n\t\t\t\"transcribe\",\n\t\t\tclient.CreateTranscription,\n\t\t},\n\t\t{\n\t\t\t\"translate\",\n\t\t\tclient.CreateTranslation,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tpath := filepath.Join(t.TempDir(), \"fake.mp3\")\n\t\t\ttest.CreateTestFile(t, path)\n\n\t\t\treq := openai.AudioRequest{\n\t\t\t\tFilePath:    path,\n\t\t\t\tModel:       \"whisper-3\",\n\t\t\t\tPrompt:      \"用简体中文\",\n\t\t\t\tTemperature: 0.5,\n\t\t\t\tLanguage:    \"zh\",\n\t\t\t\tFormat:      openai.AudioResponseFormatSRT,\n\t\t\t\tTimestampGranularities: []openai.TranscriptionTimestampGranularity{\n\t\t\t\t\topenai.TranscriptionTimestampGranularitySegment,\n\t\t\t\t\topenai.TranscriptionTimestampGranularityWord,\n\t\t\t\t},\n\t\t\t}\n\t\t\t_, err := tc.createFn(ctx, req)\n\t\t\tchecks.NoError(t, err, \"audio API error\")\n\t\t})\n\t}\n}\n\n// handleAudioEndpoint Handles the completion endpoint by the test server.\nfunc handleAudioEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\n\t// audio endpoints only accept POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\n\tmediaType, params, err := mime.ParseMediaType(r.Header.Get(\"Content-Type\"))\n\tif err != nil {\n\t\thttp.Error(w, \"failed to parse media type\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif !strings.HasPrefix(mediaType, \"multipart\") {\n\t\thttp.Error(w, \"request is not multipart\", http.StatusBadRequest)\n\t}\n\n\tboundary, ok := params[\"boundary\"]\n\tif !ok {\n\t\thttp.Error(w, \"no boundary in params\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tfileData := &bytes.Buffer{}\n\tmr := multipart.NewReader(r.Body, boundary)\n\tpart, err := mr.NextPart()\n\tif err != nil && errors.Is(err, io.EOF) {\n\t\thttp.Error(w, \"error accessing file\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif _, err = io.Copy(fileData, part); err != nil {\n\t\thttp.Error(w, \"failed to copy file\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif len(fileData.Bytes()) == 0 {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\thttp.Error(w, \"received empty file data\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif _, err = w.Write([]byte(`{\"body\": \"hello\"}`)); err != nil {\n\t\thttp.Error(w, \"failed to write body\", http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "audio_test.go",
    "content": "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/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tutils \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestAudioWithFailingFormBuilder(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"fake.mp3\")\n\ttest.CreateTestFile(t, path)\n\n\treq := AudioRequest{\n\t\tFilePath:    path,\n\t\tPrompt:      \"test\",\n\t\tTemperature: 0.5,\n\t\tLanguage:    \"en\",\n\t\tFormat:      AudioResponseFormatSRT,\n\t\tTimestampGranularities: []TranscriptionTimestampGranularity{\n\t\t\tTranscriptionTimestampGranularitySegment,\n\t\t\tTranscriptionTimestampGranularityWord,\n\t\t},\n\t}\n\n\tmockFailedErr := fmt.Errorf(\"mock form builder fail\")\n\tmockBuilder := &mockFormBuilder{}\n\n\tmockBuilder.mockCreateFormFile = func(string, *os.File) error {\n\t\treturn mockFailedErr\n\t}\n\terr := audioMultipartForm(req, mockBuilder)\n\tchecks.ErrorIs(t, err, mockFailedErr, \"audioMultipartForm should return error if form builder fails\")\n\n\tmockBuilder.mockCreateFormFile = func(string, *os.File) error {\n\t\treturn nil\n\t}\n\n\tvar failForField string\n\tmockBuilder.mockWriteField = func(fieldname, _ string) error {\n\t\tif fieldname == failForField {\n\t\t\treturn mockFailedErr\n\t\t}\n\t\treturn nil\n\t}\n\n\tfailOn := []string{\"model\", \"prompt\", \"temperature\", \"language\", \"response_format\", \"timestamp_granularities[]\"}\n\tfor _, failingField := range failOn {\n\t\tfailForField = failingField\n\t\tmockFailedErr = fmt.Errorf(\"mock form builder fail on field %s\", failingField)\n\n\t\terr = audioMultipartForm(req, mockBuilder)\n\t\tchecks.ErrorIs(t, err, mockFailedErr, \"audioMultipartForm should return error if form builder fails\")\n\t}\n}\n\nfunc TestCreateFileField(t *testing.T) {\n\tt.Run(\"createFileField failing file\", func(t *testing.T) {\n\t\tpath := filepath.Join(t.TempDir(), \"fake.mp3\")\n\t\ttest.CreateTestFile(t, path)\n\n\t\treq := AudioRequest{\n\t\t\tFilePath: path,\n\t\t}\n\n\t\tmockFailedErr := fmt.Errorf(\"mock form builder fail\")\n\t\tmockBuilder := &mockFormBuilder{\n\t\t\tmockCreateFormFile: func(string, *os.File) error {\n\t\t\t\treturn mockFailedErr\n\t\t\t},\n\t\t}\n\n\t\terr := createFileField(req, mockBuilder)\n\t\tchecks.ErrorIs(t, err, mockFailedErr, \"createFileField using a file should return error if form builder fails\")\n\t})\n\n\tt.Run(\"createFileField failing reader\", func(t *testing.T) {\n\t\treq := AudioRequest{\n\t\t\tFilePath: \"test.wav\",\n\t\t\tReader:   bytes.NewBuffer([]byte(`wav test contents`)),\n\t\t}\n\n\t\tmockFailedErr := fmt.Errorf(\"mock form builder fail\")\n\t\tmockBuilder := &mockFormBuilder{\n\t\t\tmockCreateFormFileReader: func(string, io.Reader, string) error {\n\t\t\t\treturn mockFailedErr\n\t\t\t},\n\t\t}\n\n\t\terr := createFileField(req, mockBuilder)\n\t\tchecks.ErrorIs(t, err, mockFailedErr, \"createFileField using a reader should return error if form builder fails\")\n\t})\n\n\tt.Run(\"createFileField failing open\", func(t *testing.T) {\n\t\treq := AudioRequest{\n\t\t\tFilePath: \"non_existing_file.wav\",\n\t\t}\n\n\t\tmockBuilder := &mockFormBuilder{}\n\n\t\terr := createFileField(req, mockBuilder)\n\t\tchecks.HasError(t, err, \"createFileField using file should return error when open file fails\")\n\t})\n}\n\n// failingFormBuilder always returns an error when creating form files.\ntype failingFormBuilder struct{ err error }\n\nfunc (f *failingFormBuilder) CreateFormFile(_ string, _ *os.File) error {\n\treturn f.err\n}\n\nfunc (f *failingFormBuilder) CreateFormFileReader(_ string, _ io.Reader, _ string) error {\n\treturn f.err\n}\n\nfunc (f *failingFormBuilder) WriteField(_, _ string) error {\n\treturn nil\n}\n\nfunc (f *failingFormBuilder) Close() error {\n\treturn nil\n}\n\nfunc (f *failingFormBuilder) FormDataContentType() string {\n\treturn \"multipart/form-data\"\n}\n\n// failingAudioRequestBuilder simulates an error during HTTP request construction.\ntype failingAudioRequestBuilder struct{ err error }\n\nfunc (f *failingAudioRequestBuilder) Build(\n\t_ context.Context,\n\t_, _ string,\n\t_ any,\n\t_ http.Header,\n) (*http.Request, error) {\n\treturn nil, f.err\n}\n\n// errorHTTPClient always returns an error when making HTTP calls.\ntype errorHTTPClient struct{ err error }\n\nfunc (e *errorHTTPClient) Do(_ *http.Request) (*http.Response, error) {\n\treturn nil, e.err\n}\n\nfunc TestCallAudioAPIMultipartFormError(t *testing.T) {\n\tclient := NewClient(\"test-token\")\n\terrForm := errors.New(\"mock create form file failure\")\n\t// Override form builder to force an error during multipart form creation.\n\tclient.createFormBuilder = func(_ io.Writer) utils.FormBuilder {\n\t\treturn &failingFormBuilder{err: errForm}\n\t}\n\n\t// Provide a reader so createFileField uses the reader path (no file open).\n\treq := AudioRequest{FilePath: \"fake.mp3\", Reader: bytes.NewBuffer([]byte(\"dummy\")), Model: Whisper1}\n\t_, err := client.callAudioAPI(context.Background(), req, \"transcriptions\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error but got none\")\n\t}\n\tif !errors.Is(err, errForm) {\n\t\tt.Errorf(\"expected error %v, got %v\", errForm, err)\n\t}\n}\n\nfunc TestCallAudioAPINewRequestError(t *testing.T) {\n\tclient := NewClient(\"test-token\")\n\t// Create a real temp file so multipart form succeeds.\n\ttmp := t.TempDir()\n\tpath := filepath.Join(tmp, \"file.mp3\")\n\tif err := os.WriteFile(path, []byte(\"content\"), 0644); err != nil {\n\t\tt.Fatalf(\"failed to write temp file: %v\", err)\n\t}\n\n\terrBuild := errors.New(\"mock build failure\")\n\tclient.requestBuilder = &failingAudioRequestBuilder{err: errBuild}\n\n\treq := AudioRequest{FilePath: path, Model: Whisper1}\n\t_, err := client.callAudioAPI(context.Background(), req, \"translations\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error but got none\")\n\t}\n\tif !errors.Is(err, errBuild) {\n\t\tt.Errorf(\"expected error %v, got %v\", errBuild, err)\n\t}\n}\n\nfunc TestCallAudioAPISendRequestErrorJSON(t *testing.T) {\n\tclient := NewClient(\"test-token\")\n\t// Create a real temp file so multipart form succeeds.\n\ttmp := t.TempDir()\n\tpath := filepath.Join(tmp, \"file.mp3\")\n\tif err := os.WriteFile(path, []byte(\"content\"), 0644); err != nil {\n\t\tt.Fatalf(\"failed to write temp file: %v\", err)\n\t}\n\n\terrHTTP := errors.New(\"mock HTTPClient failure\")\n\t// Override HTTP client to simulate a network error.\n\tclient.config.HTTPClient = &errorHTTPClient{err: errHTTP}\n\n\treq := AudioRequest{FilePath: path, Model: Whisper1}\n\t_, err := client.callAudioAPI(context.Background(), req, \"transcriptions\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error but got none\")\n\t}\n\tif !errors.Is(err, errHTTP) {\n\t\tt.Errorf(\"expected error %v, got %v\", errHTTP, err)\n\t}\n}\n\nfunc TestCallAudioAPISendRequestErrorText(t *testing.T) {\n\tclient := NewClient(\"test-token\")\n\ttmp := t.TempDir()\n\tpath := filepath.Join(tmp, \"file.mp3\")\n\tif err := os.WriteFile(path, []byte(\"content\"), 0644); err != nil {\n\t\tt.Fatalf(\"failed to write temp file: %v\", err)\n\t}\n\n\terrHTTP := errors.New(\"mock HTTPClient failure\")\n\tclient.config.HTTPClient = &errorHTTPClient{err: errHTTP}\n\n\t// Use a non-JSON response format to exercise the text path.\n\treq := AudioRequest{FilePath: path, Model: Whisper1, Format: AudioResponseFormatText}\n\t_, err := client.callAudioAPI(context.Background(), req, \"translations\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error but got none\")\n\t}\n\tif !errors.Is(err, errHTTP) {\n\t\tt.Errorf(\"expected error %v, got %v\", errHTTP, err)\n\t}\n}\n"
  },
  {
    "path": "batch.go",
    "content": "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 = \"/batches\"\n\ntype BatchEndpoint string\n\nconst (\n\tBatchEndpointChatCompletions BatchEndpoint = \"/v1/chat/completions\"\n\tBatchEndpointCompletions     BatchEndpoint = \"/v1/completions\"\n\tBatchEndpointEmbeddings      BatchEndpoint = \"/v1/embeddings\"\n)\n\ntype BatchLineItem interface {\n\tMarshalBatchLineItem() []byte\n}\n\ntype BatchChatCompletionRequest struct {\n\tCustomID string                `json:\"custom_id\"`\n\tBody     ChatCompletionRequest `json:\"body\"`\n\tMethod   string                `json:\"method\"`\n\tURL      BatchEndpoint         `json:\"url\"`\n}\n\nfunc (r BatchChatCompletionRequest) MarshalBatchLineItem() []byte {\n\tmarshal, _ := json.Marshal(r)\n\treturn marshal\n}\n\ntype BatchCompletionRequest struct {\n\tCustomID string            `json:\"custom_id\"`\n\tBody     CompletionRequest `json:\"body\"`\n\tMethod   string            `json:\"method\"`\n\tURL      BatchEndpoint     `json:\"url\"`\n}\n\nfunc (r BatchCompletionRequest) MarshalBatchLineItem() []byte {\n\tmarshal, _ := json.Marshal(r)\n\treturn marshal\n}\n\ntype BatchEmbeddingRequest struct {\n\tCustomID string           `json:\"custom_id\"`\n\tBody     EmbeddingRequest `json:\"body\"`\n\tMethod   string           `json:\"method\"`\n\tURL      BatchEndpoint    `json:\"url\"`\n}\n\nfunc (r BatchEmbeddingRequest) MarshalBatchLineItem() []byte {\n\tmarshal, _ := json.Marshal(r)\n\treturn marshal\n}\n\ntype Batch struct {\n\tID       string        `json:\"id\"`\n\tObject   string        `json:\"object\"`\n\tEndpoint BatchEndpoint `json:\"endpoint\"`\n\tErrors   *struct {\n\t\tObject string `json:\"object,omitempty\"`\n\t\tData   []struct {\n\t\t\tCode    string  `json:\"code,omitempty\"`\n\t\t\tMessage string  `json:\"message,omitempty\"`\n\t\t\tParam   *string `json:\"param,omitempty\"`\n\t\t\tLine    *int    `json:\"line,omitempty\"`\n\t\t} `json:\"data\"`\n\t} `json:\"errors\"`\n\tInputFileID      string             `json:\"input_file_id\"`\n\tCompletionWindow string             `json:\"completion_window\"`\n\tStatus           string             `json:\"status\"`\n\tOutputFileID     *string            `json:\"output_file_id\"`\n\tErrorFileID      *string            `json:\"error_file_id\"`\n\tCreatedAt        int                `json:\"created_at\"`\n\tInProgressAt     *int               `json:\"in_progress_at\"`\n\tExpiresAt        *int               `json:\"expires_at\"`\n\tFinalizingAt     *int               `json:\"finalizing_at\"`\n\tCompletedAt      *int               `json:\"completed_at\"`\n\tFailedAt         *int               `json:\"failed_at\"`\n\tExpiredAt        *int               `json:\"expired_at\"`\n\tCancellingAt     *int               `json:\"cancelling_at\"`\n\tCancelledAt      *int               `json:\"cancelled_at\"`\n\tRequestCounts    BatchRequestCounts `json:\"request_counts\"`\n\tMetadata         map[string]any     `json:\"metadata\"`\n}\n\ntype BatchRequestCounts struct {\n\tTotal     int `json:\"total\"`\n\tCompleted int `json:\"completed\"`\n\tFailed    int `json:\"failed\"`\n}\n\ntype CreateBatchRequest struct {\n\tInputFileID      string         `json:\"input_file_id\"`\n\tEndpoint         BatchEndpoint  `json:\"endpoint\"`\n\tCompletionWindow string         `json:\"completion_window\"`\n\tMetadata         map[string]any `json:\"metadata\"`\n}\n\ntype BatchResponse struct {\n\thttpHeader\n\tBatch\n}\n\n// CreateBatch — API call to Create batch.\nfunc (c *Client) CreateBatch(\n\tctx context.Context,\n\trequest CreateBatchRequest,\n) (response BatchResponse, err error) {\n\tif request.CompletionWindow == \"\" {\n\t\trequest.CompletionWindow = \"24h\"\n\t}\n\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(batchesSuffix), withBody(request))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\ntype UploadBatchFileRequest struct {\n\tFileName string\n\tLines    []BatchLineItem\n}\n\nfunc (r *UploadBatchFileRequest) MarshalJSONL() []byte {\n\tbuff := bytes.Buffer{}\n\tfor i, line := range r.Lines {\n\t\tif i != 0 {\n\t\t\tbuff.Write([]byte(\"\\n\"))\n\t\t}\n\t\tbuff.Write(line.MarshalBatchLineItem())\n\t}\n\treturn buff.Bytes()\n}\n\nfunc (r *UploadBatchFileRequest) AddChatCompletion(customerID string, body ChatCompletionRequest) {\n\tr.Lines = append(r.Lines, BatchChatCompletionRequest{\n\t\tCustomID: customerID,\n\t\tBody:     body,\n\t\tMethod:   \"POST\",\n\t\tURL:      BatchEndpointChatCompletions,\n\t})\n}\n\nfunc (r *UploadBatchFileRequest) AddCompletion(customerID string, body CompletionRequest) {\n\tr.Lines = append(r.Lines, BatchCompletionRequest{\n\t\tCustomID: customerID,\n\t\tBody:     body,\n\t\tMethod:   \"POST\",\n\t\tURL:      BatchEndpointCompletions,\n\t})\n}\n\nfunc (r *UploadBatchFileRequest) AddEmbedding(customerID string, body EmbeddingRequest) {\n\tr.Lines = append(r.Lines, BatchEmbeddingRequest{\n\t\tCustomID: customerID,\n\t\tBody:     body,\n\t\tMethod:   \"POST\",\n\t\tURL:      BatchEndpointEmbeddings,\n\t})\n}\n\n// UploadBatchFile — upload batch file.\nfunc (c *Client) UploadBatchFile(ctx context.Context, request UploadBatchFileRequest) (File, error) {\n\tif request.FileName == \"\" {\n\t\trequest.FileName = \"@batchinput.jsonl\"\n\t}\n\treturn c.CreateFileBytes(ctx, FileBytesRequest{\n\t\tName:    request.FileName,\n\t\tBytes:   request.MarshalJSONL(),\n\t\tPurpose: PurposeBatch,\n\t})\n}\n\ntype CreateBatchWithUploadFileRequest struct {\n\tEndpoint         BatchEndpoint  `json:\"endpoint\"`\n\tCompletionWindow string         `json:\"completion_window\"`\n\tMetadata         map[string]any `json:\"metadata\"`\n\tUploadBatchFileRequest\n}\n\n// CreateBatchWithUploadFile — API call to Create batch with upload file.\nfunc (c *Client) CreateBatchWithUploadFile(\n\tctx context.Context,\n\trequest CreateBatchWithUploadFileRequest,\n) (response BatchResponse, err error) {\n\tvar file File\n\tfile, err = c.UploadBatchFile(ctx, UploadBatchFileRequest{\n\t\tFileName: request.FileName,\n\t\tLines:    request.Lines,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\treturn c.CreateBatch(ctx, CreateBatchRequest{\n\t\tInputFileID:      file.ID,\n\t\tEndpoint:         request.Endpoint,\n\t\tCompletionWindow: request.CompletionWindow,\n\t\tMetadata:         request.Metadata,\n\t})\n}\n\n// RetrieveBatch — API call to Retrieve batch.\nfunc (c *Client) RetrieveBatch(\n\tctx context.Context,\n\tbatchID string,\n) (response BatchResponse, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", batchesSuffix, batchID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CancelBatch — API call to Cancel batch.\nfunc (c *Client) CancelBatch(\n\tctx context.Context,\n\tbatchID string,\n) (response BatchResponse, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s/cancel\", batchesSuffix, batchID)\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\ntype ListBatchResponse struct {\n\thttpHeader\n\tObject  string  `json:\"object\"`\n\tData    []Batch `json:\"data\"`\n\tFirstID string  `json:\"first_id\"`\n\tLastID  string  `json:\"last_id\"`\n\tHasMore bool    `json:\"has_more\"`\n}\n\n// ListBatch API call to List batch.\nfunc (c *Client) ListBatch(ctx context.Context, after *string, limit *int) (response ListBatchResponse, err error) {\n\turlValues := url.Values{}\n\tif limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *limit))\n\t}\n\tif after != nil {\n\t\turlValues.Add(\"after\", *after)\n\t}\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s%s\", batchesSuffix, encodedValues)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "batch_test.go",
    "content": "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\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestUploadBatchFile(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\"/v1/files\", handleCreateFile)\n\treq := openai.UploadBatchFileRequest{}\n\treq.AddChatCompletion(\"req-1\", openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\t_, err := client.UploadBatchFile(context.Background(), req)\n\tchecks.NoError(t, err, \"UploadBatchFile error\")\n}\n\nfunc TestCreateBatch(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\"/v1/batches\", handleBatchEndpoint)\n\t_, err := client.CreateBatch(context.Background(), openai.CreateBatchRequest{\n\t\tInputFileID:      \"file-abc\",\n\t\tEndpoint:         openai.BatchEndpointChatCompletions,\n\t\tCompletionWindow: \"24h\",\n\t})\n\tchecks.NoError(t, err, \"CreateBatch error\")\n}\n\nfunc TestCreateBatchWithUploadFile(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files\", handleCreateFile)\n\tserver.RegisterHandler(\"/v1/batches\", handleBatchEndpoint)\n\treq := openai.CreateBatchWithUploadFileRequest{\n\t\tEndpoint: openai.BatchEndpointChatCompletions,\n\t}\n\treq.AddChatCompletion(\"req-1\", openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\t_, err := client.CreateBatchWithUploadFile(context.Background(), req)\n\tchecks.NoError(t, err, \"CreateBatchWithUploadFile error\")\n}\n\nfunc TestRetrieveBatch(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/batches/file-id-1\", handleRetrieveBatchEndpoint)\n\t_, err := client.RetrieveBatch(context.Background(), \"file-id-1\")\n\tchecks.NoError(t, err, \"RetrieveBatch error\")\n}\n\nfunc TestCancelBatch(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/batches/file-id-1/cancel\", handleCancelBatchEndpoint)\n\t_, err := client.CancelBatch(context.Background(), \"file-id-1\")\n\tchecks.NoError(t, err, \"RetrieveBatch error\")\n}\n\nfunc TestListBatch(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/batches\", handleBatchEndpoint)\n\tafter := \"batch_abc123\"\n\tlimit := 10\n\t_, err := client.ListBatch(context.Background(), &after, &limit)\n\tchecks.NoError(t, err, \"RetrieveBatch error\")\n}\n\nfunc TestUploadBatchFileRequest_AddChatCompletion(t *testing.T) {\n\ttype args struct {\n\t\tcustomerID string\n\t\tbody       openai.ChatCompletionRequest\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs []args\n\t\twant []byte\n\t}{\n\t\t{\"\", []args{\n\t\t\t{\n\t\t\t\tcustomerID: \"req-1\",\n\t\t\t\tbody: openai.ChatCompletionRequest{\n\t\t\t\t\tMaxTokens: 5,\n\t\t\t\t\tModel:     openai.GPT3Dot5Turbo,\n\t\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tcustomerID: \"req-2\",\n\t\t\t\tbody: openai.ChatCompletionRequest{\n\t\t\t\t\tMaxTokens: 5,\n\t\t\t\t\tModel:     openai.GPT3Dot5Turbo,\n\t\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}, []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\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr := &openai.UploadBatchFileRequest{}\n\t\t\tfor _, arg := range tt.args {\n\t\t\t\tr.AddChatCompletion(arg.customerID, arg.body)\n\t\t\t}\n\t\t\tgot := r.MarshalJSONL()\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Marshal() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUploadBatchFileRequest_AddCompletion(t *testing.T) {\n\ttype args struct {\n\t\tcustomerID string\n\t\tbody       openai.CompletionRequest\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs []args\n\t\twant []byte\n\t}{\n\t\t{\"\", []args{\n\t\t\t{\n\t\t\t\tcustomerID: \"req-1\",\n\t\t\t\tbody: openai.CompletionRequest{\n\t\t\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\t\t\tUser:  \"Hello\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tcustomerID: \"req-2\",\n\t\t\t\tbody: openai.CompletionRequest{\n\t\t\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\t\t\tUser:  \"Hello\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, []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\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr := &openai.UploadBatchFileRequest{}\n\t\t\tfor _, arg := range tt.args {\n\t\t\t\tr.AddCompletion(arg.customerID, arg.body)\n\t\t\t}\n\t\t\tgot := r.MarshalJSONL()\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Marshal() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUploadBatchFileRequest_AddEmbedding(t *testing.T) {\n\ttype args struct {\n\t\tcustomerID string\n\t\tbody       openai.EmbeddingRequest\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs []args\n\t\twant []byte\n\t}{\n\t\t{\"\", []args{\n\t\t\t{\n\t\t\t\tcustomerID: \"req-1\",\n\t\t\t\tbody: openai.EmbeddingRequest{\n\t\t\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\t\t\tInput: []string{\"Hello\", \"World\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tcustomerID: \"req-2\",\n\t\t\t\tbody: openai.EmbeddingRequest{\n\t\t\t\t\tModel: openai.AdaEmbeddingV2,\n\t\t\t\t\tInput: []string{\"Hello\", \"World\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}, []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\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr := &openai.UploadBatchFileRequest{}\n\t\t\tfor _, arg := range tt.args {\n\t\t\t\tr.AddEmbedding(arg.customerID, arg.body)\n\t\t\t}\n\t\t\tgot := r.MarshalJSONL()\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Marshal() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc handleBatchEndpoint(w http.ResponseWriter, r *http.Request) {\n\tif r.Method == http.MethodPost {\n\t\t_, _ = fmt.Fprintln(w, `{\n\t\t\t  \"id\": \"batch_abc123\",\n\t\t\t  \"object\": \"batch\",\n\t\t\t  \"endpoint\": \"/v1/completions\",\n\t\t\t  \"errors\": null,\n\t\t\t  \"input_file_id\": \"file-abc123\",\n\t\t\t  \"completion_window\": \"24h\",\n\t\t\t  \"status\": \"completed\",\n\t\t\t  \"output_file_id\": \"file-cvaTdG\",\n\t\t\t  \"error_file_id\": \"file-HOWS94\",\n\t\t\t  \"created_at\": 1711471533,\n\t\t\t  \"in_progress_at\": 1711471538,\n\t\t\t  \"expires_at\": 1711557933,\n\t\t\t  \"finalizing_at\": 1711493133,\n\t\t\t  \"completed_at\": 1711493163,\n\t\t\t  \"failed_at\": null,\n\t\t\t  \"expired_at\": null,\n\t\t\t  \"cancelling_at\": null,\n\t\t\t  \"cancelled_at\": null,\n\t\t\t  \"request_counts\": {\n\t\t\t\t\"total\": 100,\n\t\t\t\t\"completed\": 95,\n\t\t\t\t\"failed\": 5\n\t\t\t  },\n\t\t\t  \"metadata\": {\n\t\t\t\t\"customer_id\": \"user_123456789\",\n\t\t\t\t\"batch_description\": \"Nightly eval job\"\n\t\t\t  }\n\t\t\t}`)\n\t} else if r.Method == http.MethodGet {\n\t\t_, _ = fmt.Fprintln(w, `{\n\t\t\t  \"object\": \"list\",\n\t\t\t  \"data\": [\n\t\t\t\t{\n\t\t\t\t  \"id\": \"batch_abc123\",\n\t\t\t\t  \"object\": \"batch\",\n\t\t\t\t  \"endpoint\": \"/v1/chat/completions\",\n\t\t\t\t  \"errors\": null,\n\t\t\t\t  \"input_file_id\": \"file-abc123\",\n\t\t\t\t  \"completion_window\": \"24h\",\n\t\t\t\t  \"status\": \"completed\",\n\t\t\t\t  \"output_file_id\": \"file-cvaTdG\",\n\t\t\t\t  \"error_file_id\": \"file-HOWS94\",\n\t\t\t\t  \"created_at\": 1711471533,\n\t\t\t\t  \"in_progress_at\": 1711471538,\n\t\t\t\t  \"expires_at\": 1711557933,\n\t\t\t\t  \"finalizing_at\": 1711493133,\n\t\t\t\t  \"completed_at\": 1711493163,\n\t\t\t\t  \"failed_at\": null,\n\t\t\t\t  \"expired_at\": null,\n\t\t\t\t  \"cancelling_at\": null,\n\t\t\t\t  \"cancelled_at\": null,\n\t\t\t\t  \"request_counts\": {\n\t\t\t\t\t\"total\": 100,\n\t\t\t\t\t\"completed\": 95,\n\t\t\t\t\t\"failed\": 5\n\t\t\t\t  },\n\t\t\t\t  \"metadata\": {\n\t\t\t\t\t\"customer_id\": \"user_123456789\",\n\t\t\t\t\t\"batch_description\": \"Nightly job\"\n\t\t\t\t  }\n\t\t\t\t}\n\t\t\t  ],\n\t\t\t  \"first_id\": \"batch_abc123\",\n\t\t\t  \"last_id\": \"batch_abc456\",\n\t\t\t  \"has_more\": true\n\t\t\t}`)\n\t}\n}\n\nfunc handleRetrieveBatchEndpoint(w http.ResponseWriter, r *http.Request) {\n\tif r.Method == http.MethodGet {\n\t\t_, _ = fmt.Fprintln(w, `{\n\t\t  \"id\": \"batch_abc123\",\n\t\t  \"object\": \"batch\",\n\t\t  \"endpoint\": \"/v1/completions\",\n\t\t  \"errors\": null,\n\t\t  \"input_file_id\": \"file-abc123\",\n\t\t  \"completion_window\": \"24h\",\n\t\t  \"status\": \"completed\",\n\t\t  \"output_file_id\": \"file-cvaTdG\",\n\t\t  \"error_file_id\": \"file-HOWS94\",\n\t\t  \"created_at\": 1711471533,\n\t\t  \"in_progress_at\": 1711471538,\n\t\t  \"expires_at\": 1711557933,\n\t\t  \"finalizing_at\": 1711493133,\n\t\t  \"completed_at\": 1711493163,\n\t\t  \"failed_at\": null,\n\t\t  \"expired_at\": null,\n\t\t  \"cancelling_at\": null,\n\t\t  \"cancelled_at\": null,\n\t\t  \"request_counts\": {\n\t\t\t\"total\": 100,\n\t\t\t\"completed\": 95,\n\t\t\t\"failed\": 5\n\t\t  },\n\t\t  \"metadata\": {\n\t\t\t\"customer_id\": \"user_123456789\",\n\t\t\t\"batch_description\": \"Nightly eval job\"\n\t\t  }\n\t\t}`)\n\t}\n}\n\nfunc handleCancelBatchEndpoint(w http.ResponseWriter, r *http.Request) {\n\tif r.Method == http.MethodPost {\n\t\t_, _ = fmt.Fprintln(w, `{\n\t\t  \"id\": \"batch_abc123\",\n\t\t  \"object\": \"batch\",\n\t\t  \"endpoint\": \"/v1/chat/completions\",\n\t\t  \"errors\": null,\n\t\t  \"input_file_id\": \"file-abc123\",\n\t\t  \"completion_window\": \"24h\",\n\t\t  \"status\": \"cancelling\",\n\t\t  \"output_file_id\": null,\n\t\t  \"error_file_id\": null,\n\t\t  \"created_at\": 1711471533,\n\t\t  \"in_progress_at\": 1711471538,\n\t\t  \"expires_at\": 1711557933,\n\t\t  \"finalizing_at\": null,\n\t\t  \"completed_at\": null,\n\t\t  \"failed_at\": null,\n\t\t  \"expired_at\": null,\n\t\t  \"cancelling_at\": 1711475133,\n\t\t  \"cancelled_at\": null,\n\t\t  \"request_counts\": {\n\t\t\t\"total\": 100,\n\t\t\t\"completed\": 23,\n\t\t\t\"failed\": 1\n\t\t  },\n\t\t  \"metadata\": {\n\t\t\t\"customer_id\": \"user_123456789\",\n\t\t\t\"batch_description\": \"Nightly eval job\"\n\t\t  }\n\t\t}`)\n\t}\n}\n"
  },
  {
    "path": "chat.go",
    "content": "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/jsonschema\"\n)\n\n// Chat message role defined by the OpenAI API.\nconst (\n\tChatMessageRoleSystem    = \"system\"\n\tChatMessageRoleUser      = \"user\"\n\tChatMessageRoleAssistant = \"assistant\"\n\tChatMessageRoleFunction  = \"function\"\n\tChatMessageRoleTool      = \"tool\"\n\tChatMessageRoleDeveloper = \"developer\"\n)\n\nconst chatCompletionsSuffix = \"/chat/completions\"\n\nvar (\n\tErrChatCompletionInvalidModel       = errors.New(\"this model is not supported with this method, please use CreateCompletion client method instead\") //nolint:lll\n\tErrChatCompletionStreamNotSupported = errors.New(\"streaming is not supported with this method, please use CreateChatCompletionStream\")              //nolint:lll\n\tErrContentFieldsMisused             = errors.New(\"can't use both Content and MultiContent properties simultaneously\")\n)\n\ntype Hate struct {\n\tFiltered bool   `json:\"filtered\"`\n\tSeverity string `json:\"severity,omitempty\"`\n}\ntype SelfHarm struct {\n\tFiltered bool   `json:\"filtered\"`\n\tSeverity string `json:\"severity,omitempty\"`\n}\ntype Sexual struct {\n\tFiltered bool   `json:\"filtered\"`\n\tSeverity string `json:\"severity,omitempty\"`\n}\ntype Violence struct {\n\tFiltered bool   `json:\"filtered\"`\n\tSeverity string `json:\"severity,omitempty\"`\n}\n\ntype JailBreak struct {\n\tFiltered bool `json:\"filtered\"`\n\tDetected bool `json:\"detected\"`\n}\n\ntype Profanity struct {\n\tFiltered bool `json:\"filtered\"`\n\tDetected bool `json:\"detected\"`\n}\n\ntype ContentFilterResults struct {\n\tHate      Hate      `json:\"hate,omitempty\"`\n\tSelfHarm  SelfHarm  `json:\"self_harm,omitempty\"`\n\tSexual    Sexual    `json:\"sexual,omitempty\"`\n\tViolence  Violence  `json:\"violence,omitempty\"`\n\tJailBreak JailBreak `json:\"jailbreak,omitempty\"`\n\tProfanity Profanity `json:\"profanity,omitempty\"`\n}\n\ntype PromptAnnotation struct {\n\tPromptIndex          int                  `json:\"prompt_index,omitempty\"`\n\tContentFilterResults ContentFilterResults `json:\"content_filter_results,omitempty\"`\n}\n\ntype ImageURLDetail string\n\nconst (\n\tImageURLDetailHigh ImageURLDetail = \"high\"\n\tImageURLDetailLow  ImageURLDetail = \"low\"\n\tImageURLDetailAuto ImageURLDetail = \"auto\"\n)\n\ntype ChatMessageImageURL struct {\n\tURL    string         `json:\"url,omitempty\"`\n\tDetail ImageURLDetail `json:\"detail,omitempty\"`\n}\n\ntype ChatMessagePartType string\n\nconst (\n\tChatMessagePartTypeText     ChatMessagePartType = \"text\"\n\tChatMessagePartTypeImageURL ChatMessagePartType = \"image_url\"\n)\n\ntype ChatMessagePart struct {\n\tType     ChatMessagePartType  `json:\"type,omitempty\"`\n\tText     string               `json:\"text,omitempty\"`\n\tImageURL *ChatMessageImageURL `json:\"image_url,omitempty\"`\n}\n\ntype ChatCompletionMessage struct {\n\tRole         string `json:\"role\"`\n\tContent      string `json:\"content,omitempty\"`\n\tRefusal      string `json:\"refusal,omitempty\"`\n\tMultiContent []ChatMessagePart\n\n\t// This property isn't in the official documentation, but it's in\n\t// the documentation for the official library for python:\n\t// - https://github.com/openai/openai-python/blob/main/chatml.md\n\t// - https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb\n\tName string `json:\"name,omitempty\"`\n\n\t// This property is used for the \"reasoning\" feature supported by deepseek-reasoner\n\t// which is not in the official documentation.\n\t// the doc from deepseek:\n\t// - https://api-docs.deepseek.com/api/create-chat-completion#responses\n\tReasoningContent string `json:\"reasoning_content,omitempty\"`\n\n\tFunctionCall *FunctionCall `json:\"function_call,omitempty\"`\n\n\t// For Role=assistant prompts this may be set to the tool calls generated by the model, such as function calls.\n\tToolCalls []ToolCall `json:\"tool_calls,omitempty\"`\n\n\t// For Role=tool prompts this should be set to the ID given in the assistant's prior request to call a tool.\n\tToolCallID string `json:\"tool_call_id,omitempty\"`\n}\n\nfunc (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {\n\tif m.Content != \"\" && m.MultiContent != nil {\n\t\treturn nil, ErrContentFieldsMisused\n\t}\n\tif len(m.MultiContent) > 0 {\n\t\tmsg := struct {\n\t\t\tRole             string            `json:\"role\"`\n\t\t\tContent          string            `json:\"-\"`\n\t\t\tRefusal          string            `json:\"refusal,omitempty\"`\n\t\t\tMultiContent     []ChatMessagePart `json:\"content,omitempty\"`\n\t\t\tName             string            `json:\"name,omitempty\"`\n\t\t\tReasoningContent string            `json:\"reasoning_content,omitempty\"`\n\t\t\tFunctionCall     *FunctionCall     `json:\"function_call,omitempty\"`\n\t\t\tToolCalls        []ToolCall        `json:\"tool_calls,omitempty\"`\n\t\t\tToolCallID       string            `json:\"tool_call_id,omitempty\"`\n\t\t}(m)\n\t\treturn json.Marshal(msg)\n\t}\n\n\tmsg := struct {\n\t\tRole             string            `json:\"role\"`\n\t\tContent          string            `json:\"content,omitempty\"`\n\t\tRefusal          string            `json:\"refusal,omitempty\"`\n\t\tMultiContent     []ChatMessagePart `json:\"-\"`\n\t\tName             string            `json:\"name,omitempty\"`\n\t\tReasoningContent string            `json:\"reasoning_content,omitempty\"`\n\t\tFunctionCall     *FunctionCall     `json:\"function_call,omitempty\"`\n\t\tToolCalls        []ToolCall        `json:\"tool_calls,omitempty\"`\n\t\tToolCallID       string            `json:\"tool_call_id,omitempty\"`\n\t}(m)\n\treturn json.Marshal(msg)\n}\n\nfunc (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {\n\tmsg := struct {\n\t\tRole             string `json:\"role\"`\n\t\tContent          string `json:\"content\"`\n\t\tRefusal          string `json:\"refusal,omitempty\"`\n\t\tMultiContent     []ChatMessagePart\n\t\tName             string        `json:\"name,omitempty\"`\n\t\tReasoningContent string        `json:\"reasoning_content,omitempty\"`\n\t\tFunctionCall     *FunctionCall `json:\"function_call,omitempty\"`\n\t\tToolCalls        []ToolCall    `json:\"tool_calls,omitempty\"`\n\t\tToolCallID       string        `json:\"tool_call_id,omitempty\"`\n\t}{}\n\n\tif err := json.Unmarshal(bs, &msg); err == nil {\n\t\t*m = ChatCompletionMessage(msg)\n\t\treturn nil\n\t}\n\tmultiMsg := struct {\n\t\tRole             string `json:\"role\"`\n\t\tContent          string\n\t\tRefusal          string            `json:\"refusal,omitempty\"`\n\t\tMultiContent     []ChatMessagePart `json:\"content\"`\n\t\tName             string            `json:\"name,omitempty\"`\n\t\tReasoningContent string            `json:\"reasoning_content,omitempty\"`\n\t\tFunctionCall     *FunctionCall     `json:\"function_call,omitempty\"`\n\t\tToolCalls        []ToolCall        `json:\"tool_calls,omitempty\"`\n\t\tToolCallID       string            `json:\"tool_call_id,omitempty\"`\n\t}{}\n\tif err := json.Unmarshal(bs, &multiMsg); err != nil {\n\t\treturn err\n\t}\n\t*m = ChatCompletionMessage(multiMsg)\n\treturn nil\n}\n\ntype ToolCall struct {\n\t// Index is not nil only in chat completion chunk object\n\tIndex    *int         `json:\"index,omitempty\"`\n\tID       string       `json:\"id,omitempty\"`\n\tType     ToolType     `json:\"type\"`\n\tFunction FunctionCall `json:\"function\"`\n}\n\ntype FunctionCall struct {\n\tName string `json:\"name,omitempty\"`\n\t// call function with arguments in JSON format\n\tArguments string `json:\"arguments,omitempty\"`\n}\n\ntype ChatCompletionResponseFormatType string\n\nconst (\n\tChatCompletionResponseFormatTypeJSONObject ChatCompletionResponseFormatType = \"json_object\"\n\tChatCompletionResponseFormatTypeJSONSchema ChatCompletionResponseFormatType = \"json_schema\"\n\tChatCompletionResponseFormatTypeText       ChatCompletionResponseFormatType = \"text\"\n)\n\ntype ChatCompletionResponseFormat struct {\n\tType       ChatCompletionResponseFormatType        `json:\"type,omitempty\"`\n\tJSONSchema *ChatCompletionResponseFormatJSONSchema `json:\"json_schema,omitempty\"`\n}\n\ntype ChatCompletionResponseFormatJSONSchema struct {\n\tName        string         `json:\"name\"`\n\tDescription string         `json:\"description,omitempty\"`\n\tSchema      json.Marshaler `json:\"schema\"`\n\tStrict      bool           `json:\"strict\"`\n}\n\nfunc (r *ChatCompletionResponseFormatJSONSchema) UnmarshalJSON(data []byte) error {\n\ttype rawJSONSchema struct {\n\t\tName        string          `json:\"name\"`\n\t\tDescription string          `json:\"description,omitempty\"`\n\t\tSchema      json.RawMessage `json:\"schema\"`\n\t\tStrict      bool            `json:\"strict\"`\n\t}\n\tvar raw rawJSONSchema\n\tif err := json.Unmarshal(data, &raw); err != nil {\n\t\treturn err\n\t}\n\tr.Name = raw.Name\n\tr.Description = raw.Description\n\tr.Strict = raw.Strict\n\tif len(raw.Schema) > 0 && string(raw.Schema) != \"null\" {\n\t\tvar d jsonschema.Definition\n\t\terr := json.Unmarshal(raw.Schema, &d)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Schema = &d\n\t}\n\treturn nil\n}\n\n// ChatCompletionRequestExtensions contains third-party OpenAI API extensions\n// (e.g., vendor-specific implementations like vLLM).\ntype ChatCompletionRequestExtensions struct {\n\t// GuidedChoice is a vLLM-specific extension that restricts the model's output\n\t// to one of the predefined string choices provided in this field. This feature\n\t// is used to constrain the model's responses to a controlled set of options,\n\t// ensuring predictable and consistent outputs in scenarios where specific\n\t// choices are required.\n\tGuidedChoice []string `json:\"guided_choice,omitempty\"`\n}\n\n// ChatCompletionRequest represents a request structure for chat completion API.\ntype ChatCompletionRequest struct {\n\tModel    string                  `json:\"model\"`\n\tMessages []ChatCompletionMessage `json:\"messages\"`\n\t// MaxTokens The maximum number of tokens that can be generated in the chat completion.\n\t// This value can be used to control costs for text generated via API.\n\t// Deprecated: use MaxCompletionTokens. Not compatible with o1-series models.\n\t// refs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens\n\tMaxTokens int `json:\"max_tokens,omitempty\"`\n\t// MaxCompletionTokens An upper bound for the number of tokens that can be generated for a completion,\n\t// including visible output tokens and reasoning tokens https://platform.openai.com/docs/guides/reasoning\n\tMaxCompletionTokens int                           `json:\"max_completion_tokens,omitempty\"`\n\tTemperature         float32                       `json:\"temperature,omitempty\"`\n\tTopP                float32                       `json:\"top_p,omitempty\"`\n\tN                   int                           `json:\"n,omitempty\"`\n\tStream              bool                          `json:\"stream,omitempty\"`\n\tStop                []string                      `json:\"stop,omitempty\"`\n\tPresencePenalty     float32                       `json:\"presence_penalty,omitempty\"`\n\tResponseFormat      *ChatCompletionResponseFormat `json:\"response_format,omitempty\"`\n\tSeed                *int                          `json:\"seed,omitempty\"`\n\tFrequencyPenalty    float32                       `json:\"frequency_penalty,omitempty\"`\n\t// LogitBias is must be a token id string (specified by their token ID in the tokenizer), not a word string.\n\t// incorrect: `\"logit_bias\":{\"You\": 6}`, correct: `\"logit_bias\":{\"1639\": 6}`\n\t// refs: https://platform.openai.com/docs/api-reference/chat/create#chat/create-logit_bias\n\tLogitBias map[string]int `json:\"logit_bias,omitempty\"`\n\t// LogProbs indicates whether to return log probabilities of the output tokens or not.\n\t// If true, returns the log probabilities of each output token returned in the content of message.\n\t// This option is currently not available on the gpt-4-vision-preview model.\n\tLogProbs bool `json:\"logprobs,omitempty\"`\n\t// TopLogProbs is an integer between 0 and 5 specifying the number of most likely tokens to return at each\n\t// token position, each with an associated log probability.\n\t// logprobs must be set to true if this parameter is used.\n\tTopLogProbs int    `json:\"top_logprobs,omitempty\"`\n\tUser        string `json:\"user,omitempty\"`\n\t// Deprecated: use Tools instead.\n\tFunctions []FunctionDefinition `json:\"functions,omitempty\"`\n\t// Deprecated: use ToolChoice instead.\n\tFunctionCall any    `json:\"function_call,omitempty\"`\n\tTools        []Tool `json:\"tools,omitempty\"`\n\t// This can be either a string or an ToolChoice object.\n\tToolChoice any `json:\"tool_choice,omitempty\"`\n\t// Options for streaming response. Only set this when you set stream: true.\n\tStreamOptions *StreamOptions `json:\"stream_options,omitempty\"`\n\t// Disable the default behavior of parallel tool calls by setting it: false.\n\tParallelToolCalls any `json:\"parallel_tool_calls,omitempty\"`\n\t// Store can be set to true to store the output of this completion request for use in distillations and evals.\n\t// https://platform.openai.com/docs/api-reference/chat/create#chat-create-store\n\tStore bool `json:\"store,omitempty\"`\n\t// Controls effort on reasoning for reasoning models. It can be set to \"low\", \"medium\", or \"high\".\n\tReasoningEffort string `json:\"reasoning_effort,omitempty\"`\n\t// Metadata to store with the completion.\n\tMetadata map[string]string `json:\"metadata,omitempty\"`\n\t// Configuration for a predicted output.\n\tPrediction *Prediction `json:\"prediction,omitempty\"`\n\t// ChatTemplateKwargs provides a way to add non-standard parameters to the request body.\n\t// Additional kwargs to pass to the template renderer. Will be accessible by the chat template.\n\t// Such as think mode for qwen3. \"chat_template_kwargs\": {\"enable_thinking\": false}\n\t// https://qwen.readthedocs.io/en/latest/deployment/vllm.html#thinking-non-thinking-modes\n\tChatTemplateKwargs map[string]any `json:\"chat_template_kwargs,omitempty\"`\n\t// Specifies the latency tier to use for processing the request.\n\tServiceTier ServiceTier `json:\"service_tier,omitempty\"`\n\t// Verbosity determines how many output tokens are generated. Lowering the number of\n\t// tokens reduces overall latency. It can be set to \"low\", \"medium\", or \"high\".\n\t// Note: This field is only confirmed to work with gpt-5, gpt-5-mini and gpt-5-nano.\n\t// Also, it is not in the API reference of chat completion at the time of writing,\n\t// though it is supported by the API.\n\tVerbosity string `json:\"verbosity,omitempty\"`\n\t// A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\n\t// The IDs should be a string that uniquely identifies each user.\n\t// We recommend hashing their username or email address, in order to avoid sending us any identifying information.\n\t// https://platform.openai.com/docs/api-reference/chat/create#chat_create-safety_identifier\n\tSafetyIdentifier string `json:\"safety_identifier,omitempty\"`\n\t// Embedded struct for non-OpenAI extensions\n\tChatCompletionRequestExtensions\n}\n\ntype StreamOptions struct {\n\t// If set, an additional chunk will be streamed before the data: [DONE] message.\n\t// The usage field on this chunk shows the token usage statistics for the entire request,\n\t// and the choices field will always be an empty array.\n\t// All other chunks will also include a usage field, but with a null value.\n\tIncludeUsage bool `json:\"include_usage,omitempty\"`\n}\n\ntype ToolType string\n\nconst (\n\tToolTypeFunction ToolType = \"function\"\n)\n\ntype Tool struct {\n\tType     ToolType            `json:\"type\"`\n\tFunction *FunctionDefinition `json:\"function,omitempty\"`\n}\n\ntype ToolChoice struct {\n\tType     ToolType     `json:\"type\"`\n\tFunction ToolFunction `json:\"function,omitempty\"`\n}\n\ntype ToolFunction struct {\n\tName string `json:\"name\"`\n}\n\ntype FunctionDefinition struct {\n\tName        string `json:\"name\"`\n\tDescription string `json:\"description,omitempty\"`\n\tStrict      bool   `json:\"strict,omitempty\"`\n\t// Parameters is an object describing the function.\n\t// You can pass json.RawMessage to describe the schema,\n\t// or you can pass in a struct which serializes to the proper JSON schema.\n\t// The jsonschema package is provided for convenience, but you should\n\t// consider another specialized library if you require more complex schemas.\n\tParameters any `json:\"parameters\"`\n}\n\n// Deprecated: use FunctionDefinition instead.\ntype FunctionDefine = FunctionDefinition\n\ntype TopLogProbs struct {\n\tToken   string  `json:\"token\"`\n\tLogProb float64 `json:\"logprob\"`\n\tBytes   []byte  `json:\"bytes,omitempty\"`\n}\n\n// LogProb represents the probability information for a token.\ntype LogProb struct {\n\tToken   string  `json:\"token\"`\n\tLogProb float64 `json:\"logprob\"`\n\tBytes   []byte  `json:\"bytes,omitempty\"` // Omitting the field if it is null\n\t// TopLogProbs is a list of the most likely tokens and their log probability, at this token position.\n\t// In rare cases, there may be fewer than the number of requested top_logprobs returned.\n\tTopLogProbs []TopLogProbs `json:\"top_logprobs\"`\n}\n\n// LogProbs is the top-level structure containing the log probability information.\ntype LogProbs struct {\n\t// Content is a list of message content tokens with log probability information.\n\tContent []LogProb `json:\"content\"`\n}\n\ntype Prediction struct {\n\tContent string `json:\"content\"`\n\tType    string `json:\"type\"`\n}\n\ntype FinishReason string\n\nconst (\n\tFinishReasonStop          FinishReason = \"stop\"\n\tFinishReasonLength        FinishReason = \"length\"\n\tFinishReasonFunctionCall  FinishReason = \"function_call\"\n\tFinishReasonToolCalls     FinishReason = \"tool_calls\"\n\tFinishReasonContentFilter FinishReason = \"content_filter\"\n\tFinishReasonNull          FinishReason = \"null\"\n)\n\ntype ServiceTier string\n\nconst (\n\tServiceTierAuto     ServiceTier = \"auto\"\n\tServiceTierDefault  ServiceTier = \"default\"\n\tServiceTierFlex     ServiceTier = \"flex\"\n\tServiceTierPriority ServiceTier = \"priority\"\n)\n\nfunc (r FinishReason) MarshalJSON() ([]byte, error) {\n\tif r == FinishReasonNull || r == \"\" {\n\t\treturn []byte(\"null\"), nil\n\t}\n\treturn []byte(`\"` + string(r) + `\"`), nil // best effort to not break future API changes\n}\n\ntype ChatCompletionChoice struct {\n\tIndex   int                   `json:\"index\"`\n\tMessage ChatCompletionMessage `json:\"message\"`\n\t// FinishReason\n\t// stop: API returned complete message,\n\t// or a message terminated by one of the stop sequences provided via the stop parameter\n\t// length: Incomplete model output due to max_tokens parameter or token limit\n\t// function_call: The model decided to call a function\n\t// content_filter: Omitted content due to a flag from our content filters\n\t// null: API response still in progress or incomplete\n\tFinishReason         FinishReason         `json:\"finish_reason\"`\n\tLogProbs             *LogProbs            `json:\"logprobs,omitempty\"`\n\tContentFilterResults ContentFilterResults `json:\"content_filter_results,omitempty\"`\n}\n\n// ChatCompletionResponse represents a response structure for chat completion API.\ntype ChatCompletionResponse struct {\n\tID                  string                 `json:\"id\"`\n\tObject              string                 `json:\"object\"`\n\tCreated             int64                  `json:\"created\"`\n\tModel               string                 `json:\"model\"`\n\tChoices             []ChatCompletionChoice `json:\"choices\"`\n\tUsage               Usage                  `json:\"usage\"`\n\tSystemFingerprint   string                 `json:\"system_fingerprint\"`\n\tPromptFilterResults []PromptFilterResult   `json:\"prompt_filter_results,omitempty\"`\n\tServiceTier         ServiceTier            `json:\"service_tier,omitempty\"`\n\n\thttpHeader\n}\n\n// CreateChatCompletion — API call to Create a completion for the chat message.\nfunc (c *Client) CreateChatCompletion(\n\tctx context.Context,\n\trequest ChatCompletionRequest,\n) (response ChatCompletionResponse, err error) {\n\tif request.Stream {\n\t\terr = ErrChatCompletionStreamNotSupported\n\t\treturn\n\t}\n\n\turlSuffix := chatCompletionsSuffix\n\tif !checkEndpointSupportsModel(urlSuffix, request.Model) {\n\t\terr = ErrChatCompletionInvalidModel\n\t\treturn\n\t}\n\n\treasoningValidator := NewReasoningValidator()\n\tif err = reasoningValidator.Validate(request); err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "chat_stream.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\ntype ChatCompletionStreamChoiceDelta struct {\n\tContent      string        `json:\"content,omitempty\"`\n\tRole         string        `json:\"role,omitempty\"`\n\tFunctionCall *FunctionCall `json:\"function_call,omitempty\"`\n\tToolCalls    []ToolCall    `json:\"tool_calls,omitempty\"`\n\tRefusal      string        `json:\"refusal,omitempty\"`\n\n\t// This property is used for the \"reasoning\" feature supported by deepseek-reasoner\n\t// which is not in the official documentation.\n\t// the doc from deepseek:\n\t// - https://api-docs.deepseek.com/api/create-chat-completion#responses\n\tReasoningContent string `json:\"reasoning_content,omitempty\"`\n}\n\ntype ChatCompletionStreamChoiceLogprobs struct {\n\tContent []ChatCompletionTokenLogprob `json:\"content,omitempty\"`\n\tRefusal []ChatCompletionTokenLogprob `json:\"refusal,omitempty\"`\n}\n\ntype ChatCompletionTokenLogprob struct {\n\tToken       string                                 `json:\"token\"`\n\tBytes       []int64                                `json:\"bytes,omitempty\"`\n\tLogprob     float64                                `json:\"logprob,omitempty\"`\n\tTopLogprobs []ChatCompletionTokenLogprobTopLogprob `json:\"top_logprobs\"`\n}\n\ntype ChatCompletionTokenLogprobTopLogprob struct {\n\tToken   string  `json:\"token\"`\n\tBytes   []int64 `json:\"bytes\"`\n\tLogprob float64 `json:\"logprob\"`\n}\n\ntype ChatCompletionStreamChoice struct {\n\tIndex                int                                 `json:\"index\"`\n\tDelta                ChatCompletionStreamChoiceDelta     `json:\"delta\"`\n\tLogprobs             *ChatCompletionStreamChoiceLogprobs `json:\"logprobs,omitempty\"`\n\tFinishReason         FinishReason                        `json:\"finish_reason\"`\n\tContentFilterResults ContentFilterResults                `json:\"content_filter_results,omitempty\"`\n}\n\ntype PromptFilterResult struct {\n\tIndex                int                  `json:\"index\"`\n\tContentFilterResults ContentFilterResults `json:\"content_filter_results,omitempty\"`\n}\n\ntype ChatCompletionStreamResponse struct {\n\tID                  string                       `json:\"id\"`\n\tObject              string                       `json:\"object\"`\n\tCreated             int64                        `json:\"created\"`\n\tModel               string                       `json:\"model\"`\n\tChoices             []ChatCompletionStreamChoice `json:\"choices\"`\n\tSystemFingerprint   string                       `json:\"system_fingerprint\"`\n\tPromptAnnotations   []PromptAnnotation           `json:\"prompt_annotations,omitempty\"`\n\tPromptFilterResults []PromptFilterResult         `json:\"prompt_filter_results,omitempty\"`\n\t// An optional field that will only be present when you set stream_options: {\"include_usage\": true} in your request.\n\t// When present, it contains a null value except for the last chunk which contains the token usage statistics\n\t// for the entire request.\n\tUsage *Usage `json:\"usage,omitempty\"`\n}\n\n// ChatCompletionStream\n// Note: Perhaps it is more elegant to abstract Stream using generics.\ntype ChatCompletionStream struct {\n\t*streamReader[ChatCompletionStreamResponse]\n}\n\n// CreateChatCompletionStream — API call to create a chat completion w/ streaming\n// support. It sets whether to stream back partial progress. If set, tokens will be\n// sent as data-only server-sent events as they become available, with the\n// stream terminated by a data: [DONE] message.\nfunc (c *Client) CreateChatCompletionStream(\n\tctx context.Context,\n\trequest ChatCompletionRequest,\n) (stream *ChatCompletionStream, err error) {\n\turlSuffix := chatCompletionsSuffix\n\tif !checkEndpointSupportsModel(urlSuffix, request.Model) {\n\t\terr = ErrChatCompletionInvalidModel\n\t\treturn\n\t}\n\n\trequest.Stream = true\n\treasoningValidator := NewReasoningValidator()\n\tif err = reasoningValidator.Validate(request); err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := sendRequestStream[ChatCompletionStreamResponse](c, req)\n\tif err != nil {\n\t\treturn\n\t}\n\tstream = &ChatCompletionStream{\n\t\tstreamReader: resp,\n\t}\n\treturn\n}\n"
  },
  {
    "path": "chat_stream_test.go",
    "content": "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\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestChatCompletionsStreamWrongModel(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\tctx := context.Background()\n\n\treq := openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     \"ada\",\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t}\n\t_, err := client.CreateChatCompletionStream(ctx, req)\n\tif !errors.Is(err, openai.ErrChatCompletionInvalidModel) {\n\t\tt.Fatalf(\"CreateChatCompletion should return ErrChatCompletionInvalidModel, but returned: %v\", err)\n\t}\n}\n\nfunc TestCreateChatCompletionStream(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata := `{\"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\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata = `{\"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\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: done\\n\")...)\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.ChatCompletionStreamResponse{\n\t\t{\n\t\t\tID:                \"1\",\n\t\t\tObject:            \"completion\",\n\t\t\tCreated:           1598069254,\n\t\t\tModel:             openai.GPT3Dot5Turbo,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"response1\",\n\t\t\t\t\t},\n\t\t\t\t\tFinishReason: \"max_tokens\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"2\",\n\t\t\tObject:            \"completion\",\n\t\t\tCreated:           1598069255,\n\t\t\tModel:             openai.GPT3Dot5Turbo,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"response2\",\n\t\t\t\t\t},\n\t\t\t\t\tFinishReason: \"max_tokens\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\tb, _ := json.Marshal(expectedResponse)\n\t\tt.Logf(\"%d: %s\", ix, string(b))\n\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tchecks.NoError(t, streamErr, \"stream.Recv() failed\")\n\t\tif !compareChatResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n\n\t_, streamErr = stream.Recv()\n\n\tchecks.ErrorIs(t, streamErr, io.EOF, \"stream.Recv() did not return EOF when the stream is finished\")\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF when the stream is finished: %v\", streamErr)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataStr := []string{\n\t\t\t`{`,\n\t\t\t`\"error\": {`,\n\t\t\t`\"message\": \"Incorrect API key provided: sk-***************************************\",`,\n\t\t\t`\"type\": \"invalid_request_error\",`,\n\t\t\t`\"param\": null,`,\n\t\t\t`\"code\": \"invalid_api_key\"`,\n\t\t\t`}`,\n\t\t\t`}`,\n\t\t}\n\t\tfor _, str := range dataStr {\n\t\t\tdataBytes = append(dataBytes, []byte(str+\"\\n\")...)\n\t\t}\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, streamErr := stream.Recv()\n\tchecks.HasError(t, streamErr, \"stream.Recv() did not return error\")\n\n\tvar apiErr *openai.APIError\n\tif !errors.As(streamErr, &apiErr) {\n\t\tt.Errorf(\"stream.Recv() did not return APIError\")\n\t}\n\tt.Logf(\"%+v\\n\", apiErr)\n}\n\nfunc TestCreateChatCompletionStreamWithHeaders(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\t\tw.Header().Set(xCustomHeader, xCustomHeaderValue)\n\n\t\t// Send test responses\n\t\t//nolint:lll\n\t\tdataBytes := []byte(`data: {\"error\":{\"message\":\"The server had an error while processing your request. Sorry about that!\", \"type\":\"server_ error\", \"param\":null,\"code\":null}}`)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\ndata: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\tvalue := stream.Header().Get(xCustomHeader)\n\tif value != xCustomHeaderValue {\n\t\tt.Errorf(\"expected %s to be %s\", xCustomHeaderValue, value)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamWithRatelimitHeaders(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\t\tfor k, v := range rateLimitHeaders {\n\t\t\tswitch val := v.(type) {\n\t\t\tcase int:\n\t\t\t\tw.Header().Set(k, strconv.Itoa(val))\n\t\t\tdefault:\n\t\t\t\tw.Header().Set(k, fmt.Sprintf(\"%s\", v))\n\t\t\t}\n\t\t}\n\n\t\t// Send test responses\n\t\t//nolint:lll\n\t\tdataBytes := []byte(`data: {\"error\":{\"message\":\"The server had an error while processing your request. Sorry about that!\", \"type\":\"server_ error\", \"param\":null,\"code\":null}}`)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\ndata: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\theaders := stream.GetRateLimitHeaders()\n\tbs1, _ := json.Marshal(headers)\n\tbs2, _ := json.Marshal(rateLimitHeaders)\n\tif string(bs1) != string(bs2) {\n\t\tt.Errorf(\"expected rate limit header %s to be %s\", bs2, bs1)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamErrorWithDataPrefix(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\t//nolint:lll\n\t\tdataBytes := []byte(`data: {\"error\":{\"message\":\"The server had an error while processing your request. Sorry about that!\", \"type\":\"server_ error\", \"param\":null,\"code\":null}}`)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\ndata: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, streamErr := stream.Recv()\n\tchecks.HasError(t, streamErr, \"stream.Recv() did not return error\")\n\n\tvar apiErr *openai.APIError\n\tif !errors.As(streamErr, &apiErr) {\n\t\tt.Errorf(\"stream.Recv() did not return APIError\")\n\t}\n\tt.Logf(\"%+v\\n\", apiErr)\n}\n\nfunc TestCreateChatCompletionStreamRateLimitError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(429)\n\n\t\t// Send test responses\n\t\tdataBytes := []byte(`{\"error\":{` +\n\t\t\t`\"message\": \"You are sending requests too quickly.\",` +\n\t\t\t`\"type\":\"rate_limit_reached\",` +\n\t\t\t`\"param\":null,` +\n\t\t\t`\"code\":\"rate_limit_reached\"}}`)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\t_, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tvar apiErr *openai.APIError\n\tif !errors.As(err, &apiErr) {\n\t\tt.Errorf(\"TestCreateChatCompletionStreamRateLimitError did not return APIError\")\n\t}\n\tt.Logf(\"%+v\\n\", apiErr)\n}\n\nfunc TestCreateChatCompletionStreamWithRefusal(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\tdataBytes := []byte{}\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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\"}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 2000,\n\t\tModel:     openai.GPT4oMini20240718,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.ChatCompletionStreamResponse{\n\t\t{\n\t\t\tID:                \"1\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"2\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tRefusal: \"Hello\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"3\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tRefusal: \" World\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"4\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex:        0,\n\t\t\t\t\tFinishReason: \"stop\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\tb, _ := json.Marshal(expectedResponse)\n\t\tt.Logf(\"%d: %s\", ix, string(b))\n\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tchecks.NoError(t, streamErr, \"stream.Recv() failed\")\n\t\tif !compareChatResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamWithLogprobs(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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\"}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 2000,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.ChatCompletionStreamResponse{\n\t\t{\n\t\t\tID:                \"1\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{},\n\t\t\t\t\tLogprobs: &openai.ChatCompletionStreamChoiceLogprobs{\n\t\t\t\t\t\tContent: []openai.ChatCompletionTokenLogprob{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"2\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"Hello\",\n\t\t\t\t\t},\n\t\t\t\t\tLogprobs: &openai.ChatCompletionStreamChoiceLogprobs{\n\t\t\t\t\t\tContent: []openai.ChatCompletionTokenLogprob{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tToken:       \"Hello\",\n\t\t\t\t\t\t\t\tLogprob:     -0.000020458236,\n\t\t\t\t\t\t\t\tBytes:       []int64{72, 101, 108, 108, 111},\n\t\t\t\t\t\t\t\tTopLogprobs: []openai.ChatCompletionTokenLogprobTopLogprob{},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"3\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \" World\",\n\t\t\t\t\t},\n\t\t\t\t\tLogprobs: &openai.ChatCompletionStreamChoiceLogprobs{\n\t\t\t\t\t\tContent: []openai.ChatCompletionTokenLogprob{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tToken:       \" World\",\n\t\t\t\t\t\t\t\tLogprob:     -0.00055303273,\n\t\t\t\t\t\t\t\tBytes:       []int64{32, 87, 111, 114, 108, 100},\n\t\t\t\t\t\t\t\tTopLogprobs: []openai.ChatCompletionTokenLogprobTopLogprob{},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"4\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.GPT4oMini20240718,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex:        0,\n\t\t\t\t\tDelta:        openai.ChatCompletionStreamChoiceDelta{},\n\t\t\t\t\tFinishReason: \"stop\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\tb, _ := json.Marshal(expectedResponse)\n\t\tt.Logf(\"%d: %s\", ix, string(b))\n\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tchecks.NoError(t, streamErr, \"stream.Recv() failed\")\n\t\tif !compareChatResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n}\n\nfunc TestAzureCreateChatCompletionStreamRateLimitError(t *testing.T) {\n\twantCode := \"429\"\n\twantMessage := \"Requests to the Creates a completion for the chat message Operation under Azure OpenAI API \" +\n\t\t\"version 2023-03-15-preview have exceeded token rate limit of your current OpenAI S0 pricing tier. \" +\n\t\t\"Please retry after 20 seconds. \" +\n\t\t\"Please go here: https://aka.ms/oai/quotaincrease if you would like to further increase the default rate limit.\"\n\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/openai/deployments/gpt-35-turbo/chat/completions\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\tw.WriteHeader(http.StatusTooManyRequests)\n\t\t\t// Send test responses\n\t\t\tdataBytes := []byte(`{\"error\": { \"code\": \"` + wantCode + `\", \"message\": \"` + wantMessage + `\"}}`)\n\t\t\t_, err := w.Write(dataBytes)\n\n\t\t\tchecks.NoError(t, err, \"Write error\")\n\t\t})\n\n\tapiErr := &openai.APIError{}\n\t_, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tif !errors.As(err, &apiErr) {\n\t\tt.Errorf(\"Did not return APIError: %+v\\n\", apiErr)\n\t\treturn\n\t}\n\tif apiErr.HTTPStatusCode != http.StatusTooManyRequests {\n\t\tt.Errorf(\"Did not return HTTPStatusCode got = %d, want = %d\\n\", apiErr.HTTPStatusCode, http.StatusTooManyRequests)\n\t\treturn\n\t}\n\tcode, ok := apiErr.Code.(string)\n\tif !ok || code != wantCode {\n\t\tt.Errorf(\"Did not return Code. got = %v, want = %s\\n\", apiErr.Code, wantCode)\n\t\treturn\n\t}\n\tif apiErr.Message != wantMessage {\n\t\tt.Errorf(\"Did not return Message. got = %s, want = %s\\n\", apiErr.Message, wantMessage)\n\t\treturn\n\t}\n}\n\nfunc TestCreateChatCompletionStreamStreamOptions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tvar dataBytes []byte\n\t\t//nolint:lll\n\t\tdata := `{\"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}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdata = `{\"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}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdata = `{\"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}}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t\tStreamOptions: &openai.StreamOptions{\n\t\t\tIncludeUsage: true,\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.ChatCompletionStreamResponse{\n\t\t{\n\t\t\tID:                \"1\",\n\t\t\tObject:            \"completion\",\n\t\t\tCreated:           1598069254,\n\t\t\tModel:             openai.GPT3Dot5Turbo,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"response1\",\n\t\t\t\t\t},\n\t\t\t\t\tFinishReason: \"max_tokens\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"2\",\n\t\t\tObject:            \"completion\",\n\t\t\tCreated:           1598069255,\n\t\t\tModel:             openai.GPT3Dot5Turbo,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"response2\",\n\t\t\t\t\t},\n\t\t\t\t\tFinishReason: \"max_tokens\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"3\",\n\t\t\tObject:            \"completion\",\n\t\t\tCreated:           1598069256,\n\t\t\tModel:             openai.GPT3Dot5Turbo,\n\t\t\tSystemFingerprint: \"fp_d9767fc5b9\",\n\t\t\tChoices:           []openai.ChatCompletionStreamChoice{},\n\t\t\tUsage: &openai.Usage{\n\t\t\t\tPromptTokens:     1,\n\t\t\t\tCompletionTokens: 1,\n\t\t\t\tTotalTokens:      2,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\tb, _ := json.Marshal(expectedResponse)\n\t\tt.Logf(\"%d: %s\", ix, string(b))\n\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tchecks.NoError(t, streamErr, \"stream.Recv() failed\")\n\t\tif !compareChatResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n\n\t_, streamErr = stream.Recv()\n\n\tchecks.ErrorIs(t, streamErr, io.EOF, \"stream.Recv() did not return EOF when the stream is finished\")\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF when the stream is finished: %v\", streamErr)\n\t}\n}\n\n// Helper funcs.\nfunc compareChatResponses(r1, r2 openai.ChatCompletionStreamResponse) bool {\n\tif r1.ID != r2.ID || r1.Object != r2.Object || r1.Created != r2.Created || r1.Model != r2.Model {\n\t\treturn false\n\t}\n\tif len(r1.Choices) != len(r2.Choices) {\n\t\treturn false\n\t}\n\tfor i := range r1.Choices {\n\t\tif !compareChatStreamResponseChoices(r1.Choices[i], r2.Choices[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\tif r1.Usage != nil || r2.Usage != nil {\n\t\tif r1.Usage == nil || r2.Usage == nil {\n\t\t\treturn false\n\t\t}\n\t\tif r1.Usage.PromptTokens != r2.Usage.PromptTokens || r1.Usage.CompletionTokens != r2.Usage.CompletionTokens ||\n\t\t\tr1.Usage.TotalTokens != r2.Usage.TotalTokens {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestCreateChatCompletionStreamWithReasoningModel(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\tdataBytes := []byte{}\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\t//nolint:lll\n\t\tdataBytes = 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\"}]}`)...)\n\t\tdataBytes = append(dataBytes, []byte(\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxCompletionTokens: 2000,\n\t\tModel:               openai.O3Mini20250131,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.ChatCompletionStreamResponse{\n\t\t{\n\t\t\tID:                \"1\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.O3Mini20250131,\n\t\t\tSystemFingerprint: \"fp_mini\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tRole: \"assistant\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"2\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.O3Mini20250131,\n\t\t\tSystemFingerprint: \"fp_mini\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \"Hello\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"3\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.O3Mini20250131,\n\t\t\tSystemFingerprint: \"fp_mini\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \" from\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"4\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.O3Mini20250131,\n\t\t\tSystemFingerprint: \"fp_mini\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex: 0,\n\t\t\t\t\tDelta: openai.ChatCompletionStreamChoiceDelta{\n\t\t\t\t\t\tContent: \" O3Mini\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:                \"5\",\n\t\t\tObject:            \"chat.completion.chunk\",\n\t\t\tCreated:           1729585728,\n\t\t\tModel:             openai.O3Mini20250131,\n\t\t\tSystemFingerprint: \"fp_mini\",\n\t\t\tChoices: []openai.ChatCompletionStreamChoice{\n\t\t\t\t{\n\t\t\t\t\tIndex:        0,\n\t\t\t\t\tDelta:        openai.ChatCompletionStreamChoiceDelta{},\n\t\t\t\t\tFinishReason: \"stop\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\tb, _ := json.Marshal(expectedResponse)\n\t\tt.Logf(\"%d: %s\", ix, string(b))\n\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tchecks.NoError(t, streamErr, \"stream.Recv() failed\")\n\t\tif !compareChatResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamReasoningValidatorFails(t *testing.T) {\n\tclient, _, _ := setupOpenAITestServer()\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 100, // This will trigger the validator to fail\n\t\tModel:     openai.O3Mini,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\n\tif stream != nil {\n\t\tt.Error(\"Expected nil stream when validation fails\")\n\t\tstream.Close()\n\t}\n\n\tif !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {\n\t\tt.Errorf(\"Expected ErrReasoningModelMaxTokensDeprecated, got: %v\", err)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamO3ReasoningValidatorFails(t *testing.T) {\n\tclient, _, _ := setupOpenAITestServer()\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 100, // This will trigger the validator to fail\n\t\tModel:     openai.O3,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\n\tif stream != nil {\n\t\tt.Error(\"Expected nil stream when validation fails\")\n\t\tstream.Close()\n\t}\n\n\tif !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {\n\t\tt.Errorf(\"Expected ErrReasoningModelMaxTokensDeprecated for O3, got: %v\", err)\n\t}\n}\n\nfunc TestCreateChatCompletionStreamO4MiniReasoningValidatorFails(t *testing.T) {\n\tclient, _, _ := setupOpenAITestServer()\n\n\tstream, err := client.CreateChatCompletionStream(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 100, // This will trigger the validator to fail\n\t\tModel:     openai.O4Mini,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t\tStream: true,\n\t})\n\n\tif stream != nil {\n\t\tt.Error(\"Expected nil stream when validation fails\")\n\t\tstream.Close()\n\t}\n\n\tif !errors.Is(err, openai.ErrReasoningModelMaxTokensDeprecated) {\n\t\tt.Errorf(\"Expected ErrReasoningModelMaxTokensDeprecated for O4Mini, got: %v\", err)\n\t}\n}\n\nfunc compareChatStreamResponseChoices(c1, c2 openai.ChatCompletionStreamChoice) bool {\n\tif c1.Index != c2.Index {\n\t\treturn false\n\t}\n\tif c1.Delta.Content != c2.Delta.Content {\n\t\treturn false\n\t}\n\tif c1.FinishReason != c2.FinishReason {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "chat_test.go",
    "content": "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\"testing\"\n\t\"time\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nconst (\n\txCustomHeader      = \"X-CUSTOM-HEADER\"\n\txCustomHeaderValue = \"test\"\n)\n\nvar rateLimitHeaders = map[string]any{\n\t\"x-ratelimit-limit-requests\":     60,\n\t\"x-ratelimit-limit-tokens\":       150000,\n\t\"x-ratelimit-remaining-requests\": 59,\n\t\"x-ratelimit-remaining-tokens\":   149984,\n\t\"x-ratelimit-reset-requests\":     \"1s\",\n\t\"x-ratelimit-reset-tokens\":       \"6m0s\",\n}\n\nfunc TestChatCompletionsWrongModel(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\tctx := context.Background()\n\n\treq := openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     \"ada\",\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t}\n\t_, err := client.CreateChatCompletion(ctx, req)\n\tmsg := fmt.Sprintf(\"CreateChatCompletion should return wrong model error, returned: %s\", err)\n\tchecks.ErrorIs(t, err, openai.ErrChatCompletionInvalidModel, msg)\n}\n\nfunc TestO1ModelsChatCompletionsDeprecatedFields(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tin            openai.ChatCompletionRequest\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"o1-preview_MaxTokens_deprecated\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxTokens: 5,\n\t\t\t\tModel:     openai.O1Preview,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelMaxTokensDeprecated,\n\t\t},\n\t\t{\n\t\t\tname: \"o1-mini_MaxTokens_deprecated\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxTokens: 5,\n\t\t\t\tModel:     openai.O1Mini,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelMaxTokensDeprecated,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := openai.DefaultConfig(\"whatever\")\n\t\t\tconfig.BaseURL = \"http://localhost/v1\"\n\t\t\tclient := openai.NewClientWithConfig(config)\n\t\t\tctx := context.Background()\n\n\t\t\t_, err := client.CreateChatCompletion(ctx, tt.in)\n\t\t\tchecks.HasError(t, err)\n\t\t\tmsg := fmt.Sprintf(\"CreateChatCompletion should return wrong model error, returned: %s\", err)\n\t\t\tchecks.ErrorIs(t, err, tt.expectedError, msg)\n\t\t})\n\t}\n}\n\nfunc TestO1ModelsChatCompletionsBetaLimitations(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tin            openai.ChatCompletionRequest\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"log_probs_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tLogProbs:            true,\n\t\t\t\tModel:               openai.O1Preview,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsLogprobs,\n\t\t},\n\t\t{\n\t\t\tname: \"set_temperature_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O1Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(2),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_top_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O1Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_n_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O1Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(1),\n\t\t\t\tN:           2,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_presence_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O1Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPresencePenalty: float32(1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_frequency_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O1Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tFrequencyPenalty: float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := openai.DefaultConfig(\"whatever\")\n\t\t\tconfig.BaseURL = \"http://localhost/v1\"\n\t\t\tclient := openai.NewClientWithConfig(config)\n\t\t\tctx := context.Background()\n\n\t\t\t_, err := client.CreateChatCompletion(ctx, tt.in)\n\t\t\tchecks.HasError(t, err)\n\t\t\tmsg := fmt.Sprintf(\"CreateChatCompletion should return wrong model error, returned: %s\", err)\n\t\t\tchecks.ErrorIs(t, err, tt.expectedError, msg)\n\t\t})\n\t}\n}\n\nfunc TestO3ModelsChatCompletionsBetaLimitations(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tin            openai.ChatCompletionRequest\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"log_probs_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tLogProbs:            true,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsLogprobs,\n\t\t},\n\t\t{\n\t\t\tname: \"set_temperature_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(2),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_top_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_n_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(1),\n\t\t\t\tN:           2,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_presence_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPresencePenalty: float32(1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_frequency_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.O3Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tFrequencyPenalty: float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := openai.DefaultConfig(\"whatever\")\n\t\t\tconfig.BaseURL = \"http://localhost/v1\"\n\t\t\tclient := openai.NewClientWithConfig(config)\n\t\t\tctx := context.Background()\n\n\t\t\t_, err := client.CreateChatCompletion(ctx, tt.in)\n\t\t\tchecks.HasError(t, err)\n\t\t\tmsg := fmt.Sprintf(\"CreateChatCompletion should return wrong model error, returned: %s\", err)\n\t\t\tchecks.ErrorIs(t, err, tt.expectedError, msg)\n\t\t})\n\t}\n}\n\nfunc TestGPT5ModelsChatCompletionsBetaLimitations(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tin            openai.ChatCompletionRequest\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"log_probs_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tLogProbs:            true,\n\t\t\t\tModel:               openai.GPT5,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsLogprobs,\n\t\t},\n\t\t{\n\t\t\tname: \"set_temperature_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.GPT5Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(2),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_top_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.GPT5Nano,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_n_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.GPT5ChatLatest,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tTemperature: float32(1),\n\t\t\t\tTopP:        float32(1),\n\t\t\t\tN:           2,\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_presence_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.GPT5,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPresencePenalty: float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t\t{\n\t\t\tname: \"set_frequency_penalty_unsupported\",\n\t\t\tin: openai.ChatCompletionRequest{\n\t\t\t\tMaxCompletionTokens: 1000,\n\t\t\t\tModel:               openai.GPT5Mini,\n\t\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRole: openai.ChatMessageRoleAssistant,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tFrequencyPenalty: float32(0.1),\n\t\t\t},\n\t\t\texpectedError: openai.ErrReasoningModelLimitationsOther,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := openai.DefaultConfig(\"whatever\")\n\t\t\tconfig.BaseURL = \"http://localhost/v1\"\n\t\t\tclient := openai.NewClientWithConfig(config)\n\t\t\tctx := context.Background()\n\n\t\t\t_, err := client.CreateChatCompletion(ctx, tt.in)\n\t\t\tchecks.HasError(t, err)\n\t\t\tmsg := fmt.Sprintf(\"CreateChatCompletion should return wrong model error, returned: %s\", err)\n\t\t\tchecks.ErrorIs(t, err, tt.expectedError, msg)\n\t\t})\n\t}\n}\n\nfunc TestChatRequestOmitEmpty(t *testing.T) {\n\tdata, err := json.Marshal(openai.ChatCompletionRequest{\n\t\t// We set model b/c it's required, so omitempty doesn't make sense\n\t\tModel: \"gpt-4\",\n\t})\n\tchecks.NoError(t, err)\n\n\t// messages is also required so isn't omitted\n\tconst expected = `{\"model\":\"gpt-4\",\"messages\":null}`\n\tif string(data) != expected {\n\t\tt.Errorf(\"expected JSON with all empty fields to be %v but was %v\", expected, string(data))\n\t}\n}\n\nfunc TestChatCompletionsWithStream(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\tctx := context.Background()\n\n\treq := openai.ChatCompletionRequest{\n\t\tStream: true,\n\t}\n\t_, err := client.CreateChatCompletion(ctx, req)\n\tchecks.ErrorIs(t, err, openai.ErrChatCompletionStreamNotSupported, \"unexpected error\")\n}\n\n// TestCompletions Tests the completions endpoint of the API using the mocked server.\nfunc TestChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n}\n\n// TestCompletions Tests the completions endpoint of the API using the mocked server.\nfunc TestO1ModelChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tModel:               openai.O1Preview,\n\t\tMaxCompletionTokens: 1000,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n}\n\nfunc TestO3ModelChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tModel:               openai.O3Mini,\n\t\tMaxCompletionTokens: 1000,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n}\n\nfunc TestDeepseekR1ModelChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleDeepseekR1ChatCompletionEndpoint)\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tModel:               \"deepseek-reasoner\",\n\t\tMaxCompletionTokens: 100,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n}\n\n// TestCompletions Tests the completions endpoint of the API using the mocked server.\nfunc TestChatCompletionsWithHeaders(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\tresp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n\n\ta := resp.Header().Get(xCustomHeader)\n\t_ = a\n\tif resp.Header().Get(xCustomHeader) != xCustomHeaderValue {\n\t\tt.Errorf(\"expected header %s to be %s\", xCustomHeader, xCustomHeaderValue)\n\t}\n}\n\n// TestChatCompletionsWithRateLimitHeaders Tests the completions endpoint of the API using the mocked server.\nfunc TestChatCompletionsWithRateLimitHeaders(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\tresp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateChatCompletion error\")\n\n\theaders := resp.GetRateLimitHeaders()\n\tresetRequests := headers.ResetRequests.String()\n\tif resetRequests != rateLimitHeaders[\"x-ratelimit-reset-requests\"] {\n\t\tt.Errorf(\"expected resetRequests %s to be %s\", resetRequests, rateLimitHeaders[\"x-ratelimit-reset-requests\"])\n\t}\n\tresetRequestsTime := headers.ResetRequests.Time()\n\tif resetRequestsTime.Before(time.Now()) {\n\t\tt.Errorf(\"unexpected reset requests: %v\", resetRequestsTime)\n\t}\n\n\tbs1, _ := json.Marshal(headers)\n\tbs2, _ := json.Marshal(rateLimitHeaders)\n\tif string(bs1) != string(bs2) {\n\t\tt.Errorf(\"expected rate limit header %s to be %s\", bs2, bs1)\n\t}\n}\n\n// TestChatCompletionsFunctions tests including a function call.\nfunc TestChatCompletionsFunctions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/chat/completions\", handleChatCompletionEndpoint)\n\tt.Run(\"bytes\", func(t *testing.T) {\n\t\t//nolint:lll\n\t\tmsg := 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\"]}`)\n\t\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo0613,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefinition{{\n\t\t\t\tName:       \"test\",\n\t\t\t\tParameters: &msg,\n\t\t\t}},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateChatCompletion with functions error\")\n\t})\n\tt.Run(\"struct\", func(t *testing.T) {\n\t\ttype testMessage struct {\n\t\t\tCount int      `json:\"count\"`\n\t\t\tWords []string `json:\"words\"`\n\t\t}\n\t\tmsg := testMessage{\n\t\t\tCount: 2,\n\t\t\tWords: []string{\"hello\", \"world\"},\n\t\t}\n\t\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo0613,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefinition{{\n\t\t\t\tName:       \"test\",\n\t\t\t\tParameters: &msg,\n\t\t\t}},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateChatCompletion with functions error\")\n\t})\n\tt.Run(\"JSONSchemaDefinition\", func(t *testing.T) {\n\t\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo0613,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefinition{{\n\t\t\t\tName: \"test\",\n\t\t\t\tParameters: &jsonschema.Definition{\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"count\": {\n\t\t\t\t\t\t\tType:        jsonschema.Number,\n\t\t\t\t\t\t\tDescription: \"total number of words in sentence\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"words\": {\n\t\t\t\t\t\t\tType:        jsonschema.Array,\n\t\t\t\t\t\t\tDescription: \"list of words in sentence\",\n\t\t\t\t\t\t\tItems: &jsonschema.Definition{\n\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"enumTest\": {\n\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\tEnum: []string{\"hello\", \"world\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateChatCompletion with functions error\")\n\t})\n\tt.Run(\"JSONSchemaDefinitionWithFunctionDefine\", func(t *testing.T) {\n\t\t// this is a compatibility check\n\t\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo0613,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefine{{\n\t\t\t\tName: \"test\",\n\t\t\t\tParameters: &jsonschema.Definition{\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"count\": {\n\t\t\t\t\t\t\tType:        jsonschema.Number,\n\t\t\t\t\t\t\tDescription: \"total number of words in sentence\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"words\": {\n\t\t\t\t\t\t\tType:        jsonschema.Array,\n\t\t\t\t\t\t\tDescription: \"list of words in sentence\",\n\t\t\t\t\t\t\tItems: &jsonschema.Definition{\n\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"enumTest\": {\n\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\tEnum: []string{\"hello\", \"world\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateChatCompletion with functions error\")\n\t})\n\tt.Run(\"StructuredOutputs\", func(t *testing.T) {\n\t\ttype testMessage struct {\n\t\t\tCount int      `json:\"count\"`\n\t\t\tWords []string `json:\"words\"`\n\t\t}\n\t\tmsg := testMessage{\n\t\t\tCount: 2,\n\t\t\tWords: []string{\"hello\", \"world\"},\n\t\t}\n\t\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo0613,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFunctions: []openai.FunctionDefinition{{\n\t\t\t\tName:       \"test\",\n\t\t\t\tStrict:     true,\n\t\t\t\tParameters: &msg,\n\t\t\t}},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateChatCompletion with functions error\")\n\t})\n}\n\nfunc TestAzureChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/openai/deployments/*\", handleChatCompletionEndpoint)\n\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: \"Hello!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateAzureChatCompletion error\")\n}\n\nfunc TestMultipartChatCompletions(t *testing.T) {\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/openai/deployments/*\", handleChatCompletionEndpoint)\n\n\t_, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole: openai.ChatMessageRoleUser,\n\t\t\t\tMultiContent: []openai.ChatMessagePart{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: openai.ChatMessagePartTypeText,\n\t\t\t\t\t\tText: \"Hello!\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType: openai.ChatMessagePartTypeImageURL,\n\t\t\t\t\t\tImageURL: &openai.ChatMessageImageURL{\n\t\t\t\t\t\t\tURL:    \"URL\",\n\t\t\t\t\t\t\tDetail: openai.ImageURLDetailLow,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateAzureChatCompletion error\")\n}\n\nfunc TestMultipartChatMessageSerialization(t *testing.T) {\n\tjsonText := `[{\"role\":\"system\",\"content\":\"system-message\"},` +\n\t\t`{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"nice-text\"},` +\n\t\t`{\"type\":\"image_url\",\"image_url\":{\"url\":\"URL\",\"detail\":\"high\"}}]}]`\n\n\tvar msgs []openai.ChatCompletionMessage\n\terr := json.Unmarshal([]byte(jsonText), &msgs)\n\tif err != nil {\n\t\tt.Fatalf(\"Expected no error: %s\", err)\n\t}\n\tif len(msgs) != 2 {\n\t\tt.Errorf(\"unexpected number of messages\")\n\t}\n\tif msgs[0].Role != \"system\" || msgs[0].Content != \"system-message\" || msgs[0].MultiContent != nil {\n\t\tt.Errorf(\"invalid user message: %v\", msgs[0])\n\t}\n\tif msgs[1].Role != \"user\" || msgs[1].Content != \"\" || len(msgs[1].MultiContent) != 2 {\n\t\tt.Errorf(\"invalid user message\")\n\t}\n\tparts := msgs[1].MultiContent\n\tif parts[0].Type != \"text\" || parts[0].Text != \"nice-text\" {\n\t\tt.Errorf(\"invalid text part: %v\", parts[0])\n\t}\n\tif parts[1].Type != \"image_url\" || parts[1].ImageURL.URL != \"URL\" || parts[1].ImageURL.Detail != \"high\" {\n\t\tt.Errorf(\"invalid image_url part\")\n\t}\n\n\ts, err := json.Marshal(msgs)\n\tif err != nil {\n\t\tt.Fatalf(\"Expected no error: %s\", err)\n\t}\n\tres := strings.ReplaceAll(string(s), \" \", \"\")\n\tif res != jsonText {\n\t\tt.Fatalf(\"invalid message: %s\", string(s))\n\t}\n\n\tinvalidMsg := []openai.ChatCompletionMessage{\n\t\t{\n\t\t\tRole:    \"user\",\n\t\t\tContent: \"some-text\",\n\t\t\tMultiContent: []openai.ChatMessagePart{\n\t\t\t\t{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: \"nice-text\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = json.Marshal(invalidMsg)\n\tif !errors.Is(err, openai.ErrContentFieldsMisused) {\n\t\tt.Fatalf(\"Expected error: %s\", err)\n\t}\n\n\terr = json.Unmarshal([]byte(`[\"not-a-message\"]`), &msgs)\n\tif err == nil {\n\t\tt.Fatalf(\"Expected error\")\n\t}\n\n\temptyMultiContentMsg := openai.ChatCompletionMessage{\n\t\tRole:         \"user\",\n\t\tMultiContent: []openai.ChatMessagePart{},\n\t}\n\ts, err = json.Marshal(emptyMultiContentMsg)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error\")\n\t}\n\tres = strings.ReplaceAll(string(s), \" \", \"\")\n\tif res != `{\"role\":\"user\"}` {\n\t\tt.Fatalf(\"invalid message: %s\", string(s))\n\t}\n}\n\n// handleChatCompletionEndpoint Handles the ChatGPT completion endpoint by the test server.\nfunc handleChatCompletionEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// completions only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar completionReq openai.ChatCompletionRequest\n\tif completionReq, err = getChatCompletionBody(r); err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tres := openai.ChatCompletionResponse{\n\t\tID:      strconv.Itoa(int(time.Now().Unix())),\n\t\tObject:  \"test-object\",\n\t\tCreated: time.Now().Unix(),\n\t\t// would be nice to validate Model during testing, but\n\t\t// this may not be possible with how much upkeep\n\t\t// would be required / wouldn't make much sense\n\t\tModel: completionReq.Model,\n\t}\n\t// create completions\n\tn := completionReq.N\n\tif n == 0 {\n\t\tn = 1\n\t}\n\tfor i := 0; i < n; i++ {\n\t\t// if there are functions, include them\n\t\tif len(completionReq.Functions) > 0 {\n\t\t\tvar fcb []byte\n\t\t\tb := completionReq.Functions[0].Parameters\n\t\t\tfcb, err = json.Marshal(b)\n\t\t\tif err != nil {\n\t\t\t\thttp.Error(w, \"could not marshal function parameters\", http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tres.Choices = append(res.Choices, openai.ChatCompletionChoice{\n\t\t\t\tMessage: openai.ChatCompletionMessage{\n\t\t\t\t\tRole: openai.ChatMessageRoleFunction,\n\t\t\t\t\t// this is valid json so it should be fine\n\t\t\t\t\tFunctionCall: &openai.FunctionCall{\n\t\t\t\t\t\tName:      completionReq.Functions[0].Name,\n\t\t\t\t\t\tArguments: string(fcb),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tIndex: i,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\t// generate a random string of length completionReq.Length\n\t\tcompletionStr := strings.Repeat(\"a\", completionReq.MaxTokens)\n\n\t\tres.Choices = append(res.Choices, openai.ChatCompletionChoice{\n\t\t\tMessage: openai.ChatCompletionMessage{\n\t\t\t\tRole:    openai.ChatMessageRoleAssistant,\n\t\t\t\tContent: completionStr,\n\t\t\t},\n\t\t\tIndex: i,\n\t\t})\n\t}\n\tinputTokens := numTokens(completionReq.Messages[0].Content) * n\n\tcompletionTokens := completionReq.MaxTokens * n\n\tres.Usage = openai.Usage{\n\t\tPromptTokens:     inputTokens,\n\t\tCompletionTokens: completionTokens,\n\t\tTotalTokens:      inputTokens + completionTokens,\n\t}\n\tresBytes, _ = json.Marshal(res)\n\tw.Header().Set(xCustomHeader, xCustomHeaderValue)\n\tfor k, v := range rateLimitHeaders {\n\t\tswitch val := v.(type) {\n\t\tcase int:\n\t\t\tw.Header().Set(k, strconv.Itoa(val))\n\t\tdefault:\n\t\t\tw.Header().Set(k, fmt.Sprintf(\"%s\", v))\n\t\t}\n\t}\n\tfmt.Fprintln(w, string(resBytes))\n}\n\nfunc handleDeepseekR1ChatCompletionEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// completions only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar completionReq openai.ChatCompletionRequest\n\tif completionReq, err = getChatCompletionBody(r); err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tres := openai.ChatCompletionResponse{\n\t\tID:      strconv.Itoa(int(time.Now().Unix())),\n\t\tObject:  \"test-object\",\n\t\tCreated: time.Now().Unix(),\n\t\t// would be nice to validate Model during testing, but\n\t\t// this may not be possible with how much upkeep\n\t\t// would be required / wouldn't make much sense\n\t\tModel: completionReq.Model,\n\t}\n\t// create completions\n\tn := completionReq.N\n\tif n == 0 {\n\t\tn = 1\n\t}\n\tif completionReq.MaxCompletionTokens == 0 {\n\t\tcompletionReq.MaxCompletionTokens = 1000\n\t}\n\tfor i := 0; i < n; i++ {\n\t\treasoningContent := \"User says hello! And I need to reply\"\n\t\tcompletionStr := strings.Repeat(\"a\", completionReq.MaxCompletionTokens-numTokens(reasoningContent))\n\t\tres.Choices = append(res.Choices, openai.ChatCompletionChoice{\n\t\t\tMessage: openai.ChatCompletionMessage{\n\t\t\t\tRole:             openai.ChatMessageRoleAssistant,\n\t\t\t\tReasoningContent: reasoningContent,\n\t\t\t\tContent:          completionStr,\n\t\t\t},\n\t\t\tIndex: i,\n\t\t})\n\t}\n\tinputTokens := numTokens(completionReq.Messages[0].Content) * n\n\tcompletionTokens := completionReq.MaxTokens * n\n\tres.Usage = openai.Usage{\n\t\tPromptTokens:     inputTokens,\n\t\tCompletionTokens: completionTokens,\n\t\tTotalTokens:      inputTokens + completionTokens,\n\t}\n\tresBytes, _ = json.Marshal(res)\n\tw.Header().Set(xCustomHeader, xCustomHeaderValue)\n\tfor k, v := range rateLimitHeaders {\n\t\tswitch val := v.(type) {\n\t\tcase int:\n\t\t\tw.Header().Set(k, strconv.Itoa(val))\n\t\tdefault:\n\t\t\tw.Header().Set(k, fmt.Sprintf(\"%s\", v))\n\t\t}\n\t}\n\tfmt.Fprintln(w, string(resBytes))\n}\n\n// getChatCompletionBody Returns the body of the request to create a completion.\nfunc getChatCompletionBody(r *http.Request) (openai.ChatCompletionRequest, error) {\n\tcompletion := openai.ChatCompletionRequest{}\n\t// read the request body\n\treqBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn openai.ChatCompletionRequest{}, err\n\t}\n\terr = json.Unmarshal(reqBody, &completion)\n\tif err != nil {\n\t\treturn openai.ChatCompletionRequest{}, err\n\t}\n\treturn completion, nil\n}\n\nfunc TestFinishReason(t *testing.T) {\n\tc := &openai.ChatCompletionChoice{\n\t\tFinishReason: openai.FinishReasonNull,\n\t}\n\tresBytes, _ := json.Marshal(c)\n\tif !strings.Contains(string(resBytes), `\"finish_reason\":null`) {\n\t\tt.Error(\"null should not be quoted\")\n\t}\n\n\tc.FinishReason = \"\"\n\n\tresBytes, _ = json.Marshal(c)\n\tif !strings.Contains(string(resBytes), `\"finish_reason\":null`) {\n\t\tt.Error(\"null should not be quoted\")\n\t}\n\n\totherReasons := []openai.FinishReason{\n\t\topenai.FinishReasonStop,\n\t\topenai.FinishReasonLength,\n\t\topenai.FinishReasonFunctionCall,\n\t\topenai.FinishReasonContentFilter,\n\t}\n\tfor _, r := range otherReasons {\n\t\tc.FinishReason = r\n\t\tresBytes, _ = json.Marshal(c)\n\t\tif !strings.Contains(string(resBytes), fmt.Sprintf(`\"finish_reason\":\"%s\"`, r)) {\n\t\t\tt.Errorf(\"%s should be quoted\", r)\n\t\t}\n\t}\n}\n\nfunc TestChatCompletionResponseFormatJSONSchema_UnmarshalJSON(t *testing.T) {\n\ttype args struct {\n\t\tdata []byte\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"\",\n\t\t\targs{\n\t\t\t\tdata: []byte(`{\n      \"name\":   \"math_response\",\n      \"strict\": true,\n      \"schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"steps\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"explanation\": { \"type\": \"string\" },\n                \"output\":      { \"type\": \"string\" }\n              },\n              \"required\": [\"explanation\",\"output\"],\n              \"additionalProperties\": false\n            }\n          },\n          \"final_answer\": { \"type\": \"string\" }\n        },\n        \"required\": [\"steps\",\"final_answer\"],\n        \"additionalProperties\": false\n      }\n  }`),\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\targs{\n\t\t\t\tdata: []byte(`{\n      \"name\":   \"math_response\",\n      \"strict\": true,\n      \"schema\": null\n  }`),\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\targs{\n\t\t\t\tdata: []byte(`[123,456]`),\n\t\t\t},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\targs{\n\t\t\t\tdata: []byte(`{\n      \"name\":   \"math_response\",\n      \"strict\": true,\n      \"schema\": 123456\n  }`),\n\t\t\t},\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar r openai.ChatCompletionResponseFormatJSONSchema\n\t\t\terr := r.UnmarshalJSON(tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestChatCompletionRequest_UnmarshalJSON(t *testing.T) {\n\ttype args struct {\n\t\tbs []byte\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"\",\n\t\t\targs{bs: []byte(`{\n  \"model\": \"llama3-1b\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are a helpful math tutor.\" },\n    { \"role\": \"user\",   \"content\": \"solve 8x + 31 = 2\" }\n  ],\n  \"response_format\": {\n    \"type\": \"json_schema\",\n    \"json_schema\": {\n      \"name\":   \"math_response\",\n      \"strict\": true,\n      \"schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"steps\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"explanation\": { \"type\": \"string\" },\n                \"output\":      { \"type\": \"string\" }\n              },\n              \"required\": [\"explanation\",\"output\"],\n              \"additionalProperties\": false\n            }\n          },\n          \"final_answer\": { \"type\": \"string\" }\n        },\n        \"required\": [\"steps\",\"final_answer\"],\n        \"additionalProperties\": false\n      }\n    }\n  }\n}`)},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar m openai.ChatCompletionRequest\n\t\t\terr := json.Unmarshal(tt.args.bs, &m)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "client.go",
    "content": "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 \"github.com/sashabaranov/go-openai/internal\"\n)\n\n// Client is OpenAI GPT-3 API client.\ntype Client struct {\n\tconfig ClientConfig\n\n\trequestBuilder    utils.RequestBuilder\n\tcreateFormBuilder func(io.Writer) utils.FormBuilder\n}\n\ntype Response interface {\n\tSetHeader(http.Header)\n}\n\ntype httpHeader http.Header\n\nfunc (h *httpHeader) SetHeader(header http.Header) {\n\t*h = httpHeader(header)\n}\n\nfunc (h *httpHeader) Header() http.Header {\n\treturn http.Header(*h)\n}\n\nfunc (h *httpHeader) GetRateLimitHeaders() RateLimitHeaders {\n\treturn newRateLimitHeaders(h.Header())\n}\n\ntype RawResponse struct {\n\tio.ReadCloser\n\n\thttpHeader\n}\n\n// NewClient creates new OpenAI API client.\nfunc NewClient(authToken string) *Client {\n\tconfig := DefaultConfig(authToken)\n\treturn NewClientWithConfig(config)\n}\n\n// NewClientWithConfig creates new OpenAI API client for specified config.\nfunc NewClientWithConfig(config ClientConfig) *Client {\n\treturn &Client{\n\t\tconfig:         config,\n\t\trequestBuilder: utils.NewRequestBuilder(),\n\t\tcreateFormBuilder: func(body io.Writer) utils.FormBuilder {\n\t\t\treturn utils.NewFormBuilder(body)\n\t\t},\n\t}\n}\n\n// NewOrgClient creates new OpenAI API client for specified Organization ID.\n//\n// Deprecated: Please use NewClientWithConfig.\nfunc NewOrgClient(authToken, org string) *Client {\n\tconfig := DefaultConfig(authToken)\n\tconfig.OrgID = org\n\treturn NewClientWithConfig(config)\n}\n\ntype requestOptions struct {\n\tbody   any\n\theader http.Header\n}\n\ntype requestOption func(*requestOptions)\n\nfunc withBody(body any) requestOption {\n\treturn func(args *requestOptions) {\n\t\targs.body = body\n\t}\n}\n\nfunc withExtraBody(extraBody map[string]any) requestOption {\n\treturn func(args *requestOptions) {\n\t\t// Assert that args.body is a map[string]any.\n\t\tbodyMap, ok := args.body.(map[string]any)\n\t\tif ok {\n\t\t\t// If it's a map[string]any then only add extraBody\n\t\t\t// fields to args.body otherwise keep only fields in request struct.\n\t\t\tfor key, value := range extraBody {\n\t\t\t\tbodyMap[key] = value\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc withContentType(contentType string) requestOption {\n\treturn func(args *requestOptions) {\n\t\targs.header.Set(\"Content-Type\", contentType)\n\t}\n}\n\nfunc withBetaAssistantVersion(version string) requestOption {\n\treturn func(args *requestOptions) {\n\t\targs.header.Set(\"OpenAI-Beta\", fmt.Sprintf(\"assistants=%s\", version))\n\t}\n}\n\nfunc (c *Client) newRequest(ctx context.Context, method, url string, setters ...requestOption) (*http.Request, error) {\n\t// Default Options\n\targs := &requestOptions{\n\t\tbody:   nil,\n\t\theader: make(http.Header),\n\t}\n\tfor _, setter := range setters {\n\t\tsetter(args)\n\t}\n\treq, err := c.requestBuilder.Build(ctx, method, url, args.body, args.header)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.setCommonHeaders(req)\n\treturn req, nil\n}\n\nfunc (c *Client) sendRequest(req *http.Request, v Response) error {\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t// Check whether Content-Type is already set, Upload Files API requires\n\t// Content-Type == multipart/form-data\n\tcontentType := req.Header.Get(\"Content-Type\")\n\tif contentType == \"\" {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\tres, err := c.config.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer res.Body.Close()\n\n\tif v != nil {\n\t\tv.SetHeader(res.Header)\n\t}\n\n\tif isFailureStatusCode(res) {\n\t\treturn c.handleErrorResp(res)\n\t}\n\n\treturn decodeResponse(res.Body, v)\n}\n\nfunc (c *Client) sendRequestRaw(req *http.Request) (response RawResponse, err error) {\n\tresp, err := c.config.HTTPClient.Do(req) //nolint:bodyclose // body should be closed by outer function\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif isFailureStatusCode(resp) {\n\t\terr = c.handleErrorResp(resp)\n\t\treturn\n\t}\n\n\tresponse.SetHeader(resp.Header)\n\tresponse.ReadCloser = resp.Body\n\treturn\n}\n\nfunc sendRequestStream[T streamable](client *Client, req *http.Request) (*streamReader[T], error) {\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"text/event-stream\")\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\n\treq.Header.Set(\"Connection\", \"keep-alive\")\n\n\tresp, err := client.config.HTTPClient.Do(req) //nolint:bodyclose // body is closed in stream.Close()\n\tif err != nil {\n\t\treturn new(streamReader[T]), err\n\t}\n\tif isFailureStatusCode(resp) {\n\t\treturn new(streamReader[T]), client.handleErrorResp(resp)\n\t}\n\treturn &streamReader[T]{\n\t\temptyMessagesLimit: client.config.EmptyMessagesLimit,\n\t\treader:             bufio.NewReader(resp.Body),\n\t\tresponse:           resp,\n\t\terrAccumulator:     utils.NewErrorAccumulator(),\n\t\tunmarshaler:        &utils.JSONUnmarshaler{},\n\t\thttpHeader:         httpHeader(resp.Header),\n\t}, nil\n}\n\nfunc (c *Client) setCommonHeaders(req *http.Request) {\n\t// https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#authentication\n\tswitch c.config.APIType {\n\tcase APITypeAzure, APITypeCloudflareAzure:\n\t\t// Azure API Key authentication\n\t\treq.Header.Set(AzureAPIKeyHeader, c.config.authToken)\n\tcase APITypeAnthropic:\n\t\t// https://docs.anthropic.com/en/api/versioning\n\t\treq.Header.Set(\"anthropic-version\", c.config.APIVersion)\n\tcase APITypeOpenAI, APITypeAzureAD:\n\t\tfallthrough\n\tdefault:\n\t\tif c.config.authToken != \"\" {\n\t\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", c.config.authToken))\n\t\t}\n\t}\n\n\tif c.config.OrgID != \"\" {\n\t\treq.Header.Set(\"OpenAI-Organization\", c.config.OrgID)\n\t}\n}\n\nfunc isFailureStatusCode(resp *http.Response) bool {\n\treturn resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest\n}\n\nfunc decodeResponse(body io.Reader, v any) error {\n\tif v == nil {\n\t\treturn nil\n\t}\n\n\tswitch o := v.(type) {\n\tcase *string:\n\t\treturn decodeString(body, o)\n\tcase *audioTextResponse:\n\t\treturn decodeString(body, &o.Text)\n\tdefault:\n\t\treturn json.NewDecoder(body).Decode(v)\n\t}\n}\n\nfunc decodeString(body io.Reader, output *string) error {\n\tb, err := io.ReadAll(body)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*output = string(b)\n\treturn nil\n}\n\ntype fullURLOptions struct {\n\tmodel string\n}\n\ntype fullURLOption func(*fullURLOptions)\n\nfunc withModel(model string) fullURLOption {\n\treturn func(args *fullURLOptions) {\n\t\targs.model = model\n\t}\n}\n\nvar azureDeploymentsEndpoints = []string{\n\t\"/completions\",\n\t\"/embeddings\",\n\t\"/chat/completions\",\n\t\"/audio/transcriptions\",\n\t\"/audio/translations\",\n\t\"/audio/speech\",\n\t\"/images/generations\",\n}\n\n// fullURL returns full URL for request.\nfunc (c *Client) fullURL(suffix string, setters ...fullURLOption) string {\n\tbaseURL := strings.TrimRight(c.config.BaseURL, \"/\")\n\targs := fullURLOptions{}\n\tfor _, setter := range setters {\n\t\tsetter(&args)\n\t}\n\n\tif c.config.APIType == APITypeAzure || c.config.APIType == APITypeAzureAD {\n\t\tbaseURL = c.baseURLWithAzureDeployment(baseURL, suffix, args.model)\n\t}\n\n\tif c.config.APIVersion != \"\" {\n\t\tsuffix = c.suffixWithAPIVersion(suffix)\n\t}\n\treturn fmt.Sprintf(\"%s%s\", baseURL, suffix)\n}\n\nfunc (c *Client) suffixWithAPIVersion(suffix string) string {\n\tparsedSuffix, err := url.Parse(suffix)\n\tif err != nil {\n\t\tpanic(\"failed to parse url suffix\")\n\t}\n\tquery := parsedSuffix.Query()\n\tquery.Add(\"api-version\", c.config.APIVersion)\n\treturn fmt.Sprintf(\"%s?%s\", parsedSuffix.Path, query.Encode())\n}\n\nfunc (c *Client) baseURLWithAzureDeployment(baseURL, suffix, model string) (newBaseURL string) {\n\tbaseURL = fmt.Sprintf(\"%s/%s\", strings.TrimRight(baseURL, \"/\"), azureAPIPrefix)\n\tif containsSubstr(azureDeploymentsEndpoints, suffix) {\n\t\tazureDeploymentName := c.config.GetAzureDeploymentByModel(model)\n\t\tif azureDeploymentName == \"\" {\n\t\t\tazureDeploymentName = \"UNKNOWN\"\n\t\t}\n\t\tbaseURL = fmt.Sprintf(\"%s/%s/%s\", baseURL, azureDeploymentsPrefix, azureDeploymentName)\n\t}\n\treturn baseURL\n}\n\nfunc (c *Client) handleErrorResp(resp *http.Response) error {\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error, reading response body: %w\", err)\n\t}\n\tvar errRes ErrorResponse\n\terr = json.Unmarshal(body, &errRes)\n\tif err != nil || errRes.Error == nil {\n\t\treqErr := &RequestError{\n\t\t\tHTTPStatus:     resp.Status,\n\t\t\tHTTPStatusCode: resp.StatusCode,\n\t\t\tErr:            err,\n\t\t\tBody:           body,\n\t\t}\n\t\tif errRes.Error != nil {\n\t\t\treqErr.Err = errRes.Error\n\t\t}\n\t\treturn reqErr\n\t}\n\n\terrRes.Error.HTTPStatus = resp.Status\n\terrRes.Error.HTTPStatusCode = resp.StatusCode\n\treturn errRes.Error\n}\n\nfunc containsSubstr(s []string, e string) bool {\n\tfor _, v := range s {\n\t\tif strings.Contains(e, v) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "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/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nvar errTestRequestBuilderFailed = errors.New(\"test request builder failed\")\n\ntype failingRequestBuilder struct{}\n\nfunc (*failingRequestBuilder) Build(_ context.Context, _, _ string, _ any, _ http.Header) (*http.Request, error) {\n\treturn nil, errTestRequestBuilderFailed\n}\n\nfunc TestClient(t *testing.T) {\n\tconst mockToken = \"mock token\"\n\tclient := NewClient(mockToken)\n\tif client.config.authToken != mockToken {\n\t\tt.Errorf(\"Client does not contain proper token\")\n\t}\n\n\tconst mockOrg = \"mock org\"\n\tclient = NewOrgClient(mockToken, mockOrg)\n\tif client.config.authToken != mockToken {\n\t\tt.Errorf(\"Client does not contain proper token\")\n\t}\n\tif client.config.OrgID != mockOrg {\n\t\tt.Errorf(\"Client does not contain proper orgID\")\n\t}\n}\n\nfunc TestSetCommonHeadersAnthropic(t *testing.T) {\n\tconfig := DefaultAnthropicConfig(\"mock-token\", \"\")\n\tclient := NewClientWithConfig(config)\n\treq, err := http.NewRequest(\"GET\", \"http://example.com\", nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create request: %v\", err)\n\t}\n\n\tclient.setCommonHeaders(req)\n\n\tif got := req.Header.Get(\"anthropic-version\"); got != AnthropicAPIVersion {\n\t\tt.Errorf(\"Expected anthropic-version header to be %q, got %q\", AnthropicAPIVersion, got)\n\t}\n}\n\nfunc TestDecodeResponse(t *testing.T) {\n\tstringInput := \"\"\n\n\ttestCases := []struct {\n\t\tname     string\n\t\tvalue    interface{}\n\t\texpected interface{}\n\t\tbody     io.Reader\n\t\thasError bool\n\t}{\n\t\t{\n\t\t\tname:     \"nil input\",\n\t\t\tvalue:    nil,\n\t\t\tbody:     bytes.NewReader([]byte(\"\")),\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tname:     \"string input\",\n\t\t\tvalue:    &stringInput,\n\t\t\tbody:     bytes.NewReader([]byte(\"test\")),\n\t\t\texpected: \"test\",\n\t\t},\n\t\t{\n\t\t\tname:  \"map input\",\n\t\t\tvalue: &map[string]interface{}{},\n\t\t\tbody:  bytes.NewReader([]byte(`{\"test\": \"test\"}`)),\n\t\t\texpected: map[string]interface{}{\n\t\t\t\t\"test\": \"test\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"reader return error\",\n\t\t\tvalue:    &stringInput,\n\t\t\tbody:     &errorReader{err: errors.New(\"dummy\")},\n\t\t\thasError: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"audio text input\",\n\t\t\tvalue: &audioTextResponse{},\n\t\t\tbody:  bytes.NewReader([]byte(\"test\")),\n\t\t\texpected: audioTextResponse{\n\t\t\t\tText: \"test\",\n\t\t\t},\n\t\t},\n\t}\n\n\tassertEqual := func(t *testing.T, expected, actual interface{}) {\n\t\tt.Helper()\n\t\tif expected == actual {\n\t\t\treturn\n\t\t}\n\t\tv := reflect.ValueOf(actual).Elem().Interface()\n\t\tif !reflect.DeepEqual(v, expected) {\n\t\t\tt.Fatalf(\"Unexpected value: %v, expected: %v\", v, expected)\n\t\t}\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := decodeResponse(tc.body, tc.value)\n\t\t\tif tc.hasError {\n\t\t\t\tchecks.HasError(t, err, \"Unexpected nil error\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t}\n\t\t\tassertEqual(t, tc.expected, tc.value)\n\t\t})\n\t}\n}\n\ntype errorReader struct {\n\terr error\n}\n\nfunc (e *errorReader) Read(_ []byte) (n int, err error) {\n\treturn 0, e.err\n}\n\nfunc TestHandleErrorResp(t *testing.T) {\n\t// var errRes *ErrorResponse\n\tvar errRes ErrorResponse\n\tvar reqErr RequestError\n\tt.Log(errRes, errRes.Error)\n\tif errRes.Error != nil {\n\t\treqErr.Err = errRes.Error\n\t}\n\tt.Log(fmt.Errorf(\"error, %w\", &reqErr))\n\tt.Log(errRes.Error, \"nil pointer check Pass\")\n\n\tconst mockToken = \"mock token\"\n\tclient := NewClient(mockToken)\n\n\ttestCases := []struct {\n\t\tname        string\n\t\thttpCode    int\n\t\thttpStatus  string\n\t\tcontentType string\n\t\tbody        io.Reader\n\t\texpected    string\n\t}{\n\t\t{\n\t\t\tname:        \"401 Invalid Authentication\",\n\t\t\thttpCode:    http.StatusUnauthorized,\n\t\t\tcontentType: \"application/json\",\n\t\t\tbody: bytes.NewReader([]byte(\n\t\t\t\t`{\n\t\t\t\t\t\"error\":{\n\t\t\t\t\t\t\"message\":\"You didn't provide an API key. ....\",\n\t\t\t\t\t\t\"type\":\"invalid_request_error\",\n\t\t\t\t\t\t\"param\":null,\n\t\t\t\t\t\t\"code\":null\n\t\t\t\t\t}\n\t\t\t\t}`,\n\t\t\t)),\n\t\t\texpected: \"error, status code: 401, status: , message: You didn't provide an API key. ....\",\n\t\t},\n\t\t{\n\t\t\tname:        \"401 Azure Access Denied\",\n\t\t\thttpCode:    http.StatusUnauthorized,\n\t\t\tcontentType: \"application/json\",\n\t\t\tbody: bytes.NewReader([]byte(\n\t\t\t\t`{\n\t\t\t\t\t\"error\":{\n\t\t\t\t\t\t\"code\":\"AccessDenied\",\n\t\t\t\t\t\t\"message\":\"Access denied due to Virtual Network/Firewall rules.\"\n\t\t\t\t\t}\n\t\t\t\t}`,\n\t\t\t)),\n\t\t\texpected: \"error, status code: 401, status: , message: Access denied due to Virtual Network/Firewall rules.\",\n\t\t},\n\t\t{\n\t\t\tname:        \"503 Model Overloaded\",\n\t\t\thttpCode:    http.StatusServiceUnavailable,\n\t\t\tcontentType: \"application/json\",\n\t\t\tbody: bytes.NewReader([]byte(`\n\t\t\t\t{\n\t\t\t\t\t\"error\":{\n\t\t\t\t\t\t\"message\":\"That model...\",\n\t\t\t\t\t\t\"type\":\"server_error\",\n\t\t\t\t\t\t\"param\":null,\n\t\t\t\t\t\t\"code\":null\n\t\t\t\t\t}\n\t\t\t\t}`)),\n\t\t\texpected: \"error, status code: 503, status: , message: That model...\",\n\t\t},\n\t\t{\n\t\t\tname:        \"503 no message (Unknown response)\",\n\t\t\thttpCode:    http.StatusServiceUnavailable,\n\t\t\tcontentType: \"application/json\",\n\t\t\tbody: bytes.NewReader([]byte(`\n\t\t\t\t{\n\t\t\t\t\t\"error\":{}\n\t\t\t\t}`)),\n\t\t\texpected: `error, status code: 503, status: , message: , body: \n\t\t\t\t{\n\t\t\t\t\t\"error\":{}\n\t\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname:        \"413 Request Entity Too Large\",\n\t\t\thttpCode:    http.StatusRequestEntityTooLarge,\n\t\t\tcontentType: \"text/html\",\n\t\t\tbody: bytes.NewReader([]byte(`\n\t<html>\n\t<head><title>413 Request Entity Too Large</title></head>\n\t<body>\n\t<center><h1>413 Request Entity Too Large</h1></center>\n\t<hr><center>nginx</center>\n\t</body>\n\t</html>`)),\n\t\t\texpected: `error, status code: 413, status: , message: invalid character '<' looking for beginning of value, body: \n\t<html>\n\t<head><title>413 Request Entity Too Large</title></head>\n\t<body>\n\t<center><h1>413 Request Entity Too Large</h1></center>\n\t<hr><center>nginx</center>\n\t</body>\n\t</html>`,\n\t\t},\n\t\t{\n\t\t\tname:        \"errorReader\",\n\t\t\thttpCode:    http.StatusRequestEntityTooLarge,\n\t\t\tcontentType: \"text/html\",\n\t\t\tbody:        &errorReader{err: errors.New(\"errorReader\")},\n\t\t\texpected:    \"error, reading response body: errorReader\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttestCase := &http.Response{\n\t\t\t\tHeader: map[string][]string{\n\t\t\t\t\t\"Content-Type\": {tc.contentType},\n\t\t\t\t},\n\t\t\t}\n\t\t\ttestCase.StatusCode = tc.httpCode\n\t\t\ttestCase.Body = io.NopCloser(tc.body)\n\t\t\terr := client.handleErrorResp(testCase)\n\t\t\tt.Log(err.Error())\n\t\t\tif err.Error() != tc.expected {\n\t\t\t\tt.Errorf(\"Unexpected error: %v , expected: %s\", err, tc.expected)\n\t\t\t\tt.Fail()\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientReturnsRequestBuilderErrors(t *testing.T) {\n\tconfig := DefaultConfig(test.GetTestToken())\n\tclient := NewClientWithConfig(config)\n\tclient.requestBuilder = &failingRequestBuilder{}\n\tctx := context.Background()\n\n\ttype TestCase struct {\n\t\tName     string\n\t\tTestFunc func() (any, error)\n\t}\n\n\ttestCases := []TestCase{\n\t\t{\"CreateCompletion\", func() (any, error) {\n\t\t\treturn client.CreateCompletion(ctx, CompletionRequest{Prompt: \"testing\"})\n\t\t}},\n\t\t{\"CreateCompletionStream\", func() (any, error) {\n\t\t\treturn client.CreateCompletionStream(ctx, CompletionRequest{Prompt: \"\"})\n\t\t}},\n\t\t{\"CreateChatCompletion\", func() (any, error) {\n\t\t\treturn client.CreateChatCompletion(ctx, ChatCompletionRequest{Model: GPT3Dot5Turbo})\n\t\t}},\n\t\t{\"CreateChatCompletionStream\", func() (any, error) {\n\t\t\treturn client.CreateChatCompletionStream(ctx, ChatCompletionRequest{Model: GPT3Dot5Turbo})\n\t\t}},\n\t\t{\"CreateFineTune\", func() (any, error) {\n\t\t\treturn client.CreateFineTune(ctx, FineTuneRequest{})\n\t\t}},\n\t\t{\"ListFineTunes\", func() (any, error) {\n\t\t\treturn client.ListFineTunes(ctx)\n\t\t}},\n\t\t{\"CancelFineTune\", func() (any, error) {\n\t\t\treturn client.CancelFineTune(ctx, \"\")\n\t\t}},\n\t\t{\"GetFineTune\", func() (any, error) {\n\t\t\treturn client.GetFineTune(ctx, \"\")\n\t\t}},\n\t\t{\"DeleteFineTune\", func() (any, error) {\n\t\t\treturn client.DeleteFineTune(ctx, \"\")\n\t\t}},\n\t\t{\"ListFineTuneEvents\", func() (any, error) {\n\t\t\treturn client.ListFineTuneEvents(ctx, \"\")\n\t\t}},\n\t\t{\"CreateFineTuningJob\", func() (any, error) {\n\t\t\treturn client.CreateFineTuningJob(ctx, FineTuningJobRequest{})\n\t\t}},\n\t\t{\"CancelFineTuningJob\", func() (any, error) {\n\t\t\treturn client.CancelFineTuningJob(ctx, \"\")\n\t\t}},\n\t\t{\"RetrieveFineTuningJob\", func() (any, error) {\n\t\t\treturn client.RetrieveFineTuningJob(ctx, \"\")\n\t\t}},\n\t\t{\"ListFineTuningJobEvents\", func() (any, error) {\n\t\t\treturn client.ListFineTuningJobEvents(ctx, \"\")\n\t\t}},\n\t\t{\"Moderations\", func() (any, error) {\n\t\t\treturn client.Moderations(ctx, ModerationRequest{})\n\t\t}},\n\t\t{\"Edits\", func() (any, error) {\n\t\t\treturn client.Edits(ctx, EditsRequest{})\n\t\t}},\n\t\t{\"CreateEmbeddings\", func() (any, error) {\n\t\t\treturn client.CreateEmbeddings(ctx, EmbeddingRequest{})\n\t\t}},\n\t\t{\"CreateImage\", func() (any, error) {\n\t\t\treturn client.CreateImage(ctx, ImageRequest{})\n\t\t}},\n\t\t{\"CreateFileBytes\", func() (any, error) {\n\t\t\treturn client.CreateFileBytes(ctx, FileBytesRequest{})\n\t\t}},\n\t\t{\"DeleteFile\", func() (any, error) {\n\t\t\treturn nil, client.DeleteFile(ctx, \"\")\n\t\t}},\n\t\t{\"GetFile\", func() (any, error) {\n\t\t\treturn client.GetFile(ctx, \"\")\n\t\t}},\n\t\t{\"GetFileContent\", func() (any, error) {\n\t\t\treturn client.GetFileContent(ctx, \"\")\n\t\t}},\n\t\t{\"ListFiles\", func() (any, error) {\n\t\t\treturn client.ListFiles(ctx)\n\t\t}},\n\t\t{\"ListEngines\", func() (any, error) {\n\t\t\treturn client.ListEngines(ctx)\n\t\t}},\n\t\t{\"GetEngine\", func() (any, error) {\n\t\t\treturn client.GetEngine(ctx, \"\")\n\t\t}},\n\t\t{\"ListModels\", func() (any, error) {\n\t\t\treturn client.ListModels(ctx)\n\t\t}},\n\t\t{\"GetModel\", func() (any, error) {\n\t\t\treturn client.GetModel(ctx, \"text-davinci-003\")\n\t\t}},\n\t\t{\"DeleteFineTuneModel\", func() (any, error) {\n\t\t\treturn client.DeleteFineTuneModel(ctx, \"\")\n\t\t}},\n\t\t{\"CreateAssistant\", func() (any, error) {\n\t\t\treturn client.CreateAssistant(ctx, AssistantRequest{})\n\t\t}},\n\t\t{\"RetrieveAssistant\", func() (any, error) {\n\t\t\treturn client.RetrieveAssistant(ctx, \"\")\n\t\t}},\n\t\t{\"ModifyAssistant\", func() (any, error) {\n\t\t\treturn client.ModifyAssistant(ctx, \"\", AssistantRequest{})\n\t\t}},\n\t\t{\"DeleteAssistant\", func() (any, error) {\n\t\t\treturn client.DeleteAssistant(ctx, \"\")\n\t\t}},\n\t\t{\"ListAssistants\", func() (any, error) {\n\t\t\treturn client.ListAssistants(ctx, nil, nil, nil, nil)\n\t\t}},\n\t\t{\"CreateAssistantFile\", func() (any, error) {\n\t\t\treturn client.CreateAssistantFile(ctx, \"\", AssistantFileRequest{})\n\t\t}},\n\t\t{\"ListAssistantFiles\", func() (any, error) {\n\t\t\treturn client.ListAssistantFiles(ctx, \"\", nil, nil, nil, nil)\n\t\t}},\n\t\t{\"RetrieveAssistantFile\", func() (any, error) {\n\t\t\treturn client.RetrieveAssistantFile(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"DeleteAssistantFile\", func() (any, error) {\n\t\t\treturn nil, client.DeleteAssistantFile(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"CreateMessage\", func() (any, error) {\n\t\t\treturn client.CreateMessage(ctx, \"\", MessageRequest{})\n\t\t}},\n\t\t{\"ListMessage\", func() (any, error) {\n\t\t\treturn client.ListMessage(ctx, \"\", nil, nil, nil, nil, nil)\n\t\t}},\n\t\t{\"RetrieveMessage\", func() (any, error) {\n\t\t\treturn client.RetrieveMessage(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"ModifyMessage\", func() (any, error) {\n\t\t\treturn client.ModifyMessage(ctx, \"\", \"\", nil)\n\t\t}},\n\t\t{\"DeleteMessage\", func() (any, error) {\n\t\t\treturn client.DeleteMessage(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"RetrieveMessageFile\", func() (any, error) {\n\t\t\treturn client.RetrieveMessageFile(ctx, \"\", \"\", \"\")\n\t\t}},\n\t\t{\"ListMessageFiles\", func() (any, error) {\n\t\t\treturn client.ListMessageFiles(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"CreateThread\", func() (any, error) {\n\t\t\treturn client.CreateThread(ctx, ThreadRequest{})\n\t\t}},\n\t\t{\"RetrieveThread\", func() (any, error) {\n\t\t\treturn client.RetrieveThread(ctx, \"\")\n\t\t}},\n\t\t{\"ModifyThread\", func() (any, error) {\n\t\t\treturn client.ModifyThread(ctx, \"\", ModifyThreadRequest{})\n\t\t}},\n\t\t{\"DeleteThread\", func() (any, error) {\n\t\t\treturn client.DeleteThread(ctx, \"\")\n\t\t}},\n\t\t{\"CreateRun\", func() (any, error) {\n\t\t\treturn client.CreateRun(ctx, \"\", RunRequest{})\n\t\t}},\n\t\t{\"RetrieveRun\", func() (any, error) {\n\t\t\treturn client.RetrieveRun(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"ModifyRun\", func() (any, error) {\n\t\t\treturn client.ModifyRun(ctx, \"\", \"\", RunModifyRequest{})\n\t\t}},\n\t\t{\"ListRuns\", func() (any, error) {\n\t\t\treturn client.ListRuns(ctx, \"\", Pagination{})\n\t\t}},\n\t\t{\"SubmitToolOutputs\", func() (any, error) {\n\t\t\treturn client.SubmitToolOutputs(ctx, \"\", \"\", SubmitToolOutputsRequest{})\n\t\t}},\n\t\t{\"CancelRun\", func() (any, error) {\n\t\t\treturn client.CancelRun(ctx, \"\", \"\")\n\t\t}},\n\t\t{\"CreateThreadAndRun\", func() (any, error) {\n\t\t\treturn client.CreateThreadAndRun(ctx, CreateThreadAndRunRequest{})\n\t\t}},\n\t\t{\"RetrieveRunStep\", func() (any, error) {\n\t\t\treturn client.RetrieveRunStep(ctx, \"\", \"\", \"\")\n\t\t}},\n\t\t{\"ListRunSteps\", func() (any, error) {\n\t\t\treturn client.ListRunSteps(ctx, \"\", \"\", Pagination{})\n\t\t}},\n\t\t{\"CreateSpeech\", func() (any, error) {\n\t\t\treturn client.CreateSpeech(ctx, CreateSpeechRequest{Model: TTSModel1, Voice: VoiceAlloy})\n\t\t}},\n\t\t{\"CreateBatch\", func() (any, error) {\n\t\t\treturn client.CreateBatch(ctx, CreateBatchRequest{})\n\t\t}},\n\t\t{\"CreateBatchWithUploadFile\", func() (any, error) {\n\t\t\treturn client.CreateBatchWithUploadFile(ctx, CreateBatchWithUploadFileRequest{})\n\t\t}},\n\t\t{\"RetrieveBatch\", func() (any, error) {\n\t\t\treturn client.RetrieveBatch(ctx, \"\")\n\t\t}},\n\t\t{\"CancelBatch\", func() (any, error) { return client.CancelBatch(ctx, \"\") }},\n\t\t{\"ListBatch\", func() (any, error) { return client.ListBatch(ctx, nil, nil) }},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, err := testCase.TestFunc()\n\t\tif !errors.Is(err, errTestRequestBuilderFailed) {\n\t\t\tt.Fatalf(\"%s did not return error when request builder failed: %v\", testCase.Name, err)\n\t\t}\n\t}\n}\n\nfunc TestClientReturnsRequestBuilderErrorsAddition(t *testing.T) {\n\tconfig := DefaultConfig(test.GetTestToken())\n\tclient := NewClientWithConfig(config)\n\tclient.requestBuilder = &failingRequestBuilder{}\n\tctx := context.Background()\n\t_, err := client.CreateCompletion(ctx, CompletionRequest{Prompt: 1})\n\tif !errors.Is(err, ErrCompletionRequestPromptTypeNotSupported) {\n\t\tt.Fatalf(\"Did not return error when request builder failed: %v\", err)\n\t}\n\t_, err = client.CreateCompletionStream(ctx, CompletionRequest{Prompt: 1})\n\tif !errors.Is(err, ErrCompletionRequestPromptTypeNotSupported) {\n\t\tt.Fatalf(\"Did not return error when request builder failed: %v\", err)\n\t}\n}\n\nfunc TestClient_suffixWithAPIVersion(t *testing.T) {\n\ttype fields struct {\n\t\tapiVersion string\n\t}\n\ttype args struct {\n\t\tsuffix string\n\t}\n\ttests := []struct {\n\t\tname      string\n\t\tfields    fields\n\t\targs      args\n\t\twant      string\n\t\twantPanic string\n\t}{\n\t\t{\n\t\t\t\"\",\n\t\t\tfields{apiVersion: \"2023-05\"},\n\t\t\targs{suffix: \"/assistants\"},\n\t\t\t\"/assistants?api-version=2023-05\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\tfields{apiVersion: \"2023-05\"},\n\t\t\targs{suffix: \"/assistants?limit=5\"},\n\t\t\t\"/assistants?api-version=2023-05&limit=5\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\tfields{apiVersion: \"2023-05\"},\n\t\t\targs{suffix: \"123:assistants?limit=5\"},\n\t\t\t\"/assistants?api-version=2023-05&limit=5\",\n\t\t\t\"failed to parse url suffix\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc := &Client{\n\t\t\t\tconfig: ClientConfig{APIVersion: tt.fields.apiVersion},\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t// Check if the panic message matches the expected panic message\n\t\t\t\t\tif rStr, ok := r.(string); ok {\n\t\t\t\t\t\tif rStr != tt.wantPanic {\n\t\t\t\t\t\t\tt.Errorf(\"suffixWithAPIVersion() = %v, want %v\", rStr, tt.wantPanic)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the panic is not a string, log it\n\t\t\t\t\t\tt.Errorf(\"suffixWithAPIVersion() panicked with non-string value: %v\", r)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t\tif got := c.suffixWithAPIVersion(tt.args.suffix); got != tt.want {\n\t\t\t\tt.Errorf(\"suffixWithAPIVersion() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClient_baseURLWithAzureDeployment(t *testing.T) {\n\ttype args struct {\n\t\tbaseURL string\n\t\tsuffix  string\n\t\tmodel   string\n\t}\n\ttests := []struct {\n\t\tname           string\n\t\targs           args\n\t\twantNewBaseURL string\n\t}{\n\t\t{\n\t\t\t\"\",\n\t\t\targs{baseURL: \"https://test.openai.azure.com/\", suffix: assistantsSuffix, model: GPT4oMini},\n\t\t\t\"https://test.openai.azure.com/openai\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\targs{baseURL: \"https://test.openai.azure.com/\", suffix: chatCompletionsSuffix, model: GPT4oMini},\n\t\t\t\"https://test.openai.azure.com/openai/deployments/gpt-4o-mini\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\targs{baseURL: \"https://test.openai.azure.com/\", suffix: chatCompletionsSuffix, model: \"\"},\n\t\t\t\"https://test.openai.azure.com/openai/deployments/UNKNOWN\",\n\t\t},\n\t}\n\tclient := NewClient(\"\")\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif gotNewBaseURL := client.baseURLWithAzureDeployment(\n\t\t\t\ttt.args.baseURL,\n\t\t\t\ttt.args.suffix,\n\t\t\t\ttt.args.model,\n\t\t\t); gotNewBaseURL != tt.wantNewBaseURL {\n\t\t\t\tt.Errorf(\"baseURLWithAzureDeployment() = %v, want %v\", gotNewBaseURL, tt.wantNewBaseURL)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "common.go",
    "content": "package openai\n\n// common.go defines common types used throughout the OpenAI API.\n\n// Usage Represents the total token usage per request to OpenAI.\ntype Usage struct {\n\tPromptTokens            int                      `json:\"prompt_tokens\"`\n\tCompletionTokens        int                      `json:\"completion_tokens\"`\n\tTotalTokens             int                      `json:\"total_tokens\"`\n\tPromptTokensDetails     *PromptTokensDetails     `json:\"prompt_tokens_details\"`\n\tCompletionTokensDetails *CompletionTokensDetails `json:\"completion_tokens_details\"`\n}\n\n// CompletionTokensDetails Breakdown of tokens used in a completion.\ntype CompletionTokensDetails struct {\n\tAudioTokens              int `json:\"audio_tokens\"`\n\tReasoningTokens          int `json:\"reasoning_tokens\"`\n\tAcceptedPredictionTokens int `json:\"accepted_prediction_tokens\"`\n\tRejectedPredictionTokens int `json:\"rejected_prediction_tokens\"`\n}\n\n// PromptTokensDetails Breakdown of tokens used in the prompt.\ntype PromptTokensDetails struct {\n\tAudioTokens  int `json:\"audio_tokens\"`\n\tCachedTokens int `json:\"cached_tokens\"`\n}\n"
  },
  {
    "path": "completion.go",
    "content": "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// completions from OpenAI.\n// GPT3 Models are designed for text-based tasks. For code-specific\n// tasks, please refer to the Codex series of models.\nconst (\n\tO1Mini                  = \"o1-mini\"\n\tO1Mini20240912          = \"o1-mini-2024-09-12\"\n\tO1Preview               = \"o1-preview\"\n\tO1Preview20240912       = \"o1-preview-2024-09-12\"\n\tO1                      = \"o1\"\n\tO120241217              = \"o1-2024-12-17\"\n\tO3                      = \"o3\"\n\tO320250416              = \"o3-2025-04-16\"\n\tO3Mini                  = \"o3-mini\"\n\tO3Mini20250131          = \"o3-mini-2025-01-31\"\n\tO4Mini                  = \"o4-mini\"\n\tO4Mini20250416          = \"o4-mini-2025-04-16\"\n\tGPT432K0613             = \"gpt-4-32k-0613\"\n\tGPT432K0314             = \"gpt-4-32k-0314\"\n\tGPT432K                 = \"gpt-4-32k\"\n\tGPT40613                = \"gpt-4-0613\"\n\tGPT40314                = \"gpt-4-0314\"\n\tGPT4o                   = \"gpt-4o\"\n\tGPT4o20240513           = \"gpt-4o-2024-05-13\"\n\tGPT4o20240806           = \"gpt-4o-2024-08-06\"\n\tGPT4o20241120           = \"gpt-4o-2024-11-20\"\n\tGPT4oLatest             = \"chatgpt-4o-latest\"\n\tGPT4oMini               = \"gpt-4o-mini\"\n\tGPT4oMini20240718       = \"gpt-4o-mini-2024-07-18\"\n\tGPT4Turbo               = \"gpt-4-turbo\"\n\tGPT4Turbo20240409       = \"gpt-4-turbo-2024-04-09\"\n\tGPT4Turbo0125           = \"gpt-4-0125-preview\"\n\tGPT4Turbo1106           = \"gpt-4-1106-preview\"\n\tGPT4TurboPreview        = \"gpt-4-turbo-preview\"\n\tGPT4VisionPreview       = \"gpt-4-vision-preview\"\n\tGPT4                    = \"gpt-4\"\n\tGPT4Dot1                = \"gpt-4.1\"\n\tGPT4Dot120250414        = \"gpt-4.1-2025-04-14\"\n\tGPT4Dot1Mini            = \"gpt-4.1-mini\"\n\tGPT4Dot1Mini20250414    = \"gpt-4.1-mini-2025-04-14\"\n\tGPT4Dot1Nano            = \"gpt-4.1-nano\"\n\tGPT4Dot1Nano20250414    = \"gpt-4.1-nano-2025-04-14\"\n\tGPT4Dot5Preview         = \"gpt-4.5-preview\"\n\tGPT4Dot5Preview20250227 = \"gpt-4.5-preview-2025-02-27\"\n\tGPT5                    = \"gpt-5\"\n\tGPT5Mini                = \"gpt-5-mini\"\n\tGPT5Nano                = \"gpt-5-nano\"\n\tGPT5ChatLatest          = \"gpt-5-chat-latest\"\n\tGPT3Dot5Turbo0125       = \"gpt-3.5-turbo-0125\"\n\tGPT3Dot5Turbo1106       = \"gpt-3.5-turbo-1106\"\n\tGPT3Dot5Turbo0613       = \"gpt-3.5-turbo-0613\"\n\tGPT3Dot5Turbo0301       = \"gpt-3.5-turbo-0301\"\n\tGPT3Dot5Turbo16K        = \"gpt-3.5-turbo-16k\"\n\tGPT3Dot5Turbo16K0613    = \"gpt-3.5-turbo-16k-0613\"\n\tGPT3Dot5Turbo           = \"gpt-3.5-turbo\"\n\tGPT3Dot5TurboInstruct   = \"gpt-3.5-turbo-instruct\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextDavinci003 = \"text-davinci-003\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextDavinci002 = \"text-davinci-002\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextCurie001 = \"text-curie-001\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextBabbage001 = \"text-babbage-001\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextAda001 = \"text-ada-001\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3TextDavinci001 = \"text-davinci-001\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3DavinciInstructBeta = \"davinci-instruct-beta\"\n\t// Deprecated: Model is shutdown. Use davinci-002 instead.\n\tGPT3Davinci    = \"davinci\"\n\tGPT3Davinci002 = \"davinci-002\"\n\t// Deprecated: Model is shutdown. Use gpt-3.5-turbo-instruct instead.\n\tGPT3CurieInstructBeta = \"curie-instruct-beta\"\n\tGPT3Curie             = \"curie\"\n\tGPT3Curie002          = \"curie-002\"\n\t// Deprecated: Model is shutdown. Use babbage-002 instead.\n\tGPT3Ada    = \"ada\"\n\tGPT3Ada002 = \"ada-002\"\n\t// Deprecated: Model is shutdown. Use babbage-002 instead.\n\tGPT3Babbage    = \"babbage\"\n\tGPT3Babbage002 = \"babbage-002\"\n)\n\n// Codex Defines the models provided by OpenAI.\n// These models are designed for code-specific tasks, and use\n// a different tokenizer which optimizes for whitespace.\nconst (\n\tCodexCodeDavinci002 = \"code-davinci-002\"\n\tCodexCodeCushman001 = \"code-cushman-001\"\n\tCodexCodeDavinci001 = \"code-davinci-001\"\n)\n\nvar disabledModelsForEndpoints = map[string]map[string]bool{\n\t\"/completions\": {\n\t\tO1Mini:                  true,\n\t\tO1Mini20240912:          true,\n\t\tO1Preview:               true,\n\t\tO1Preview20240912:       true,\n\t\tO3Mini:                  true,\n\t\tO3Mini20250131:          true,\n\t\tO4Mini:                  true,\n\t\tO4Mini20250416:          true,\n\t\tO3:                      true,\n\t\tO320250416:              true,\n\t\tGPT3Dot5Turbo:           true,\n\t\tGPT3Dot5Turbo0301:       true,\n\t\tGPT3Dot5Turbo0613:       true,\n\t\tGPT3Dot5Turbo1106:       true,\n\t\tGPT3Dot5Turbo0125:       true,\n\t\tGPT3Dot5Turbo16K:        true,\n\t\tGPT3Dot5Turbo16K0613:    true,\n\t\tGPT4:                    true,\n\t\tGPT4Dot5Preview:         true,\n\t\tGPT4Dot5Preview20250227: true,\n\t\tGPT4o:                   true,\n\t\tGPT4o20240513:           true,\n\t\tGPT4o20240806:           true,\n\t\tGPT4o20241120:           true,\n\t\tGPT4oLatest:             true,\n\t\tGPT4oMini:               true,\n\t\tGPT4oMini20240718:       true,\n\t\tGPT4TurboPreview:        true,\n\t\tGPT4VisionPreview:       true,\n\t\tGPT4Turbo1106:           true,\n\t\tGPT4Turbo0125:           true,\n\t\tGPT4Turbo:               true,\n\t\tGPT4Turbo20240409:       true,\n\t\tGPT40314:                true,\n\t\tGPT40613:                true,\n\t\tGPT432K:                 true,\n\t\tGPT432K0314:             true,\n\t\tGPT432K0613:             true,\n\t\tO1:                      true,\n\t\tGPT4Dot1:                true,\n\t\tGPT4Dot120250414:        true,\n\t\tGPT4Dot1Mini:            true,\n\t\tGPT4Dot1Mini20250414:    true,\n\t\tGPT4Dot1Nano:            true,\n\t\tGPT4Dot1Nano20250414:    true,\n\t\tGPT5:                    true,\n\t\tGPT5Mini:                true,\n\t\tGPT5Nano:                true,\n\t\tGPT5ChatLatest:          true,\n\t},\n\tchatCompletionsSuffix: {\n\t\tCodexCodeDavinci002:     true,\n\t\tCodexCodeCushman001:     true,\n\t\tCodexCodeDavinci001:     true,\n\t\tGPT3TextDavinci003:      true,\n\t\tGPT3TextDavinci002:      true,\n\t\tGPT3TextCurie001:        true,\n\t\tGPT3TextBabbage001:      true,\n\t\tGPT3TextAda001:          true,\n\t\tGPT3TextDavinci001:      true,\n\t\tGPT3DavinciInstructBeta: true,\n\t\tGPT3Davinci:             true,\n\t\tGPT3CurieInstructBeta:   true,\n\t\tGPT3Curie:               true,\n\t\tGPT3Ada:                 true,\n\t\tGPT3Babbage:             true,\n\t},\n}\n\nfunc checkEndpointSupportsModel(endpoint, model string) bool {\n\treturn !disabledModelsForEndpoints[endpoint][model]\n}\n\nfunc checkPromptType(prompt any) bool {\n\t_, isString := prompt.(string)\n\t_, isStringSlice := prompt.([]string)\n\tif isString || isStringSlice {\n\t\treturn true\n\t}\n\n\t// check if it is prompt is []string hidden under []any\n\tslice, isSlice := prompt.([]any)\n\tif !isSlice {\n\t\treturn false\n\t}\n\n\tfor _, item := range slice {\n\t\t_, itemIsString := item.(string)\n\t\tif !itemIsString {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true // all items in the slice are string, so it is []string\n}\n\n// CompletionRequest represents a request structure for completion API.\ntype CompletionRequest struct {\n\tModel            string  `json:\"model\"`\n\tPrompt           any     `json:\"prompt,omitempty\"`\n\tBestOf           int     `json:\"best_of,omitempty\"`\n\tEcho             bool    `json:\"echo,omitempty\"`\n\tFrequencyPenalty float32 `json:\"frequency_penalty,omitempty\"`\n\t// LogitBias is must be a token id string (specified by their token ID in the tokenizer), not a word string.\n\t// incorrect: `\"logit_bias\":{\"You\": 6}`, correct: `\"logit_bias\":{\"1639\": 6}`\n\t// refs: https://platform.openai.com/docs/api-reference/completions/create#completions/create-logit_bias\n\tLogitBias map[string]int `json:\"logit_bias,omitempty\"`\n\t// Store can be set to true to store the output of this completion request for use in distillations and evals.\n\t// https://platform.openai.com/docs/api-reference/chat/create#chat-create-store\n\tStore bool `json:\"store,omitempty\"`\n\t// Metadata to store with the completion.\n\tMetadata        map[string]string `json:\"metadata,omitempty\"`\n\tLogProbs        int               `json:\"logprobs,omitempty\"`\n\tMaxTokens       int               `json:\"max_tokens,omitempty\"`\n\tN               int               `json:\"n,omitempty\"`\n\tPresencePenalty float32           `json:\"presence_penalty,omitempty\"`\n\tSeed            *int              `json:\"seed,omitempty\"`\n\tStop            []string          `json:\"stop,omitempty\"`\n\tStream          bool              `json:\"stream,omitempty\"`\n\tSuffix          string            `json:\"suffix,omitempty\"`\n\tTemperature     float32           `json:\"temperature,omitempty\"`\n\tTopP            float32           `json:\"top_p,omitempty\"`\n\tUser            string            `json:\"user,omitempty\"`\n\t// Options for streaming response. Only set this when you set stream: true.\n\tStreamOptions *StreamOptions `json:\"stream_options,omitempty\"`\n}\n\n// CompletionChoice represents one of possible completions.\ntype CompletionChoice struct {\n\tText         string        `json:\"text\"`\n\tIndex        int           `json:\"index\"`\n\tFinishReason string        `json:\"finish_reason\"`\n\tLogProbs     LogprobResult `json:\"logprobs\"`\n}\n\n// LogprobResult represents logprob result of Choice.\ntype LogprobResult struct {\n\tTokens        []string             `json:\"tokens\"`\n\tTokenLogprobs []float32            `json:\"token_logprobs\"`\n\tTopLogprobs   []map[string]float32 `json:\"top_logprobs\"`\n\tTextOffset    []int                `json:\"text_offset\"`\n}\n\n// CompletionResponse represents a response structure for completion API.\ntype CompletionResponse struct {\n\tID      string             `json:\"id\"`\n\tObject  string             `json:\"object\"`\n\tCreated int64              `json:\"created\"`\n\tModel   string             `json:\"model\"`\n\tChoices []CompletionChoice `json:\"choices\"`\n\tUsage   *Usage             `json:\"usage,omitempty\"`\n\n\thttpHeader\n}\n\n// CreateCompletion — API call to create a completion. This is the main endpoint of the API. Returns new text as well\n// as, if requested, the probabilities over each alternative token at each position.\n//\n// If using a fine-tuned model, simply provide the model's ID in the CompletionRequest object,\n// and the server will use the model's parameters to generate the completion.\nfunc (c *Client) CreateCompletion(\n\tctx context.Context,\n\trequest CompletionRequest,\n) (response CompletionResponse, err error) {\n\tif request.Stream {\n\t\terr = ErrCompletionStreamNotSupported\n\t\treturn\n\t}\n\n\turlSuffix := \"/completions\"\n\tif !checkEndpointSupportsModel(urlSuffix, request.Model) {\n\t\terr = ErrCompletionUnsupportedModel\n\t\treturn\n\t}\n\n\tif !checkPromptType(request.Prompt) {\n\t\terr = ErrCompletionRequestPromptTypeNotSupported\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "completion_test.go",
    "content": "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\"testing\"\n\t\"time\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestCompletionsWrongModel(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\t_, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo,\n\t\t},\n\t)\n\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel, but returned: %v\", err)\n\t}\n}\n\n// TestCompletionsWrongModelO3 Tests the completions endpoint with O3 model which is not supported.\nfunc TestCompletionsWrongModelO3(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\t_, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.O3,\n\t\t},\n\t)\n\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for O3, but returned: %v\", err)\n\t}\n}\n\n// TestCompletionsWrongModelO4Mini Tests the completions endpoint with O4Mini model which is not supported.\nfunc TestCompletionsWrongModelO4Mini(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\t_, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.O4Mini,\n\t\t},\n\t)\n\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for O4Mini, but returned: %v\", err)\n\t}\n}\n\nfunc TestCompletionWithStream(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tclient := openai.NewClientWithConfig(config)\n\n\tctx := context.Background()\n\treq := openai.CompletionRequest{Stream: true}\n\t_, err := client.CreateCompletion(ctx, req)\n\tif !errors.Is(err, openai.ErrCompletionStreamNotSupported) {\n\t\tt.Fatalf(\"CreateCompletion didn't return ErrCompletionStreamNotSupported\")\n\t}\n}\n\n// TestCompletions Tests the completions endpoint of the API using the mocked server.\nfunc TestCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", handleCompletionEndpoint)\n\treq := openai.CompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     \"ada\",\n\t\tPrompt:    \"Lorem ipsum\",\n\t}\n\t_, err := client.CreateCompletion(context.Background(), req)\n\tchecks.NoError(t, err, \"CreateCompletion error\")\n}\n\n// TestMultiplePromptsCompletionsWrong Tests the completions endpoint of the API using the mocked server\n// where the completions requests has a list of prompts with wrong type.\nfunc TestMultiplePromptsCompletionsWrong(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", handleCompletionEndpoint)\n\treq := openai.CompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     \"ada\",\n\t\tPrompt:    []interface{}{\"Lorem ipsum\", 9},\n\t}\n\t_, err := client.CreateCompletion(context.Background(), req)\n\tif !errors.Is(err, openai.ErrCompletionRequestPromptTypeNotSupported) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionRequestPromptTypeNotSupported, but returned: %v\", err)\n\t}\n}\n\n// TestMultiplePromptsCompletions Tests the completions endpoint of the API using the mocked server\n// where the completions requests has a list of prompts.\nfunc TestMultiplePromptsCompletions(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", handleCompletionEndpoint)\n\treq := openai.CompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     \"ada\",\n\t\tPrompt:    []interface{}{\"Lorem ipsum\", \"Lorem ipsum\"},\n\t}\n\t_, err := client.CreateCompletion(context.Background(), req)\n\tchecks.NoError(t, err, \"CreateCompletion error\")\n}\n\n// handleCompletionEndpoint Handles the completion endpoint by the test server.\nfunc handleCompletionEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// completions only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar completionReq openai.CompletionRequest\n\tif completionReq, err = getCompletionBody(r); err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tres := openai.CompletionResponse{\n\t\tID:      strconv.Itoa(int(time.Now().Unix())),\n\t\tObject:  \"test-object\",\n\t\tCreated: time.Now().Unix(),\n\t\t// would be nice to validate Model during testing, but\n\t\t// this may not be possible with how much upkeep\n\t\t// would be required / wouldn't make much sense\n\t\tModel: completionReq.Model,\n\t}\n\t// create completions\n\tn := completionReq.N\n\tif n == 0 {\n\t\tn = 1\n\t}\n\t// Handle different types of prompts: single string or list of strings\n\tprompts := []string{}\n\tswitch v := completionReq.Prompt.(type) {\n\tcase string:\n\t\tprompts = append(prompts, v)\n\tcase []interface{}:\n\t\tfor _, item := range v {\n\t\t\tif str, ok := item.(string); ok {\n\t\t\t\tprompts = append(prompts, str)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\thttp.Error(w, \"Invalid prompt type\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tfor i := 0; i < n; i++ {\n\t\tfor _, prompt := range prompts {\n\t\t\t// Generate a random string of length completionReq.MaxTokens\n\t\t\tcompletionStr := strings.Repeat(\"a\", completionReq.MaxTokens)\n\t\t\tif completionReq.Echo {\n\t\t\t\tcompletionStr = prompt + completionStr\n\t\t\t}\n\n\t\t\tres.Choices = append(res.Choices, openai.CompletionChoice{\n\t\t\t\tText:  completionStr,\n\t\t\t\tIndex: len(res.Choices),\n\t\t\t})\n\t\t}\n\t}\n\n\tinputTokens := 0\n\tfor _, prompt := range prompts {\n\t\tinputTokens += numTokens(prompt)\n\t}\n\tinputTokens *= n\n\tcompletionTokens := completionReq.MaxTokens * len(prompts) * n\n\tres.Usage = &openai.Usage{\n\t\tPromptTokens:     inputTokens,\n\t\tCompletionTokens: completionTokens,\n\t\tTotalTokens:      inputTokens + completionTokens,\n\t}\n\n\t// Serialize the response and send it back\n\tresBytes, _ = json.Marshal(res)\n\tfmt.Fprintln(w, string(resBytes))\n}\n\n// getCompletionBody Returns the body of the request to create a completion.\nfunc getCompletionBody(r *http.Request) (openai.CompletionRequest, error) {\n\tcompletion := openai.CompletionRequest{}\n\t// read the request body\n\treqBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn openai.CompletionRequest{}, err\n\t}\n\terr = json.Unmarshal(reqBody, &completion)\n\tif err != nil {\n\t\treturn openai.CompletionRequest{}, err\n\t}\n\treturn completion, nil\n}\n\n// TestCompletionWithO1Model Tests that O1 model is not supported for completion endpoint.\nfunc TestCompletionWithO1Model(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\t_, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.O1,\n\t\t},\n\t)\n\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for O1 model, but returned: %v\", err)\n\t}\n}\n\n// TestCompletionWithGPT4DotModels Tests that newer GPT4 models are not supported for completion endpoint.\nfunc TestCompletionWithGPT4DotModels(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\tmodels := []string{\n\t\topenai.GPT4Dot1,\n\t\topenai.GPT4Dot120250414,\n\t\topenai.GPT4Dot1Mini,\n\t\topenai.GPT4Dot1Mini20250414,\n\t\topenai.GPT4Dot1Nano,\n\t\topenai.GPT4Dot1Nano20250414,\n\t\topenai.GPT4Dot5Preview,\n\t\topenai.GPT4Dot5Preview20250227,\n\t}\n\n\tfor _, model := range models {\n\t\tt.Run(model, func(t *testing.T) {\n\t\t\t_, err := client.CreateCompletion(\n\t\t\t\tcontext.Background(),\n\t\t\t\topenai.CompletionRequest{\n\t\t\t\t\tMaxTokens: 5,\n\t\t\t\t\tModel:     model,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\t\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for %s model, but returned: %v\", model, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCompletionWithGPT4oModels Tests that GPT4o models are not supported for completion endpoint.\nfunc TestCompletionWithGPT4oModels(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\tmodels := []string{\n\t\topenai.GPT4o,\n\t\topenai.GPT4o20240513,\n\t\topenai.GPT4o20240806,\n\t\topenai.GPT4o20241120,\n\t\topenai.GPT4oLatest,\n\t\topenai.GPT4oMini,\n\t\topenai.GPT4oMini20240718,\n\t}\n\n\tfor _, model := range models {\n\t\tt.Run(model, func(t *testing.T) {\n\t\t\t_, err := client.CreateCompletion(\n\t\t\t\tcontext.Background(),\n\t\t\t\topenai.CompletionRequest{\n\t\t\t\t\tMaxTokens: 5,\n\t\t\t\t\tModel:     model,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\t\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for %s model, but returned: %v\", model, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCompletionWithGPT5Models Tests that GPT5 models are not supported for completion endpoint.\nfunc TestCompletionWithGPT5Models(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\tmodels := []string{\n\t\topenai.GPT5,\n\t\topenai.GPT5Mini,\n\t\topenai.GPT5Nano,\n\t\topenai.GPT5ChatLatest,\n\t}\n\n\tfor _, model := range models {\n\t\tt.Run(model, func(t *testing.T) {\n\t\t\t_, err := client.CreateCompletion(\n\t\t\t\tcontext.Background(),\n\t\t\t\topenai.CompletionRequest{\n\t\t\t\t\tMaxTokens: 5,\n\t\t\t\t\tModel:     model,\n\t\t\t\t},\n\t\t\t)\n\t\t\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\t\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel for %s model, but returned: %v\", model, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "config.go",
    "content": "package openai\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nconst (\n\topenaiAPIURLv1                 = \"https://api.openai.com/v1\"\n\tdefaultEmptyMessagesLimit uint = 300\n\n\tazureAPIPrefix         = \"openai\"\n\tazureDeploymentsPrefix = \"deployments\"\n\n\tAnthropicAPIVersion = \"2023-06-01\"\n)\n\ntype APIType string\n\nconst (\n\tAPITypeOpenAI          APIType = \"OPEN_AI\"\n\tAPITypeAzure           APIType = \"AZURE\"\n\tAPITypeAzureAD         APIType = \"AZURE_AD\"\n\tAPITypeCloudflareAzure APIType = \"CLOUDFLARE_AZURE\"\n\tAPITypeAnthropic       APIType = \"ANTHROPIC\"\n)\n\nconst AzureAPIKeyHeader = \"api-key\"\n\nconst defaultAssistantVersion = \"v2\" // upgrade to v2 to support vector store\n\ntype HTTPDoer interface {\n\tDo(req *http.Request) (*http.Response, error)\n}\n\n// ClientConfig is a configuration of a client.\ntype ClientConfig struct {\n\tauthToken string\n\n\tBaseURL              string\n\tOrgID                string\n\tAPIType              APIType\n\tAPIVersion           string // required when APIType is APITypeAzure or APITypeAzureAD or APITypeAnthropic\n\tAssistantVersion     string\n\tAzureModelMapperFunc func(model string) string // replace model to azure deployment name func\n\tHTTPClient           HTTPDoer\n\n\tEmptyMessagesLimit uint\n}\n\nfunc DefaultConfig(authToken string) ClientConfig {\n\treturn ClientConfig{\n\t\tauthToken:        authToken,\n\t\tBaseURL:          openaiAPIURLv1,\n\t\tAPIType:          APITypeOpenAI,\n\t\tAssistantVersion: defaultAssistantVersion,\n\t\tOrgID:            \"\",\n\n\t\tHTTPClient: &http.Client{},\n\n\t\tEmptyMessagesLimit: defaultEmptyMessagesLimit,\n\t}\n}\n\nfunc DefaultAzureConfig(apiKey, baseURL string) ClientConfig {\n\treturn ClientConfig{\n\t\tauthToken:  apiKey,\n\t\tBaseURL:    baseURL,\n\t\tOrgID:      \"\",\n\t\tAPIType:    APITypeAzure,\n\t\tAPIVersion: \"2023-05-15\",\n\t\tAzureModelMapperFunc: func(model string) string {\n\t\t\t// only 3.5 models have the \".\" stripped in their names\n\t\t\tif strings.Contains(model, \"3.5\") {\n\t\t\t\treturn regexp.MustCompile(`[.:]`).ReplaceAllString(model, \"\")\n\t\t\t}\n\t\t\treturn strings.ReplaceAll(model, \":\", \"\")\n\t\t},\n\n\t\tHTTPClient: &http.Client{},\n\n\t\tEmptyMessagesLimit: defaultEmptyMessagesLimit,\n\t}\n}\n\nfunc DefaultAnthropicConfig(apiKey, baseURL string) ClientConfig {\n\tif baseURL == \"\" {\n\t\tbaseURL = \"https://api.anthropic.com/v1\"\n\t}\n\treturn ClientConfig{\n\t\tauthToken:  apiKey,\n\t\tBaseURL:    baseURL,\n\t\tOrgID:      \"\",\n\t\tAPIType:    APITypeAnthropic,\n\t\tAPIVersion: AnthropicAPIVersion,\n\n\t\tHTTPClient: &http.Client{},\n\n\t\tEmptyMessagesLimit: defaultEmptyMessagesLimit,\n\t}\n}\n\nfunc (ClientConfig) String() string {\n\treturn \"<OpenAI API ClientConfig>\"\n}\n\nfunc (c ClientConfig) GetAzureDeploymentByModel(model string) string {\n\tif c.AzureModelMapperFunc != nil {\n\t\treturn c.AzureModelMapperFunc(model)\n\t}\n\n\treturn model\n}\n"
  },
  {
    "path": "config_test.go",
    "content": "package openai_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc TestGetAzureDeploymentByModel(t *testing.T) {\n\tcases := []struct {\n\t\tModel                string\n\t\tAzureModelMapperFunc func(model string) string\n\t\tExpect               string\n\t}{\n\t\t{\n\t\t\tModel:  \"gpt-3.5-turbo\",\n\t\t\tExpect: \"gpt-35-turbo\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"gpt-3.5-turbo-0301\",\n\t\t\tExpect: \"gpt-35-turbo-0301\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"gpt-4.1\",\n\t\t\tExpect: \"gpt-4.1\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"text-embedding-ada-002\",\n\t\t\tExpect: \"text-embedding-ada-002\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"\",\n\t\t\tExpect: \"\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"models\",\n\t\t\tExpect: \"models\",\n\t\t},\n\t\t{\n\t\t\tModel:  \"gpt-3.5-turbo\",\n\t\t\tExpect: \"my-gpt35\",\n\t\t\tAzureModelMapperFunc: func(model string) string {\n\t\t\t\tmodelmapper := map[string]string{\n\t\t\t\t\t\"gpt-3.5-turbo\": \"my-gpt35\",\n\t\t\t\t}\n\t\t\t\tif val, ok := modelmapper[model]; ok {\n\t\t\t\t\treturn val\n\t\t\t\t}\n\t\t\t\treturn model\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.Model, func(t *testing.T) {\n\t\t\tconf := openai.DefaultAzureConfig(\"\", \"https://test.openai.azure.com/\")\n\t\t\tif c.AzureModelMapperFunc != nil {\n\t\t\t\tconf.AzureModelMapperFunc = c.AzureModelMapperFunc\n\t\t\t}\n\t\t\tactual := conf.GetAzureDeploymentByModel(c.Model)\n\t\t\tif actual != c.Expect {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\", c.Expect, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultAnthropicConfig(t *testing.T) {\n\tapiKey := \"test-key\"\n\tbaseURL := \"https://api.anthropic.com/v1\"\n\n\tconfig := openai.DefaultAnthropicConfig(apiKey, baseURL)\n\n\tif config.APIType != openai.APITypeAnthropic {\n\t\tt.Errorf(\"Expected APIType to be %v, got %v\", openai.APITypeAnthropic, config.APIType)\n\t}\n\n\tif config.APIVersion != openai.AnthropicAPIVersion {\n\t\tt.Errorf(\"Expected APIVersion to be 2023-06-01, got %v\", config.APIVersion)\n\t}\n\n\tif config.BaseURL != baseURL {\n\t\tt.Errorf(\"Expected BaseURL to be %v, got %v\", baseURL, config.BaseURL)\n\t}\n\n\tif config.EmptyMessagesLimit != 300 {\n\t\tt.Errorf(\"Expected EmptyMessagesLimit to be 300, got %v\", config.EmptyMessagesLimit)\n\t}\n}\n\nfunc TestDefaultAnthropicConfigWithEmptyValues(t *testing.T) {\n\tconfig := openai.DefaultAnthropicConfig(\"\", \"\")\n\n\tif config.APIType != openai.APITypeAnthropic {\n\t\tt.Errorf(\"Expected APIType to be %v, got %v\", openai.APITypeAnthropic, config.APIType)\n\t}\n\n\tif config.APIVersion != openai.AnthropicAPIVersion {\n\t\tt.Errorf(\"Expected APIVersion to be %s, got %v\", openai.AnthropicAPIVersion, config.APIVersion)\n\t}\n\n\texpectedBaseURL := \"https://api.anthropic.com/v1\"\n\tif config.BaseURL != expectedBaseURL {\n\t\tt.Errorf(\"Expected BaseURL to be %v, got %v\", expectedBaseURL, config.BaseURL)\n\t}\n}\n\nfunc TestClientConfigString(t *testing.T) {\n\t// String() should always return the constant value\n\tconf := openai.DefaultConfig(\"dummy-token\")\n\texpected := \"<OpenAI API ClientConfig>\"\n\tgot := conf.String()\n\tif got != expected {\n\t\tt.Errorf(\"ClientConfig.String() = %q; want %q\", got, expected)\n\t}\n}\n\nfunc TestGetAzureDeploymentByModel_NoMapper(t *testing.T) {\n\t// On a zero-value or DefaultConfig, AzureModelMapperFunc is nil,\n\t// so GetAzureDeploymentByModel should just return the input model.\n\tconf := openai.DefaultConfig(\"dummy-token\")\n\tmodel := \"some-model\"\n\tgot := conf.GetAzureDeploymentByModel(model)\n\tif got != model {\n\t\tt.Errorf(\"GetAzureDeploymentByModel(%q) = %q; want %q\", model, got, model)\n\t}\n}\n"
  },
  {
    "path": "edits.go",
    "content": "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.\ntype EditsRequest struct {\n\tModel       *string `json:\"model,omitempty\"`\n\tInput       string  `json:\"input,omitempty\"`\n\tInstruction string  `json:\"instruction,omitempty\"`\n\tN           int     `json:\"n,omitempty\"`\n\tTemperature float32 `json:\"temperature,omitempty\"`\n\tTopP        float32 `json:\"top_p,omitempty\"`\n}\n\n// EditsChoice represents one of possible edits.\ntype EditsChoice struct {\n\tText  string `json:\"text\"`\n\tIndex int    `json:\"index\"`\n}\n\n// EditsResponse represents a response structure for Edits API.\ntype EditsResponse struct {\n\tObject  string        `json:\"object\"`\n\tCreated int64         `json:\"created\"`\n\tUsage   Usage         `json:\"usage\"`\n\tChoices []EditsChoice `json:\"choices\"`\n\n\thttpHeader\n}\n\n// Edits Perform an API call to the Edits endpoint.\n/* Deprecated: Users of the Edits API and its associated models (e.g., text-davinci-edit-001 or code-davinci-edit-001)\nwill need to migrate to GPT-3.5 Turbo by January 4, 2024.\nYou can use CreateChatCompletion or CreateChatCompletionStream instead.\n*/\nfunc (c *Client) Edits(ctx context.Context, request EditsRequest) (response EditsResponse, err error) {\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/edits\", withModel(fmt.Sprint(request.Model))),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "edits_test.go",
    "content": "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/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\n// TestEdits Tests the edits endpoint of the API using the mocked server.\nfunc TestEdits(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/edits\", handleEditEndpoint)\n\t// create an edit request\n\tmodel := \"ada\"\n\teditReq := openai.EditsRequest{\n\t\tModel: &model,\n\t\tInput: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \" +\n\t\t\t\"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim\" +\n\t\t\t\" ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip\" +\n\t\t\t\" ex ea commodo consequat. Duis aute irure dolor in reprehe\",\n\t\tInstruction: \"test instruction\",\n\t\tN:           3,\n\t}\n\tresponse, err := client.Edits(context.Background(), editReq)\n\tchecks.NoError(t, err, \"Edits error\")\n\tif len(response.Choices) != editReq.N {\n\t\tt.Fatalf(\"edits does not properly return the correct number of choices\")\n\t}\n}\n\n// handleEditEndpoint Handles the edit endpoint by the test server.\nfunc handleEditEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// edits only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar editReq openai.EditsRequest\n\teditReq, err = getEditBody(r)\n\tif err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\t// create a response\n\tres := openai.EditsResponse{\n\t\tObject:  \"test-object\",\n\t\tCreated: time.Now().Unix(),\n\t}\n\t// edit and calculate token usage\n\teditString := \"edited by mocked OpenAI server :)\"\n\tinputTokens := numTokens(editReq.Input+editReq.Instruction) * editReq.N\n\tcompletionTokens := int(float32(len(editString))/4) * editReq.N\n\tfor i := 0; i < editReq.N; i++ {\n\t\t// instruction will be hidden and only seen by OpenAI\n\t\tres.Choices = append(res.Choices, openai.EditsChoice{\n\t\t\tText:  editReq.Input + editString,\n\t\t\tIndex: i,\n\t\t})\n\t}\n\tres.Usage = openai.Usage{\n\t\tPromptTokens:     inputTokens,\n\t\tCompletionTokens: completionTokens,\n\t\tTotalTokens:      inputTokens + completionTokens,\n\t}\n\tresBytes, _ = json.Marshal(res)\n\tfmt.Fprint(w, string(resBytes))\n}\n\n// getEditBody Returns the body of the request to create an edit.\nfunc getEditBody(r *http.Request) (openai.EditsRequest, error) {\n\tedit := openai.EditsRequest{}\n\t// read the request body\n\treqBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn openai.EditsRequest{}, err\n\t}\n\terr = json.Unmarshal(reqBody, &edit)\n\tif err != nil {\n\t\treturn openai.EditsRequest{}, err\n\t}\n\treturn edit, nil\n}\n"
  },
  {
    "path": "embeddings.go",
    "content": "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\"\n)\n\nvar ErrVectorLengthMismatch = errors.New(\"vector length mismatch\")\n\n// EmbeddingModel enumerates the models which can be used\n// to generate Embedding vectors.\ntype EmbeddingModel string\n\nconst (\n\t// Deprecated: The following block is shut down. Use text-embedding-ada-002 instead.\n\tAdaSimilarity         EmbeddingModel = \"text-similarity-ada-001\"\n\tBabbageSimilarity     EmbeddingModel = \"text-similarity-babbage-001\"\n\tCurieSimilarity       EmbeddingModel = \"text-similarity-curie-001\"\n\tDavinciSimilarity     EmbeddingModel = \"text-similarity-davinci-001\"\n\tAdaSearchDocument     EmbeddingModel = \"text-search-ada-doc-001\"\n\tAdaSearchQuery        EmbeddingModel = \"text-search-ada-query-001\"\n\tBabbageSearchDocument EmbeddingModel = \"text-search-babbage-doc-001\"\n\tBabbageSearchQuery    EmbeddingModel = \"text-search-babbage-query-001\"\n\tCurieSearchDocument   EmbeddingModel = \"text-search-curie-doc-001\"\n\tCurieSearchQuery      EmbeddingModel = \"text-search-curie-query-001\"\n\tDavinciSearchDocument EmbeddingModel = \"text-search-davinci-doc-001\"\n\tDavinciSearchQuery    EmbeddingModel = \"text-search-davinci-query-001\"\n\tAdaCodeSearchCode     EmbeddingModel = \"code-search-ada-code-001\"\n\tAdaCodeSearchText     EmbeddingModel = \"code-search-ada-text-001\"\n\tBabbageCodeSearchCode EmbeddingModel = \"code-search-babbage-code-001\"\n\tBabbageCodeSearchText EmbeddingModel = \"code-search-babbage-text-001\"\n\n\tAdaEmbeddingV2  EmbeddingModel = \"text-embedding-ada-002\"\n\tSmallEmbedding3 EmbeddingModel = \"text-embedding-3-small\"\n\tLargeEmbedding3 EmbeddingModel = \"text-embedding-3-large\"\n)\n\n// Embedding is a special format of data representation that can be easily utilized by machine\n// learning models and algorithms. The embedding is an information dense representation of the\n// semantic meaning of a piece of text. Each embedding is a vector of floating point numbers,\n// such that the distance between two embeddings in the vector space is correlated with semantic similarity\n// between two inputs in the original format. For example, if two texts are similar,\n// then their vector representations should also be similar.\ntype Embedding struct {\n\tObject    string    `json:\"object\"`\n\tEmbedding []float32 `json:\"embedding\"`\n\tIndex     int       `json:\"index\"`\n}\n\n// DotProduct calculates the dot product of the embedding vector with another\n// embedding vector. Both vectors must have the same length; otherwise, an\n// ErrVectorLengthMismatch is returned. The method returns the calculated dot\n// product as a float32 value.\nfunc (e *Embedding) DotProduct(other *Embedding) (float32, error) {\n\tif len(e.Embedding) != len(other.Embedding) {\n\t\treturn 0, ErrVectorLengthMismatch\n\t}\n\n\tvar dotProduct float32\n\tfor i := range e.Embedding {\n\t\tdotProduct += e.Embedding[i] * other.Embedding[i]\n\t}\n\n\treturn dotProduct, nil\n}\n\n// EmbeddingResponse is the response from a Create embeddings request.\ntype EmbeddingResponse struct {\n\tObject string         `json:\"object\"`\n\tData   []Embedding    `json:\"data\"`\n\tModel  EmbeddingModel `json:\"model\"`\n\tUsage  Usage          `json:\"usage\"`\n\n\thttpHeader\n}\n\ntype base64String string\n\nfunc (b base64String) Decode() ([]float32, error) {\n\tdecodedData, err := base64.StdEncoding.DecodeString(string(b))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconst sizeOfFloat32 = 4\n\tfloats := make([]float32, len(decodedData)/sizeOfFloat32)\n\tfor i := 0; i < len(floats); i++ {\n\t\tfloats[i] = math.Float32frombits(binary.LittleEndian.Uint32(decodedData[i*4 : (i+1)*4]))\n\t}\n\n\treturn floats, nil\n}\n\n// Base64Embedding is a container for base64 encoded embeddings.\ntype Base64Embedding struct {\n\tObject    string       `json:\"object\"`\n\tEmbedding base64String `json:\"embedding\"`\n\tIndex     int          `json:\"index\"`\n}\n\n// EmbeddingResponseBase64 is the response from a Create embeddings request with base64 encoding format.\ntype EmbeddingResponseBase64 struct {\n\tObject string            `json:\"object\"`\n\tData   []Base64Embedding `json:\"data\"`\n\tModel  EmbeddingModel    `json:\"model\"`\n\tUsage  Usage             `json:\"usage\"`\n\n\thttpHeader\n}\n\n// ToEmbeddingResponse converts an embeddingResponseBase64 to an EmbeddingResponse.\nfunc (r *EmbeddingResponseBase64) ToEmbeddingResponse() (EmbeddingResponse, error) {\n\tdata := make([]Embedding, len(r.Data))\n\n\tfor i, base64Embedding := range r.Data {\n\t\tembedding, err := base64Embedding.Embedding.Decode()\n\t\tif err != nil {\n\t\t\treturn EmbeddingResponse{}, err\n\t\t}\n\n\t\tdata[i] = Embedding{\n\t\t\tObject:    base64Embedding.Object,\n\t\t\tEmbedding: embedding,\n\t\t\tIndex:     base64Embedding.Index,\n\t\t}\n\t}\n\n\treturn EmbeddingResponse{\n\t\tObject: r.Object,\n\t\tModel:  r.Model,\n\t\tData:   data,\n\t\tUsage:  r.Usage,\n\t}, nil\n}\n\ntype EmbeddingRequestConverter interface {\n\t// Needs to be of type EmbeddingRequestStrings or EmbeddingRequestTokens\n\tConvert() EmbeddingRequest\n}\n\n// EmbeddingEncodingFormat is the format of the embeddings data.\n// Currently, only \"float\" and \"base64\" are supported, however, \"base64\" is not officially documented.\n// If not specified OpenAI will use \"float\".\ntype EmbeddingEncodingFormat string\n\nconst (\n\tEmbeddingEncodingFormatFloat  EmbeddingEncodingFormat = \"float\"\n\tEmbeddingEncodingFormatBase64 EmbeddingEncodingFormat = \"base64\"\n)\n\ntype EmbeddingRequest struct {\n\tInput          any                     `json:\"input\"`\n\tModel          EmbeddingModel          `json:\"model\"`\n\tUser           string                  `json:\"user,omitempty\"`\n\tEncodingFormat EmbeddingEncodingFormat `json:\"encoding_format,omitempty\"`\n\t// Dimensions The number of dimensions the resulting output embeddings should have.\n\t// Only supported in text-embedding-3 and later models.\n\tDimensions int `json:\"dimensions,omitempty\"`\n\t// The ExtraBody field allows for the inclusion of arbitrary key-value pairs\n\t// in the request body that may not be explicitly defined in this struct.\n\tExtraBody map[string]any `json:\"extra_body,omitempty\"`\n}\n\nfunc (r EmbeddingRequest) Convert() EmbeddingRequest {\n\treturn r\n}\n\n// EmbeddingRequestStrings is the input to a create embeddings request with a slice of strings.\ntype EmbeddingRequestStrings struct {\n\t// Input is a slice of strings for which you want to generate an Embedding vector.\n\t// Each input must not exceed 8192 tokens in length.\n\t// OpenAPI suggests replacing newlines (\\n) in your input with a single space, as they\n\t// have observed inferior results when newlines are present.\n\t// E.g.\n\t//\t\"The food was delicious and the waiter...\"\n\tInput []string `json:\"input\"`\n\t// ID of the model to use. You can use the List models API to see all of your available models,\n\t// or see our Model overview for descriptions of them.\n\tModel EmbeddingModel `json:\"model\"`\n\t// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse.\n\tUser string `json:\"user\"`\n\t// EmbeddingEncodingFormat is the format of the embeddings data.\n\t// Currently, only \"float\" and \"base64\" are supported, however, \"base64\" is not officially documented.\n\t// If not specified OpenAI will use \"float\".\n\tEncodingFormat EmbeddingEncodingFormat `json:\"encoding_format,omitempty\"`\n\t// Dimensions The number of dimensions the resulting output embeddings should have.\n\t// Only supported in text-embedding-3 and later models.\n\tDimensions int `json:\"dimensions,omitempty\"`\n\t// The ExtraBody field allows for the inclusion of arbitrary key-value pairs\n\t// in the request body that may not be explicitly defined in this struct.\n\tExtraBody map[string]any `json:\"extra_body,omitempty\"`\n}\n\nfunc (r EmbeddingRequestStrings) Convert() EmbeddingRequest {\n\treturn EmbeddingRequest{\n\t\tInput:          r.Input,\n\t\tModel:          r.Model,\n\t\tUser:           r.User,\n\t\tEncodingFormat: r.EncodingFormat,\n\t\tDimensions:     r.Dimensions,\n\t\tExtraBody:      r.ExtraBody,\n\t}\n}\n\ntype EmbeddingRequestTokens struct {\n\t// Input is a slice of slices of ints ([][]int) for which you want to generate an Embedding vector.\n\t// Each input must not exceed 8192 tokens in length.\n\t// OpenAPI suggests replacing newlines (\\n) in your input with a single space, as they\n\t// have observed inferior results when newlines are present.\n\t// E.g.\n\t//\t\"The food was delicious and the waiter...\"\n\tInput [][]int `json:\"input\"`\n\t// ID of the model to use. You can use the List models API to see all of your available models,\n\t// or see our Model overview for descriptions of them.\n\tModel EmbeddingModel `json:\"model\"`\n\t// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse.\n\tUser string `json:\"user\"`\n\t// EmbeddingEncodingFormat is the format of the embeddings data.\n\t// Currently, only \"float\" and \"base64\" are supported, however, \"base64\" is not officially documented.\n\t// If not specified OpenAI will use \"float\".\n\tEncodingFormat EmbeddingEncodingFormat `json:\"encoding_format,omitempty\"`\n\t// Dimensions The number of dimensions the resulting output embeddings should have.\n\t// Only supported in text-embedding-3 and later models.\n\tDimensions int `json:\"dimensions,omitempty\"`\n\t// The ExtraBody field allows for the inclusion of arbitrary key-value pairs\n\t// in the request body that may not be explicitly defined in this struct.\n\tExtraBody map[string]any `json:\"extra_body,omitempty\"`\n}\n\nfunc (r EmbeddingRequestTokens) Convert() EmbeddingRequest {\n\treturn EmbeddingRequest{\n\t\tInput:          r.Input,\n\t\tModel:          r.Model,\n\t\tUser:           r.User,\n\t\tEncodingFormat: r.EncodingFormat,\n\t\tDimensions:     r.Dimensions,\n\t\tExtraBody:      r.ExtraBody,\n\t}\n}\n\n// CreateEmbeddings returns an EmbeddingResponse which will contain an Embedding for every item in |body.Input|.\n// https://beta.openai.com/docs/api-reference/embeddings/create\n//\n// Body should be of type EmbeddingRequestStrings for embedding strings or EmbeddingRequestTokens\n// for embedding groups of text already converted to tokens.\nfunc (c *Client) CreateEmbeddings(\n\tctx context.Context,\n\tconv EmbeddingRequestConverter,\n) (res EmbeddingResponse, err error) {\n\tbaseReq := conv.Convert()\n\n\t// The body map is used to dynamically construct the request payload for the embedding API.\n\t// Instead of relying on a fixed struct, the body map allows for flexible inclusion of fields\n\t// based on their presence, avoiding unnecessary or empty fields in the request.\n\textraBody := baseReq.ExtraBody\n\tbaseReq.ExtraBody = nil\n\n\t// Serialize baseReq to JSON\n\tjsonData, err := json.Marshal(baseReq)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// Deserialize JSON to map[string]any\n\tvar body map[string]any\n\t_ = json.Unmarshal(jsonData, &body)\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/embeddings\", withModel(string(baseReq.Model))),\n\t\twithBody(body),           // Main request body.\n\t\twithExtraBody(extraBody), // Merge ExtraBody fields.\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif baseReq.EncodingFormat != EmbeddingEncodingFormatBase64 {\n\t\terr = c.sendRequest(req, &res)\n\t\treturn\n\t}\n\n\tbase64Response := &EmbeddingResponseBase64{}\n\terr = c.sendRequest(req, base64Response)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err = base64Response.ToEmbeddingResponse()\n\treturn\n}\n"
  },
  {
    "path": "embeddings_test.go",
    "content": "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\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestEmbedding(t *testing.T) {\n\tembeddedModels := []openai.EmbeddingModel{\n\t\topenai.AdaSimilarity,\n\t\topenai.BabbageSimilarity,\n\t\topenai.CurieSimilarity,\n\t\topenai.DavinciSimilarity,\n\t\topenai.AdaSearchDocument,\n\t\topenai.AdaSearchQuery,\n\t\topenai.BabbageSearchDocument,\n\t\topenai.BabbageSearchQuery,\n\t\topenai.CurieSearchDocument,\n\t\topenai.CurieSearchQuery,\n\t\topenai.DavinciSearchDocument,\n\t\topenai.DavinciSearchQuery,\n\t\topenai.AdaCodeSearchCode,\n\t\topenai.AdaCodeSearchText,\n\t\topenai.BabbageCodeSearchCode,\n\t\topenai.BabbageCodeSearchText,\n\t}\n\tfor _, model := range embeddedModels {\n\t\t// test embedding request with strings (simple embedding request)\n\t\tembeddingReq := openai.EmbeddingRequest{\n\t\t\tInput: []string{\n\t\t\t\t\"The food was delicious and the waiter\",\n\t\t\t\t\"Other examples of embedding request\",\n\t\t\t},\n\t\t\tModel: model,\n\t\t}\n\t\t// marshal embeddingReq to JSON and confirm that the model field equals\n\t\t// the AdaSearchQuery type\n\t\tmarshaled, err := json.Marshal(embeddingReq)\n\t\tchecks.NoError(t, err, \"Could not marshal embedding request\")\n\t\tif !bytes.Contains(marshaled, []byte(`\"model\":\"`+model+`\"`)) {\n\t\t\tt.Fatalf(\"Expected embedding request to contain model field\")\n\t\t}\n\n\t\t// test embedding request with strings and extra_body param\n\t\tembeddingReqWithExtraBody := openai.EmbeddingRequest{\n\t\t\tInput: []string{\n\t\t\t\t\"The food was delicious and the waiter\",\n\t\t\t\t\"Other examples of embedding request\",\n\t\t\t},\n\t\t\tModel: model,\n\t\t\tExtraBody: map[string]any{\n\t\t\t\t\"input_type\": \"query\",\n\t\t\t\t\"truncate\":   \"NONE\",\n\t\t\t},\n\t\t}\n\t\tmarshaled, err = json.Marshal(embeddingReqWithExtraBody)\n\t\tchecks.NoError(t, err, \"Could not marshal embedding request\")\n\t\tif !bytes.Contains(marshaled, []byte(`\"model\":\"`+model+`\"`)) {\n\t\t\tt.Fatalf(\"Expected embedding request to contain model field\")\n\t\t}\n\n\t\t// test embedding request with strings\n\t\tembeddingReqStrings := openai.EmbeddingRequestStrings{\n\t\t\tInput: []string{\n\t\t\t\t\"The food was delicious and the waiter\",\n\t\t\t\t\"Other examples of embedding request\",\n\t\t\t},\n\t\t\tModel: model,\n\t\t}\n\t\tmarshaled, err = json.Marshal(embeddingReqStrings)\n\t\tchecks.NoError(t, err, \"Could not marshal embedding request\")\n\t\tif !bytes.Contains(marshaled, []byte(`\"model\":\"`+model+`\"`)) {\n\t\t\tt.Fatalf(\"Expected embedding request to contain model field\")\n\t\t}\n\n\t\t// test embedding request with tokens\n\t\tembeddingReqTokens := openai.EmbeddingRequestTokens{\n\t\t\tInput: [][]int{\n\t\t\t\t{464, 2057, 373, 12625, 290, 262, 46612},\n\t\t\t\t{6395, 6096, 286, 11525, 12083, 2581},\n\t\t\t},\n\t\t\tModel: model,\n\t\t}\n\t\tmarshaled, err = json.Marshal(embeddingReqTokens)\n\t\tchecks.NoError(t, err, \"Could not marshal embedding request\")\n\t\tif !bytes.Contains(marshaled, []byte(`\"model\":\"`+model+`\"`)) {\n\t\t\tt.Fatalf(\"Expected embedding request to contain model field\")\n\t\t}\n\t}\n}\n\nfunc TestEmbeddingEndpoint(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tsampleEmbeddings := []openai.Embedding{\n\t\t{Embedding: []float32{1.23, 4.56, 7.89}},\n\t\t{Embedding: []float32{-0.006968617, -0.0052718227, 0.011901081}},\n\t}\n\n\tsampleBase64Embeddings := []openai.Base64Embedding{\n\t\t{Embedding: \"pHCdP4XrkUDhevxA\"},\n\t\t{Embedding: \"/1jku0G/rLvA/EI8\"},\n\t}\n\n\tserver.RegisterHandler(\n\t\t\"/v1/embeddings\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tvar req struct {\n\t\t\t\tEncodingFormat openai.EmbeddingEncodingFormat `json:\"encoding_format\"`\n\t\t\t\tUser           string                         `json:\"user\"`\n\t\t\t}\n\t\t\t_ = json.NewDecoder(r.Body).Decode(&req)\n\n\t\t\tvar resBytes []byte\n\t\t\tswitch {\n\t\t\tcase req.User == \"invalid\":\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\tcase req.EncodingFormat == openai.EmbeddingEncodingFormatBase64:\n\t\t\t\tresBytes, _ = json.Marshal(openai.EmbeddingResponseBase64{Data: sampleBase64Embeddings})\n\t\t\tdefault:\n\t\t\t\tresBytes, _ = json.Marshal(openai.EmbeddingResponse{Data: sampleEmbeddings})\n\t\t\t}\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\t// test create embeddings with strings (simple embedding request)\n\tres, err := client.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{})\n\tchecks.NoError(t, err, \"CreateEmbeddings error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n\n\t// test create embeddings with strings (ExtraBody in request)\n\tres, err = client.CreateEmbeddings(\n\t\tcontext.Background(),\n\t\topenai.EmbeddingRequest{\n\t\t\tExtraBody: map[string]any{\n\t\t\t\t\"input_type\": \"query\",\n\t\t\t\t\"truncate\":   \"NONE\",\n\t\t\t},\n\t\t\tDimensions: 1,\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateEmbeddings error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n\n\t// test create embeddings with strings (ExtraBody in request and )\n\t_, err = client.CreateEmbeddings(\n\t\tcontext.Background(),\n\t\topenai.EmbeddingRequest{\n\t\t\tInput: make(chan int), // Channels are not serializable\n\t\t\tModel: \"example_model\",\n\t\t},\n\t)\n\tchecks.HasError(t, err, \"CreateEmbeddings error\")\n\n\t// test failed (Serialize JSON error)\n\tres, err = client.CreateEmbeddings(\n\t\tcontext.Background(),\n\t\topenai.EmbeddingRequest{\n\t\t\tEncodingFormat: openai.EmbeddingEncodingFormatBase64,\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"CreateEmbeddings error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n\n\t// test create embeddings with strings\n\tres, err = client.CreateEmbeddings(context.Background(), openai.EmbeddingRequestStrings{})\n\tchecks.NoError(t, err, \"CreateEmbeddings strings error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n\n\t// test create embeddings with tokens\n\tres, err = client.CreateEmbeddings(context.Background(), openai.EmbeddingRequestTokens{})\n\tchecks.NoError(t, err, \"CreateEmbeddings tokens error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n\n\t// test failed sendRequest\n\t_, err = client.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{\n\t\tUser:           \"invalid\",\n\t\tEncodingFormat: openai.EmbeddingEncodingFormatBase64,\n\t})\n\tchecks.HasError(t, err, \"CreateEmbeddings error\")\n}\n\nfunc TestAzureEmbeddingEndpoint(t *testing.T) {\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\n\tsampleEmbeddings := []openai.Embedding{\n\t\t{Embedding: []float32{1.23, 4.56, 7.89}},\n\t\t{Embedding: []float32{-0.006968617, -0.0052718227, 0.011901081}},\n\t}\n\n\tserver.RegisterHandler(\n\t\t\"/openai/deployments/text-embedding-ada-002/embeddings\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.EmbeddingResponse{Data: sampleEmbeddings})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\t// test create embeddings with strings (simple embedding request)\n\tres, err := client.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{\n\t\tModel: openai.AdaEmbeddingV2,\n\t})\n\tchecks.NoError(t, err, \"CreateEmbeddings error\")\n\tif !reflect.DeepEqual(res.Data, sampleEmbeddings) {\n\t\tt.Errorf(\"Expected %#v embeddings, got %#v\", sampleEmbeddings, res.Data)\n\t}\n}\n\nfunc TestEmbeddingResponseBase64_ToEmbeddingResponse(t *testing.T) {\n\ttype fields struct {\n\t\tObject string\n\t\tData   []openai.Base64Embedding\n\t\tModel  openai.EmbeddingModel\n\t\tUsage  openai.Usage\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twant    openai.EmbeddingResponse\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"test embedding response base64 to embedding response\",\n\t\t\tfields: fields{\n\t\t\t\tData: []openai.Base64Embedding{\n\t\t\t\t\t{Embedding: \"pHCdP4XrkUDhevxA\"},\n\t\t\t\t\t{Embedding: \"/1jku0G/rLvA/EI8\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: openai.EmbeddingResponse{\n\t\t\t\tData: []openai.Embedding{\n\t\t\t\t\t{Embedding: []float32{1.23, 4.56, 7.89}},\n\t\t\t\t\t{Embedding: []float32{-0.006968617, -0.0052718227, 0.011901081}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid embedding\",\n\t\t\tfields: fields{\n\t\t\t\tData: []openai.Base64Embedding{\n\t\t\t\t\t{\n\t\t\t\t\t\tEmbedding: \"----\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant:    openai.EmbeddingResponse{},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr := &openai.EmbeddingResponseBase64{\n\t\t\t\tObject: tt.fields.Object,\n\t\t\t\tData:   tt.fields.Data,\n\t\t\t\tModel:  tt.fields.Model,\n\t\t\t\tUsage:  tt.fields.Usage,\n\t\t\t}\n\t\t\tgot, err := r.ToEmbeddingResponse()\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EmbeddingResponseBase64.ToEmbeddingResponse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"EmbeddingResponseBase64.ToEmbeddingResponse() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDotProduct(t *testing.T) {\n\tv1 := &openai.Embedding{Embedding: []float32{1, 2, 3}}\n\tv2 := &openai.Embedding{Embedding: []float32{2, 4, 6}}\n\texpected := float32(28.0)\n\n\tresult, err := v1.DotProduct(v2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\tif math.Abs(float64(result-expected)) > 1e-12 {\n\t\tt.Errorf(\"Unexpected result. Expected: %v, but got %v\", expected, result)\n\t}\n\n\tv1 = &openai.Embedding{Embedding: []float32{1, 0, 0}}\n\tv2 = &openai.Embedding{Embedding: []float32{0, 1, 0}}\n\texpected = float32(0.0)\n\n\tresult, err = v1.DotProduct(v2)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\tif math.Abs(float64(result-expected)) > 1e-12 {\n\t\tt.Errorf(\"Unexpected result. Expected: %v, but got %v\", expected, result)\n\t}\n\n\t// Test for VectorLengthMismatchError\n\tv1 = &openai.Embedding{Embedding: []float32{1, 0, 0}}\n\tv2 = &openai.Embedding{Embedding: []float32{0, 1}}\n\t_, err = v1.DotProduct(v2)\n\tif !errors.Is(err, openai.ErrVectorLengthMismatch) {\n\t\tt.Errorf(\"Expected Vector Length Mismatch Error, but got: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "engines.go",
    "content": "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 Engine struct {\n\tID     string `json:\"id\"`\n\tObject string `json:\"object\"`\n\tOwner  string `json:\"owner\"`\n\tReady  bool   `json:\"ready\"`\n\n\thttpHeader\n}\n\n// EnginesList is a list of engines.\ntype EnginesList struct {\n\tEngines []Engine `json:\"data\"`\n\n\thttpHeader\n}\n\n// ListEngines Lists the currently available engines, and provides basic\n// information about each option such as the owner and availability.\nfunc (c *Client) ListEngines(ctx context.Context) (engines EnginesList, err error) {\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(\"/engines\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &engines)\n\treturn\n}\n\n// GetEngine Retrieves an engine instance, providing basic information about\n// the engine such as the owner and availability.\nfunc (c *Client) GetEngine(\n\tctx context.Context,\n\tengineID string,\n) (engine Engine, err error) {\n\turlSuffix := fmt.Sprintf(\"/engines/%s\", engineID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &engine)\n\treturn\n}\n"
  },
  {
    "path": "engines_test.go",
    "content": "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-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\n// TestGetEngine Tests the retrieve engine endpoint of the API using the mocked server.\nfunc TestGetEngine(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/engines/text-davinci-003\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tresBytes, _ := json.Marshal(openai.Engine{})\n\t\tfmt.Fprintln(w, string(resBytes))\n\t})\n\t_, err := client.GetEngine(context.Background(), \"text-davinci-003\")\n\tchecks.NoError(t, err, \"GetEngine error\")\n}\n\n// TestListEngines Tests the list engines endpoint of the API using the mocked server.\nfunc TestListEngines(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/engines\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tresBytes, _ := json.Marshal(openai.EnginesList{})\n\t\tfmt.Fprintln(w, string(resBytes))\n\t})\n\t_, err := client.ListEngines(context.Background())\n\tchecks.NoError(t, err, \"ListEngines error\")\n}\n\nfunc TestListEnginesReturnError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/engines\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.WriteHeader(http.StatusTeapot)\n\t})\n\n\t_, err := client.ListEngines(context.Background())\n\tchecks.HasError(t, err, \"ListEngines did not fail\")\n}\n"
  },
  {
    "path": "error.go",
    "content": "package openai\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// APIError provides error information returned by the OpenAI API.\n// InnerError struct is only valid for Azure OpenAI Service.\ntype APIError struct {\n\tCode           any         `json:\"code,omitempty\"`\n\tMessage        string      `json:\"message\"`\n\tParam          *string     `json:\"param,omitempty\"`\n\tType           string      `json:\"type\"`\n\tHTTPStatus     string      `json:\"-\"`\n\tHTTPStatusCode int         `json:\"-\"`\n\tInnerError     *InnerError `json:\"innererror,omitempty\"`\n}\n\n// InnerError Azure Content filtering. Only valid for Azure OpenAI Service.\ntype InnerError struct {\n\tCode                 string               `json:\"code,omitempty\"`\n\tContentFilterResults ContentFilterResults `json:\"content_filter_result,omitempty\"`\n}\n\n// RequestError provides information about generic request errors.\ntype RequestError struct {\n\tHTTPStatus     string\n\tHTTPStatusCode int\n\tErr            error\n\tBody           []byte\n}\n\ntype ErrorResponse struct {\n\tError *APIError `json:\"error,omitempty\"`\n}\n\nfunc (e *APIError) Error() string {\n\tif e.HTTPStatusCode > 0 {\n\t\treturn fmt.Sprintf(\"error, status code: %d, status: %s, message: %s\", e.HTTPStatusCode, e.HTTPStatus, e.Message)\n\t}\n\n\treturn e.Message\n}\n\nfunc (e *APIError) UnmarshalJSON(data []byte) (err error) {\n\tvar rawMap map[string]json.RawMessage\n\terr = json.Unmarshal(data, &rawMap)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(rawMap[\"message\"], &e.Message)\n\tif err != nil {\n\t\t// If the parameter field of a function call is invalid as a JSON schema\n\t\t// refs: https://github.com/sashabaranov/go-openai/issues/381\n\t\tvar messages []string\n\t\terr = json.Unmarshal(rawMap[\"message\"], &messages)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\te.Message = strings.Join(messages, \", \")\n\t}\n\n\t// optional fields for azure openai\n\t// refs: https://github.com/sashabaranov/go-openai/issues/343\n\tif _, ok := rawMap[\"type\"]; ok {\n\t\terr = json.Unmarshal(rawMap[\"type\"], &e.Type)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif _, ok := rawMap[\"innererror\"]; ok {\n\t\terr = json.Unmarshal(rawMap[\"innererror\"], &e.InnerError)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// optional fields\n\tif _, ok := rawMap[\"param\"]; ok {\n\t\terr = json.Unmarshal(rawMap[\"param\"], &e.Param)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif _, ok := rawMap[\"code\"]; !ok {\n\t\treturn nil\n\t}\n\n\t// if the api returned a number, we need to force an integer\n\t// since the json package defaults to float64\n\tvar intCode int\n\terr = json.Unmarshal(rawMap[\"code\"], &intCode)\n\tif err == nil {\n\t\te.Code = intCode\n\t\treturn nil\n\t}\n\n\treturn json.Unmarshal(rawMap[\"code\"], &e.Code)\n}\n\nfunc (e *RequestError) Error() string {\n\treturn fmt.Sprintf(\n\t\t\"error, status code: %d, status: %s, message: %s, body: %s\",\n\t\te.HTTPStatusCode, e.HTTPStatus, e.Err, e.Body,\n\t)\n}\n\nfunc (e *RequestError) Unwrap() error {\n\treturn e.Err\n}\n"
  },
  {
    "path": "error_test.go",
    "content": "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 TestAPIErrorUnmarshalJSON(t *testing.T) {\n\ttype testCase struct {\n\t\tname      string\n\t\tresponse  string\n\t\thasError  bool\n\t\tcheckFunc func(t *testing.T, apiErr openai.APIError)\n\t}\n\ttestCases := []testCase{\n\t\t// testcase for message field\n\t\t{\n\t\t\tname:     \"parse succeeds when the message is string\",\n\t\t\tresponse: `{\"message\":\"foo\",\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"foo\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the message is array with single item\",\n\t\t\tresponse: `{\"message\":[\"foo\"],\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"foo\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the message is array with multiple items\",\n\t\t\tresponse: `{\"message\":[\"foo\", \"bar\", \"baz\"],\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"foo, bar, baz\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the message is empty array\",\n\t\t\tresponse: `{\"message\":[],\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the message is null\",\n\t\t\tresponse: `{\"message\":null,\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"parse succeeds when the innerError is not exists (Azure Openai)\",\n\t\t\tresponse: `{\n\t\t\t\t\t\t\"message\": \"test message\",\n\t\t\t\t\t\t\"type\": null,\n\t\t\t\t\t\t\"param\": \"prompt\",\n\t\t\t\t\t\t\"code\": \"content_filter\",\n\t\t\t\t\t\t\"status\": 400,\n\t\t\t\t\t\t\"innererror\": {\n\t\t\t\t\t\t\t\"code\": \"ResponsibleAIPolicyViolation\",\n\t\t\t\t\t\t\t\"content_filter_result\": {\n\t\t\t\t\t\t\t\t\"hate\": {\n\t\t\t\t\t\t\t\t\t\"filtered\": false,\n\t\t\t\t\t\t\t\t\t\"severity\": \"safe\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"self_harm\": {\n\t\t\t\t\t\t\t\t\t\"filtered\": false,\n\t\t\t\t\t\t\t\t\t\"severity\": \"safe\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"sexual\": {\n\t\t\t\t\t\t\t\t\t\"filtered\": true,\n\t\t\t\t\t\t\t\t\t\"severity\": \"medium\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"violence\": {\n\t\t\t\t\t\t\t\t\t\"filtered\": false,\n\t\t\t\t\t\t\t\t\t\"severity\": \"safe\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorInnerError(t, apiErr, &openai.InnerError{\n\t\t\t\t\tCode: \"ResponsibleAIPolicyViolation\",\n\t\t\t\t\tContentFilterResults: openai.ContentFilterResults{\n\t\t\t\t\t\tHate: openai.Hate{\n\t\t\t\t\t\t\tFiltered: false,\n\t\t\t\t\t\t\tSeverity: \"safe\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSelfHarm: openai.SelfHarm{\n\t\t\t\t\t\t\tFiltered: false,\n\t\t\t\t\t\t\tSeverity: \"safe\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSexual: openai.Sexual{\n\t\t\t\t\t\t\tFiltered: true,\n\t\t\t\t\t\t\tSeverity: \"medium\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tViolence: openai.Violence{\n\t\t\t\t\t\t\tFiltered: false,\n\t\t\t\t\t\t\tSeverity: \"safe\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the innerError is empty (Azure Openai)\",\n\t\t\tresponse: `{\"message\": \"\",\"type\": null,\"param\": \"\",\"code\": \"\",\"status\": 0,\"innererror\": {}}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorInnerError(t, apiErr, &openai.InnerError{})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the innerError is not InnerError struct (Azure Openai)\",\n\t\t\tresponse: `{\"message\": \"\",\"type\": null,\"param\": \"\",\"code\": \"\",\"status\": 0,\"innererror\": \"test\"}`,\n\t\t\thasError: true,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorInnerError(t, apiErr, &openai.InnerError{})\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse failed when the message is object\",\n\t\t\tresponse: `{\"message\":{},\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"parse failed when the message is int\",\n\t\t\tresponse: `{\"message\":1,\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"parse failed when the message is float\",\n\t\t\tresponse: `{\"message\":0.1,\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"parse failed when the message is bool\",\n\t\t\tresponse: `{\"message\":true,\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"parse failed when the message is not exists\",\n\t\t\tresponse: `{\"type\":\"invalid_request_error\",\"param\":null,\"code\":null}`,\n\t\t\thasError: true,\n\t\t},\n\t\t// testcase for code field\n\t\t{\n\t\t\tname:     \"parse succeeds when the code is int\",\n\t\t\tresponse: `{\"code\":418,\"message\":\"I'm a teapot\",\"param\":\"prompt\",\"type\":\"teapot_error\"}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorCode(t, apiErr, 418)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the code is string\",\n\t\t\tresponse: `{\"code\":\"teapot\",\"message\":\"I'm a teapot\",\"param\":\"prompt\",\"type\":\"teapot_error\"}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorCode(t, apiErr, \"teapot\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"parse succeeds when the code is not exists\",\n\t\t\tresponse: `{\"message\":\"I'm a teapot\",\"param\":\"prompt\",\"type\":\"teapot_error\"}`,\n\t\t\thasError: false,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorCode(t, apiErr, nil)\n\t\t\t},\n\t\t},\n\t\t// testcase for param field\n\t\t{\n\t\t\tname:     \"parse failed when the param is bool\",\n\t\t\tresponse: `{\"code\":418,\"message\":\"I'm a teapot\",\"param\":true,\"type\":\"teapot_error\"}`,\n\t\t\thasError: true,\n\t\t},\n\t\t// testcase for type field\n\t\t{\n\t\t\tname:     \"parse failed when the type is bool\",\n\t\t\tresponse: `{\"code\":418,\"message\":\"I'm a teapot\",\"param\":\"prompt\",\"type\":true}`,\n\t\t\thasError: true,\n\t\t},\n\t\t// testcase for error response\n\t\t{\n\t\t\tname:     \"parse failed when the response is invalid json\",\n\t\t\tresponse: `--- {\"code\":418,\"message\":\"I'm a teapot\",\"param\":\"prompt\",\"type\":\"teapot_error\"}`,\n\t\t\thasError: true,\n\t\t\tcheckFunc: func(t *testing.T, apiErr openai.APIError) {\n\t\t\t\tassertAPIErrorCode(t, apiErr, nil)\n\t\t\t\tassertAPIErrorMessage(t, apiErr, \"\")\n\t\t\t\tassertAPIErrorParam(t, apiErr, nil)\n\t\t\t\tassertAPIErrorType(t, apiErr, \"\")\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar apiErr openai.APIError\n\t\t\terr := apiErr.UnmarshalJSON([]byte(tc.response))\n\t\t\tif (err != nil) != tc.hasError {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif tc.checkFunc != nil {\n\t\t\t\ttc.checkFunc(t, apiErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc assertAPIErrorMessage(t *testing.T, apiErr openai.APIError, expected string) {\n\tif apiErr.Message != expected {\n\t\tt.Errorf(\"Unexpected APIError message: %v; expected: %s\", apiErr, expected)\n\t}\n}\n\nfunc assertAPIErrorInnerError(t *testing.T, apiErr openai.APIError, expected interface{}) {\n\tif !reflect.DeepEqual(apiErr.InnerError, expected) {\n\t\tt.Errorf(\"Unexpected APIError InnerError: %v; expected: %v; \", apiErr, expected)\n\t}\n}\n\nfunc assertAPIErrorCode(t *testing.T, apiErr openai.APIError, expected interface{}) {\n\tswitch v := apiErr.Code.(type) {\n\tcase int:\n\t\tif v != expected {\n\t\t\tt.Errorf(\"Unexpected APIError code integer: %d; expected %d\", v, expected)\n\t\t}\n\tcase string:\n\t\tif v != expected {\n\t\t\tt.Errorf(\"Unexpected APIError code string: %s; expected %s\", v, expected)\n\t\t}\n\tcase nil:\n\tdefault:\n\t\tt.Errorf(\"Unexpected APIError error code type: %T\", v)\n\t}\n}\n\nfunc assertAPIErrorParam(t *testing.T, apiErr openai.APIError, expected *string) {\n\tif apiErr.Param != expected {\n\t\tt.Errorf(\"Unexpected APIError param: %v; expected: %s\", apiErr, *expected)\n\t}\n}\n\nfunc assertAPIErrorType(t *testing.T, apiErr openai.APIError, typ string) {\n\tif apiErr.Type != typ {\n\t\tt.Errorf(\"Unexpected API type: %v; expected: %s\", apiErr, typ)\n\t}\n}\n\nfunc TestRequestError(t *testing.T) {\n\tvar err error = &openai.RequestError{\n\t\tHTTPStatusCode: http.StatusTeapot,\n\t\tErr:            errors.New(\"i am a teapot\"),\n\t}\n\n\tvar reqErr *openai.RequestError\n\tif !errors.As(err, &reqErr) {\n\t\tt.Fatalf(\"Error is not a RequestError: %+v\", err)\n\t}\n\n\tif reqErr.HTTPStatusCode != 418 {\n\t\tt.Fatalf(\"Unexpected request error status code: %d\", reqErr.HTTPStatusCode)\n\t}\n\n\tif reqErr.Unwrap() == nil {\n\t\tt.Fatalf(\"Empty request error occurred\")\n\t}\n}\n"
  },
  {
    "path": "example_test.go",
    "content": "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\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nfunc Example() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateChatCompletion(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(resp.Choices[0].Message.Content)\n}\n\nfunc ExampleClient_CreateChatCompletionStream() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\tstream, err := client.CreateChatCompletionStream(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel:     openai.GPT3Dot5Turbo,\n\t\t\tMaxTokens: 20,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Lorem ipsum\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStream: true,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletionStream error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\tfmt.Print(\"Stream response: \")\n\tfor {\n\t\tvar response openai.ChatCompletionStreamResponse\n\t\tresponse, err = stream.Recv()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tfmt.Println(\"\\nStream finished\")\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"\\nStream error: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Println(response.Choices[0].Delta.Content)\n\t}\n}\n\nfunc ExampleClient_CreateCompletion() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tModel:     openai.GPT3Babbage002,\n\t\t\tMaxTokens: 5,\n\t\t\tPrompt:    \"Lorem ipsum\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Completion error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Choices[0].Text)\n}\n\nfunc ExampleClient_CreateCompletionStream() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tstream, err := client.CreateCompletionStream(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tModel:     openai.GPT3Babbage002,\n\t\t\tMaxTokens: 5,\n\t\t\tPrompt:    \"Lorem ipsum\",\n\t\t\tStream:    true,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"CompletionStream error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\tfor {\n\t\tvar response openai.CompletionResponse\n\t\tresponse, err = stream.Recv()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tfmt.Println(\"Stream finished\")\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Stream error: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\"Stream response: %#v\\n\", response)\n\t}\n}\n\nfunc ExampleClient_CreateTranscription() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateTranscription(\n\t\tcontext.Background(),\n\t\topenai.AudioRequest{\n\t\t\tModel:    openai.Whisper1,\n\t\t\tFilePath: \"recording.mp3\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Transcription error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Text)\n}\n\nfunc ExampleClient_CreateTranscription_captions() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\tresp, err := client.CreateTranscription(\n\t\tcontext.Background(),\n\t\topenai.AudioRequest{\n\t\t\tModel:    openai.Whisper1,\n\t\t\tFilePath: os.Args[1],\n\t\t\tFormat:   openai.AudioResponseFormatSRT,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Transcription error: %v\\n\", err)\n\t\treturn\n\t}\n\tf, err := os.Create(os.Args[1] + \".srt\")\n\tif err != nil {\n\t\tfmt.Printf(\"Could not open file: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\tif _, err = f.WriteString(resp.Text); err != nil {\n\t\tfmt.Printf(\"Error writing to file: %v\\n\", err)\n\t\treturn\n\t}\n}\n\nfunc ExampleClient_CreateTranslation() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateTranslation(\n\t\tcontext.Background(),\n\t\topenai.AudioRequest{\n\t\t\tModel:    openai.Whisper1,\n\t\t\tFilePath: \"recording.mp3\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Translation error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Text)\n}\n\nfunc ExampleClient_CreateImage() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\trespURL, err := client.CreateImage(\n\t\tcontext.Background(),\n\t\topenai.ImageRequest{\n\t\t\tPrompt:         \"Parrot on a skateboard performs a trick, cartoon style, natural light, high detail\",\n\t\t\tSize:           openai.CreateImageSize256x256,\n\t\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t\t\tN:              1,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(respURL.Data[0].URL)\n}\n\nfunc ExampleClient_CreateImage_base64() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\tresp, err := client.CreateImage(\n\t\tcontext.Background(),\n\t\topenai.ImageRequest{\n\t\t\tPrompt:         \"Portrait of a humanoid parrot in a classic costume, high detail, realistic light, unreal engine\",\n\t\t\tSize:           openai.CreateImageSize512x512,\n\t\t\tResponseFormat: openai.CreateImageResponseFormatB64JSON,\n\t\t\tN:              1,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tb, err := base64.StdEncoding.DecodeString(resp.Data[0].B64JSON)\n\tif err != nil {\n\t\tfmt.Printf(\"Base64 decode error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tf, err := os.Create(\"example.png\")\n\tif err != nil {\n\t\tfmt.Printf(\"File creation error: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\n\t_, err = f.Write(b)\n\tif err != nil {\n\t\tfmt.Printf(\"File write error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(\"The image was saved as example.png\")\n}\n\nfunc ExampleClientConfig_clientWithProxy() {\n\tconfig := openai.DefaultConfig(os.Getenv(\"OPENAI_API_KEY\"))\n\tport := os.Getenv(\"OPENAI_PROXY_PORT\")\n\tproxyURL, err := url.Parse(fmt.Sprintf(\"http://localhost:%s\", port))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\ttransport := &http.Transport{\n\t\tProxy: http.ProxyURL(proxyURL),\n\t}\n\tconfig.HTTPClient = &http.Client{\n\t\tTransport: transport,\n\t}\n\n\tclient := openai.NewClientWithConfig(config)\n\n\tclient.CreateChatCompletion( //nolint:errcheck // outside of the scope of this example.\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\t// etc...\n\t\t},\n\t)\n}\n\nfunc Example_chatbot() {\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\treq := openai.ChatCompletionRequest{\n\t\tModel: openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleSystem,\n\t\t\t\tContent: \"you are a helpful chatbot\",\n\t\t\t},\n\t\t},\n\t}\n\tfmt.Println(\"Conversation\")\n\tfmt.Println(\"---------------------\")\n\tfmt.Print(\"> \")\n\ts := bufio.NewScanner(os.Stdin)\n\tfor s.Scan() {\n\t\treq.Messages = append(req.Messages, openai.ChatCompletionMessage{\n\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\tContent: s.Text(),\n\t\t})\n\t\tresp, err := client.CreateChatCompletion(context.Background(), req)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"%s\\n\\n\", resp.Choices[0].Message.Content)\n\t\treq.Messages = append(req.Messages, resp.Choices[0].Message)\n\t\tfmt.Print(\"> \")\n\t}\n}\n\nfunc ExampleDefaultAzureConfig() {\n\tazureKey := os.Getenv(\"AZURE_OPENAI_API_KEY\")       // Your azure API key\n\tazureEndpoint := os.Getenv(\"AZURE_OPENAI_ENDPOINT\") // Your azure OpenAI endpoint\n\tconfig := openai.DefaultAzureConfig(azureKey, azureEndpoint)\n\tclient := openai.NewClientWithConfig(config)\n\tresp, err := client.CreateChatCompletion(\n\t\tcontext.Background(),\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel: openai.GPT3Dot5Turbo,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: \"Hello Azure OpenAI!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Println(resp.Choices[0].Message.Content)\n}\n\n// Open-AI maintains clear documentation on how to handle API errors.\n//\n// see: https://platform.openai.com/docs/guides/error-codes/api-errors\nfunc ExampleAPIError() {\n\tvar err error // Assume this is the error you are checking.\n\te := &openai.APIError{}\n\tif errors.As(err, &e) {\n\t\tswitch e.HTTPStatusCode {\n\t\tcase 401:\n\t\t// invalid auth or key (do not retry)\n\t\tcase 429:\n\t\t// rate limiting or engine overload (wait and retry)\n\t\tcase 500:\n\t\t// openai server error (retry)\n\t\tdefault:\n\t\t\t// unhandled\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "To run an example:\n\n```\nexport OPENAI_API_KEY=\"<your key here>\"\ngo run ./example/<target>\n```\n"
  },
  {
    "path": "examples/chatbot/main.go",
    "content": "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 := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\treq := openai.ChatCompletionRequest{\n\t\tModel: openai.GPT3Dot5Turbo,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleSystem,\n\t\t\t\tContent: \"you are a helpful chatbot\",\n\t\t\t},\n\t\t},\n\t}\n\tfmt.Println(\"Conversation\")\n\tfmt.Println(\"---------------------\")\n\tfmt.Print(\"> \")\n\ts := bufio.NewScanner(os.Stdin)\n\tfor s.Scan() {\n\t\treq.Messages = append(req.Messages, openai.ChatCompletionMessage{\n\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\tContent: s.Text(),\n\t\t})\n\t\tresp, err := client.CreateChatCompletion(context.Background(), req)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"ChatCompletion error: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"%s\\n\\n\", resp.Choices[0].Message.Content)\n\t\treq.Messages = append(req.Messages, resp.Choices[0].Message)\n\t\tfmt.Print(\"> \")\n\t}\n}\n"
  },
  {
    "path": "examples/completion/main.go",
    "content": "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.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateCompletion(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tModel:     openai.GPT3Babbage002,\n\t\t\tMaxTokens: 5,\n\t\t\tPrompt:    \"Lorem ipsum\",\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Completion error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Choices[0].Text)\n}\n"
  },
  {
    "path": "examples/completion-with-tool/main.go",
    "content": "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/jsonschema\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\t// describe the function & its inputs\n\tparams := jsonschema.Definition{\n\t\tType: jsonschema.Object,\n\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\"location\": {\n\t\t\t\tType:        jsonschema.String,\n\t\t\t\tDescription: \"The city and state, e.g. San Francisco, CA\",\n\t\t\t},\n\t\t\t\"unit\": {\n\t\t\t\tType: jsonschema.String,\n\t\t\t\tEnum: []string{\"celsius\", \"fahrenheit\"},\n\t\t\t},\n\t\t},\n\t\tRequired: []string{\"location\"},\n\t}\n\tf := openai.FunctionDefinition{\n\t\tName:        \"get_current_weather\",\n\t\tDescription: \"Get the current weather in a given location\",\n\t\tParameters:  params,\n\t}\n\tt := openai.Tool{\n\t\tType:     openai.ToolTypeFunction,\n\t\tFunction: &f,\n\t}\n\n\t// simulate user asking a question that requires the function\n\tdialogue := []openai.ChatCompletionMessage{\n\t\t{Role: openai.ChatMessageRoleUser, Content: \"What is the weather in Boston today?\"},\n\t}\n\tfmt.Printf(\"Asking OpenAI '%v' and providing it a '%v()' function...\\n\",\n\t\tdialogue[0].Content, f.Name)\n\tresp, err := client.CreateChatCompletion(ctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel:    openai.GPT4TurboPreview,\n\t\t\tMessages: dialogue,\n\t\t\tTools:    []openai.Tool{t},\n\t\t},\n\t)\n\tif err != nil || len(resp.Choices) != 1 {\n\t\tfmt.Printf(\"Completion error: err:%v len(choices):%v\\n\", err,\n\t\t\tlen(resp.Choices))\n\t\treturn\n\t}\n\tmsg := resp.Choices[0].Message\n\tif len(msg.ToolCalls) != 1 {\n\t\tfmt.Printf(\"Completion error: len(toolcalls): %v\\n\", len(msg.ToolCalls))\n\t\treturn\n\t}\n\n\t// simulate calling the function & responding to OpenAI\n\tdialogue = append(dialogue, msg)\n\tfmt.Printf(\"OpenAI called us back wanting to invoke our function '%v' with params '%v'\\n\",\n\t\tmsg.ToolCalls[0].Function.Name, msg.ToolCalls[0].Function.Arguments)\n\tdialogue = append(dialogue, openai.ChatCompletionMessage{\n\t\tRole:       openai.ChatMessageRoleTool,\n\t\tContent:    \"Sunny and 80 degrees.\",\n\t\tName:       msg.ToolCalls[0].Function.Name,\n\t\tToolCallID: msg.ToolCalls[0].ID,\n\t})\n\tfmt.Printf(\"Sending OpenAI our '%v()' function's response and requesting the reply to the original question...\\n\",\n\t\tf.Name)\n\tresp, err = client.CreateChatCompletion(ctx,\n\t\topenai.ChatCompletionRequest{\n\t\t\tModel:    openai.GPT4TurboPreview,\n\t\t\tMessages: dialogue,\n\t\t\tTools:    []openai.Tool{t},\n\t\t},\n\t)\n\tif err != nil || len(resp.Choices) != 1 {\n\t\tfmt.Printf(\"2nd completion error: err:%v len(choices):%v\\n\", err,\n\t\t\tlen(resp.Choices))\n\t\treturn\n\t}\n\n\t// display OpenAI's response to the original question utilizing our function\n\tmsg = resp.Choices[0].Message\n\tfmt.Printf(\"OpenAI answered the original request with: %v\\n\",\n\t\tmsg.Content)\n}\n"
  },
  {
    "path": "examples/images/main.go",
    "content": "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.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\n\trespUrl, err := client.CreateImage(\n\t\tcontext.Background(),\n\t\topenai.ImageRequest{\n\t\t\tPrompt:         \"Parrot on a skateboard performs a trick, cartoon style, natural light, high detail\",\n\t\t\tSize:           openai.CreateImageSize256x256,\n\t\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t\t\tN:              1,\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Image creation error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(respUrl.Data[0].URL)\n}\n"
  },
  {
    "path": "examples/voice-to-text/main.go",
    "content": "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(os.Args) < 2 {\n\t\tfmt.Println(\"please provide a filename to convert to text\")\n\t\treturn\n\t}\n\tif _, err := os.Stat(os.Args[1]); errors.Is(err, os.ErrNotExist) {\n\t\tfmt.Printf(\"file %s does not exist\\n\", os.Args[1])\n\t\treturn\n\t}\n\n\tclient := openai.NewClient(os.Getenv(\"OPENAI_API_KEY\"))\n\tresp, err := client.CreateTranscription(\n\t\tcontext.Background(),\n\t\topenai.AudioRequest{\n\t\t\tModel:    openai.Whisper1,\n\t\t\tFilePath: os.Args[1],\n\t\t},\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"Transcription error: %v\\n\", err)\n\t\treturn\n\t}\n\tfmt.Println(resp.Text)\n}\n"
  },
  {
    "path": "files.go",
    "content": "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 `json:\"file\"`\n\tFilePath string `json:\"-\"`\n\tPurpose  string `json:\"purpose\"`\n}\n\n// PurposeType represents the purpose of the file when uploading.\ntype PurposeType string\n\nconst (\n\tPurposeFineTune         PurposeType = \"fine-tune\"\n\tPurposeFineTuneResults  PurposeType = \"fine-tune-results\"\n\tPurposeAssistants       PurposeType = \"assistants\"\n\tPurposeAssistantsOutput PurposeType = \"assistants_output\"\n\tPurposeBatch            PurposeType = \"batch\"\n)\n\n// FileBytesRequest represents a file upload request.\ntype FileBytesRequest struct {\n\t// the name of the uploaded file in OpenAI\n\tName string\n\t// the bytes of the file\n\tBytes []byte\n\t// the purpose of the file\n\tPurpose PurposeType\n}\n\n// File struct represents an OpenAPI file.\ntype File struct {\n\tBytes         int    `json:\"bytes\"`\n\tCreatedAt     int64  `json:\"created_at\"`\n\tID            string `json:\"id\"`\n\tFileName      string `json:\"filename\"`\n\tObject        string `json:\"object\"`\n\tStatus        string `json:\"status\"`\n\tPurpose       string `json:\"purpose\"`\n\tStatusDetails string `json:\"status_details\"`\n\n\thttpHeader\n}\n\n// FilesList is a list of files that belong to the user or organization.\ntype FilesList struct {\n\tFiles []File `json:\"data\"`\n\n\thttpHeader\n}\n\n// CreateFileBytes uploads bytes directly to OpenAI without requiring a local file.\nfunc (c *Client) CreateFileBytes(ctx context.Context, request FileBytesRequest) (file File, err error) {\n\tvar b bytes.Buffer\n\treader := bytes.NewReader(request.Bytes)\n\tbuilder := c.createFormBuilder(&b)\n\n\terr = builder.WriteField(\"purpose\", string(request.Purpose))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.CreateFormFileReader(\"file\", reader, request.Name)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.Close()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(\"/files\"),\n\t\twithBody(&b), withContentType(builder.FormDataContentType()))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &file)\n\treturn\n}\n\n// CreateFile uploads a jsonl file to GPT3\n// FilePath must be a local file path.\nfunc (c *Client) CreateFile(ctx context.Context, request FileRequest) (file File, err error) {\n\tvar b bytes.Buffer\n\tbuilder := c.createFormBuilder(&b)\n\n\terr = builder.WriteField(\"purpose\", request.Purpose)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfileData, err := os.Open(request.FilePath)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer fileData.Close()\n\n\terr = builder.CreateFormFile(\"file\", fileData)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.Close()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(\"/files\"),\n\t\twithBody(&b), withContentType(builder.FormDataContentType()))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &file)\n\treturn\n}\n\n// DeleteFile deletes an existing file.\nfunc (c *Client) DeleteFile(ctx context.Context, fileID string) (err error) {\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(\"/files/\"+fileID))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, nil)\n\treturn\n}\n\n// ListFiles Lists the currently available files,\n// and provides basic information about each file such as the file name and purpose.\nfunc (c *Client) ListFiles(ctx context.Context) (files FilesList, err error) {\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(\"/files\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &files)\n\treturn\n}\n\n// GetFile Retrieves a file instance, providing basic information about the file\n// such as the file name and purpose.\nfunc (c *Client) GetFile(ctx context.Context, fileID string) (file File, err error) {\n\turlSuffix := fmt.Sprintf(\"/files/%s\", fileID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &file)\n\treturn\n}\n\nfunc (c *Client) GetFileContent(ctx context.Context, fileID string) (content RawResponse, err error) {\n\turlSuffix := fmt.Sprintf(\"/files/%s/content\", fileID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn c.sendRequestRaw(req)\n}\n"
  },
  {
    "path": "files_api_test.go",
    "content": "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\"\n\t\"time\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestFileBytesUpload(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files\", handleCreateFile)\n\treq := openai.FileBytesRequest{\n\t\tName:    \"foo\",\n\t\tBytes:   []byte(\"foo\"),\n\t\tPurpose: openai.PurposeFineTune,\n\t}\n\t_, err := client.CreateFileBytes(context.Background(), req)\n\tchecks.NoError(t, err, \"CreateFile error\")\n}\n\nfunc TestFileUpload(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files\", handleCreateFile)\n\treq := openai.FileRequest{\n\t\tFileName: \"test.go\",\n\t\tFilePath: \"client.go\",\n\t\tPurpose:  \"fine-tune\",\n\t}\n\t_, err := client.CreateFile(context.Background(), req)\n\tchecks.NoError(t, err, \"CreateFile error\")\n}\n\n// handleCreateFile Handles the images endpoint by the test server.\nfunc handleCreateFile(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// edits only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\terr = r.ParseMultipartForm(1024 * 1024 * 1024)\n\tif err != nil {\n\t\thttp.Error(w, \"file is more than 1GB\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tvalues := r.Form\n\tvar purpose string\n\tfor key, value := range values {\n\t\tif key == \"purpose\" {\n\t\t\tpurpose = value[0]\n\t\t}\n\t}\n\tfile, header, err := r.FormFile(\"file\")\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer file.Close()\n\n\tfileReq := openai.File{\n\t\tBytes:     int(header.Size),\n\t\tID:        strconv.Itoa(int(time.Now().Unix())),\n\t\tFileName:  header.Filename,\n\t\tPurpose:   purpose,\n\t\tCreatedAt: time.Now().Unix(),\n\t\tObject:    \"test-objecct\",\n\t}\n\n\tresBytes, _ = json.Marshal(fileReq)\n\tfmt.Fprint(w, string(resBytes))\n}\n\nfunc TestDeleteFile(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files/deadbeef\", func(http.ResponseWriter, *http.Request) {})\n\terr := client.DeleteFile(context.Background(), \"deadbeef\")\n\tchecks.NoError(t, err, \"DeleteFile error\")\n}\n\nfunc TestListFile(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tresBytes, _ := json.Marshal(openai.FilesList{})\n\t\tfmt.Fprintln(w, string(resBytes))\n\t})\n\t_, err := client.ListFiles(context.Background())\n\tchecks.NoError(t, err, \"ListFiles error\")\n}\n\nfunc TestGetFile(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files/deadbeef\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tresBytes, _ := json.Marshal(openai.File{})\n\t\tfmt.Fprintln(w, string(resBytes))\n\t})\n\t_, err := client.GetFile(context.Background(), \"deadbeef\")\n\tchecks.NoError(t, err, \"GetFile error\")\n}\n\nfunc TestGetFileContent(t *testing.T) {\n\twantRespJsonl := `{\"prompt\": \"foo\", \"completion\": \"foo\"}\n{\"prompt\": \"bar\", \"completion\": \"bar\"}\n{\"prompt\": \"baz\", \"completion\": \"baz\"}\n`\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files/deadbeef/content\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// edits only accepts GET requests\n\t\tif r.Method != http.MethodGet {\n\t\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\t}\n\t\tfmt.Fprint(w, wantRespJsonl)\n\t})\n\n\tcontent, err := client.GetFileContent(context.Background(), \"deadbeef\")\n\tchecks.NoError(t, err, \"GetFileContent error\")\n\tdefer content.Close()\n\n\tactual, _ := io.ReadAll(content)\n\tif string(actual) != wantRespJsonl {\n\t\tt.Errorf(\"Expected %s, got %s\", wantRespJsonl, string(actual))\n\t}\n}\n\nfunc TestGetFileContentReturnError(t *testing.T) {\n\twantMessage := \"To help mitigate abuse, downloading of fine-tune training files is disabled for free accounts.\"\n\twantType := \"invalid_request_error\"\n\twantErrorResp := `{\n  \"error\": {\n    \"message\": \"` + wantMessage + `\",\n    \"type\": \"` + wantType + `\",\n    \"param\": null,\n    \"code\": null\n  }\n}`\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files/deadbeef/content\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\tfmt.Fprint(w, wantErrorResp)\n\t})\n\n\t_, err := client.GetFileContent(context.Background(), \"deadbeef\")\n\tif err == nil {\n\t\tt.Fatal(\"Did not return error\")\n\t}\n\n\tapiErr := &openai.APIError{}\n\tif !errors.As(err, &apiErr) {\n\t\tt.Fatalf(\"Did not return APIError: %+v\\n\", apiErr)\n\t}\n\tif apiErr.Message != wantMessage {\n\t\tt.Fatalf(\"Expected %s Message, got = %s\\n\", wantMessage, apiErr.Message)\n\t\treturn\n\t}\n\tif apiErr.Type != wantType {\n\t\tt.Fatalf(\"Expected %s Type, got = %s\\n\", wantType, apiErr.Type)\n\t\treturn\n\t}\n}\n\nfunc TestGetFileContentReturnTimeoutError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/files/deadbeef/content\", func(http.ResponseWriter, *http.Request) {\n\t\ttime.Sleep(10 * time.Nanosecond)\n\t})\n\tctx := context.Background()\n\tctx, cancel := context.WithTimeout(ctx, time.Nanosecond)\n\tdefer cancel()\n\n\t_, err := client.GetFileContent(ctx, \"deadbeef\")\n\tif err == nil {\n\t\tt.Fatal(\"Did not return error\")\n\t}\n\tif !os.IsTimeout(err) {\n\t\tt.Fatal(\"Did not return timeout error\")\n\t}\n}\n"
  },
  {
    "path": "files_test.go",
    "content": "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 \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestFileBytesUploadWithFailingFormBuilder(t *testing.T) {\n\tconfig := DefaultConfig(\"\")\n\tconfig.BaseURL = \"\"\n\tclient := NewClientWithConfig(config)\n\tmockBuilder := &mockFormBuilder{}\n\tclient.createFormBuilder = func(io.Writer) utils.FormBuilder {\n\t\treturn mockBuilder\n\t}\n\n\tctx := context.Background()\n\treq := FileBytesRequest{\n\t\tName:    \"foo\",\n\t\tBytes:   []byte(\"foo\"),\n\t\tPurpose: PurposeAssistants,\n\t}\n\n\tmockError := fmt.Errorf(\"mockWriteField error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn mockError\n\t}\n\t_, err := client.CreateFileBytes(ctx, req)\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n\n\tmockError = fmt.Errorf(\"mockCreateFormFile error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockCreateFormFileReader = func(string, io.Reader, string) error {\n\t\treturn mockError\n\t}\n\t_, err = client.CreateFileBytes(ctx, req)\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n\n\tmockError = fmt.Errorf(\"mockClose error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockCreateFormFileReader = func(string, io.Reader, string) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockClose = func() error {\n\t\treturn mockError\n\t}\n\t_, err = client.CreateFileBytes(ctx, req)\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n}\n\nfunc TestFileUploadWithFailingFormBuilder(t *testing.T) {\n\tconfig := DefaultConfig(\"\")\n\tconfig.BaseURL = \"\"\n\tclient := NewClientWithConfig(config)\n\tmockBuilder := &mockFormBuilder{}\n\tclient.createFormBuilder = func(io.Writer) utils.FormBuilder {\n\t\treturn mockBuilder\n\t}\n\n\tctx := context.Background()\n\treq := FileRequest{\n\t\tFileName: \"test.go\",\n\t\tFilePath: \"client.go\",\n\t\tPurpose:  \"fine-tune\",\n\t}\n\n\tmockError := fmt.Errorf(\"mockWriteField error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn mockError\n\t}\n\t_, err := client.CreateFile(ctx, req)\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n\n\tmockError = fmt.Errorf(\"mockCreateFormFile error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockCreateFormFile = func(string, *os.File) error {\n\t\treturn mockError\n\t}\n\t_, err = client.CreateFile(ctx, req)\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n\n\tmockError = fmt.Errorf(\"mockClose error\")\n\tmockBuilder.mockWriteField = func(string, string) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockCreateFormFile = func(string, *os.File) error {\n\t\treturn nil\n\t}\n\tmockBuilder.mockClose = func() error {\n\t\treturn mockError\n\t}\n\t_, err = client.CreateFile(ctx, req)\n\tif err == nil {\n\t\tt.Fatal(\"CreateFile should return error if form builder fails\")\n\t}\n\tchecks.ErrorIs(t, err, mockError, \"CreateFile should return error if form builder fails\")\n}\n\nfunc TestFileUploadWithNonExistentPath(t *testing.T) {\n\tconfig := DefaultConfig(\"\")\n\tconfig.BaseURL = \"\"\n\tclient := NewClientWithConfig(config)\n\n\tctx := context.Background()\n\treq := FileRequest{\n\t\tFilePath: \"some non existent file path/F616FD18-589E-44A8-BF0C-891EAE69C455\",\n\t}\n\n\t_, err := client.CreateFile(ctx, req)\n\tchecks.ErrorIs(t, err, os.ErrNotExist, \"CreateFile should return error if file does not exist\")\n}\nfunc TestCreateFileRequestBuilderFailure(t *testing.T) {\n\tconfig := DefaultConfig(\"\")\n\tconfig.BaseURL = \"\"\n\tclient := NewClientWithConfig(config)\n\tclient.requestBuilder = &failingRequestBuilder{}\n\n\tclient.createFormBuilder = func(io.Writer) utils.FormBuilder {\n\t\treturn &mockFormBuilder{\n\t\t\tmockWriteField:     func(string, string) error { return nil },\n\t\t\tmockCreateFormFile: func(string, *os.File) error { return nil },\n\t\t\tmockClose:          func() error { return nil },\n\t\t}\n\t}\n\n\t_, err := client.CreateFile(context.Background(), FileRequest{FilePath: \"client.go\"})\n\tchecks.ErrorIs(t, err, errTestRequestBuilderFailed, \"CreateFile should return error if request builder fails\")\n}\n"
  },
  {
    "path": "fine_tunes.go",
    "content": "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 deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneRequest struct {\n\tTrainingFile                 string    `json:\"training_file\"`\n\tValidationFile               string    `json:\"validation_file,omitempty\"`\n\tModel                        string    `json:\"model,omitempty\"`\n\tEpochs                       int       `json:\"n_epochs,omitempty\"`\n\tBatchSize                    int       `json:\"batch_size,omitempty\"`\n\tLearningRateMultiplier       float32   `json:\"learning_rate_multiplier,omitempty\"`\n\tPromptLossRate               float32   `json:\"prompt_loss_rate,omitempty\"`\n\tComputeClassificationMetrics bool      `json:\"compute_classification_metrics,omitempty\"`\n\tClassificationClasses        int       `json:\"classification_n_classes,omitempty\"`\n\tClassificationPositiveClass  string    `json:\"classification_positive_class,omitempty\"`\n\tClassificationBetas          []float32 `json:\"classification_betas,omitempty\"`\n\tSuffix                       string    `json:\"suffix,omitempty\"`\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTune struct {\n\tID                string              `json:\"id\"`\n\tObject            string              `json:\"object\"`\n\tModel             string              `json:\"model\"`\n\tCreatedAt         int64               `json:\"created_at\"`\n\tFineTuneEventList []FineTuneEvent     `json:\"events,omitempty\"`\n\tFineTunedModel    string              `json:\"fine_tuned_model\"`\n\tHyperParams       FineTuneHyperParams `json:\"hyperparams\"`\n\tOrganizationID    string              `json:\"organization_id\"`\n\tResultFiles       []File              `json:\"result_files\"`\n\tStatus            string              `json:\"status\"`\n\tValidationFiles   []File              `json:\"validation_files\"`\n\tTrainingFiles     []File              `json:\"training_files\"`\n\tUpdatedAt         int64               `json:\"updated_at\"`\n\n\thttpHeader\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneEvent struct {\n\tObject    string `json:\"object\"`\n\tCreatedAt int64  `json:\"created_at\"`\n\tLevel     string `json:\"level\"`\n\tMessage   string `json:\"message\"`\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneHyperParams struct {\n\tBatchSize              int     `json:\"batch_size\"`\n\tLearningRateMultiplier float64 `json:\"learning_rate_multiplier\"`\n\tEpochs                 int     `json:\"n_epochs\"`\n\tPromptLossWeight       float64 `json:\"prompt_loss_weight\"`\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneList struct {\n\tObject string     `json:\"object\"`\n\tData   []FineTune `json:\"data\"`\n\n\thttpHeader\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneEventList struct {\n\tObject string          `json:\"object\"`\n\tData   []FineTuneEvent `json:\"data\"`\n\n\thttpHeader\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\ntype FineTuneDeleteResponse struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) CreateFineTune(ctx context.Context, request FineTuneRequest) (response FineTune, err error) {\n\turlSuffix := \"/fine-tunes\"\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CancelFineTune cancel a fine-tune job.\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) CancelFineTune(ctx context.Context, fineTuneID string) (response FineTune, err error) {\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(\"/fine-tunes/\"+fineTuneID+\"/cancel\")) //nolint:lll //this method is deprecated\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) ListFineTunes(ctx context.Context) (response FineTuneList, err error) {\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(\"/fine-tunes\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) GetFineTune(ctx context.Context, fineTuneID string) (response FineTune, err error) {\n\turlSuffix := fmt.Sprintf(\"/fine-tunes/%s\", fineTuneID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) DeleteFineTune(ctx context.Context, fineTuneID string) (response FineTuneDeleteResponse, err error) {\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(\"/fine-tunes/\"+fineTuneID))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.\n// This API will be officially deprecated on January 4th, 2024.\n// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.\nfunc (c *Client) ListFineTuneEvents(ctx context.Context, fineTuneID string) (response FineTuneEventList, err error) {\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(\"/fine-tunes/\"+fineTuneID+\"/events\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "fine_tunes_test.go",
    "content": "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-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nconst testFineTuneID = \"fine-tune-id\"\n\n// TestFineTunes Tests the fine tunes endpoint of the API using the mocked server.\nfunc TestFineTunes(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\n\t\t\"/v1/fine-tunes\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tvar resBytes []byte\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ = json.Marshal(openai.FineTuneList{})\n\t\t\t} else {\n\t\t\t\tresBytes, _ = json.Marshal(openai.FineTune{})\n\t\t\t}\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine-tunes/\"+testFineTuneID+\"/cancel\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.FineTune{})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine-tunes/\"+testFineTuneID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tvar resBytes []byte\n\t\t\tif r.Method == http.MethodDelete {\n\t\t\t\tresBytes, _ = json.Marshal(openai.FineTuneDeleteResponse{})\n\t\t\t} else {\n\t\t\t\tresBytes, _ = json.Marshal(openai.FineTune{})\n\t\t\t}\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine-tunes/\"+testFineTuneID+\"/events\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.FineTuneEventList{})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.ListFineTunes(ctx)\n\tchecks.NoError(t, err, \"ListFineTunes error\")\n\n\t_, err = client.CreateFineTune(ctx, openai.FineTuneRequest{})\n\tchecks.NoError(t, err, \"CreateFineTune error\")\n\n\t_, err = client.CancelFineTune(ctx, testFineTuneID)\n\tchecks.NoError(t, err, \"CancelFineTune error\")\n\n\t_, err = client.GetFineTune(ctx, testFineTuneID)\n\tchecks.NoError(t, err, \"GetFineTune error\")\n\n\t_, err = client.DeleteFineTune(ctx, testFineTuneID)\n\tchecks.NoError(t, err, \"DeleteFineTune error\")\n\n\t_, err = client.ListFineTuneEvents(ctx, testFineTuneID)\n\tchecks.NoError(t, err, \"ListFineTuneEvents error\")\n}\n"
  },
  {
    "path": "fine_tuning_job.go",
    "content": "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          `json:\"id\"`\n\tObject          string          `json:\"object\"`\n\tCreatedAt       int64           `json:\"created_at\"`\n\tFinishedAt      int64           `json:\"finished_at\"`\n\tModel           string          `json:\"model\"`\n\tFineTunedModel  string          `json:\"fine_tuned_model,omitempty\"`\n\tOrganizationID  string          `json:\"organization_id\"`\n\tStatus          string          `json:\"status\"`\n\tHyperparameters Hyperparameters `json:\"hyperparameters\"`\n\tTrainingFile    string          `json:\"training_file\"`\n\tValidationFile  string          `json:\"validation_file,omitempty\"`\n\tResultFiles     []string        `json:\"result_files\"`\n\tTrainedTokens   int             `json:\"trained_tokens\"`\n\n\thttpHeader\n}\n\ntype Hyperparameters struct {\n\tEpochs                 any `json:\"n_epochs,omitempty\"`\n\tLearningRateMultiplier any `json:\"learning_rate_multiplier,omitempty\"`\n\tBatchSize              any `json:\"batch_size,omitempty\"`\n}\n\ntype FineTuningJobRequest struct {\n\tTrainingFile    string           `json:\"training_file\"`\n\tValidationFile  string           `json:\"validation_file,omitempty\"`\n\tModel           string           `json:\"model,omitempty\"`\n\tHyperparameters *Hyperparameters `json:\"hyperparameters,omitempty\"`\n\tSuffix          string           `json:\"suffix,omitempty\"`\n}\n\ntype FineTuningJobEventList struct {\n\tObject  string          `json:\"object\"`\n\tData    []FineTuneEvent `json:\"data\"`\n\tHasMore bool            `json:\"has_more\"`\n\n\thttpHeader\n}\n\ntype FineTuningJobEvent struct {\n\tObject    string `json:\"object\"`\n\tID        string `json:\"id\"`\n\tCreatedAt int    `json:\"created_at\"`\n\tLevel     string `json:\"level\"`\n\tMessage   string `json:\"message\"`\n\tData      any    `json:\"data\"`\n\tType      string `json:\"type\"`\n}\n\n// CreateFineTuningJob create a fine tuning job.\nfunc (c *Client) CreateFineTuningJob(\n\tctx context.Context,\n\trequest FineTuningJobRequest,\n) (response FineTuningJob, err error) {\n\turlSuffix := \"/fine_tuning/jobs\"\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CancelFineTuningJob cancel a fine tuning job.\nfunc (c *Client) CancelFineTuningJob(ctx context.Context, fineTuningJobID string) (response FineTuningJob, err error) {\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(\"/fine_tuning/jobs/\"+fineTuningJobID+\"/cancel\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveFineTuningJob retrieve a fine tuning job.\nfunc (c *Client) RetrieveFineTuningJob(\n\tctx context.Context,\n\tfineTuningJobID string,\n) (response FineTuningJob, err error) {\n\turlSuffix := fmt.Sprintf(\"/fine_tuning/jobs/%s\", fineTuningJobID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\ntype listFineTuningJobEventsParameters struct {\n\tafter *string\n\tlimit *int\n}\n\ntype ListFineTuningJobEventsParameter func(*listFineTuningJobEventsParameters)\n\nfunc ListFineTuningJobEventsWithAfter(after string) ListFineTuningJobEventsParameter {\n\treturn func(args *listFineTuningJobEventsParameters) {\n\t\targs.after = &after\n\t}\n}\n\nfunc ListFineTuningJobEventsWithLimit(limit int) ListFineTuningJobEventsParameter {\n\treturn func(args *listFineTuningJobEventsParameters) {\n\t\targs.limit = &limit\n\t}\n}\n\n// ListFineTuningJobs list fine tuning jobs events.\nfunc (c *Client) ListFineTuningJobEvents(\n\tctx context.Context,\n\tfineTuningJobID string,\n\tsetters ...ListFineTuningJobEventsParameter,\n) (response FineTuningJobEventList, err error) {\n\tparameters := &listFineTuningJobEventsParameters{\n\t\tafter: nil,\n\t\tlimit: nil,\n\t}\n\n\tfor _, setter := range setters {\n\t\tsetter(parameters)\n\t}\n\n\turlValues := url.Values{}\n\tif parameters.after != nil {\n\t\turlValues.Add(\"after\", *parameters.after)\n\t}\n\tif parameters.limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *parameters.limit))\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tc.fullURL(\"/fine_tuning/jobs/\"+fineTuningJobID+\"/events\"+encodedValues),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "fine_tuning_job_test.go",
    "content": "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-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nconst testFineTuninigJobID = \"fine-tuning-job-id\"\n\n// TestFineTuningJob Tests the fine tuning job endpoint of the API using the mocked server.\nfunc TestFineTuningJob(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\n\t\t\"/v1/fine_tuning/jobs\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.FineTuningJob{\n\t\t\t\tObject:         \"fine_tuning.job\",\n\t\t\t\tID:             testFineTuninigJobID,\n\t\t\t\tModel:          \"davinci-002\",\n\t\t\t\tCreatedAt:      1692661014,\n\t\t\t\tFinishedAt:     1692661190,\n\t\t\t\tFineTunedModel: \"ft:davinci-002:my-org:custom_suffix:7q8mpxmy\",\n\t\t\t\tOrganizationID: \"org-123\",\n\t\t\t\tResultFiles:    []string{\"file-abc123\"},\n\t\t\t\tStatus:         \"succeeded\",\n\t\t\t\tValidationFile: \"\",\n\t\t\t\tTrainingFile:   \"file-abc123\",\n\t\t\t\tHyperparameters: openai.Hyperparameters{\n\t\t\t\t\tEpochs:                 \"auto\",\n\t\t\t\t\tLearningRateMultiplier: \"auto\",\n\t\t\t\t\tBatchSize:              \"auto\",\n\t\t\t\t},\n\t\t\t\tTrainedTokens: 5768,\n\t\t\t})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine_tuning/jobs/\"+testFineTuninigJobID+\"/cancel\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.FineTuningJob{})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine_tuning/jobs/\"+testFineTuninigJobID,\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tvar resBytes []byte\n\t\t\tresBytes, _ = json.Marshal(openai.FineTuningJob{})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/fine_tuning/jobs/\"+testFineTuninigJobID+\"/events\",\n\t\tfunc(w http.ResponseWriter, _ *http.Request) {\n\t\t\tresBytes, _ := json.Marshal(openai.FineTuningJobEventList{})\n\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.CreateFineTuningJob(ctx, openai.FineTuningJobRequest{})\n\tchecks.NoError(t, err, \"CreateFineTuningJob error\")\n\n\t_, err = client.CancelFineTuningJob(ctx, testFineTuninigJobID)\n\tchecks.NoError(t, err, \"CancelFineTuningJob error\")\n\n\t_, err = client.RetrieveFineTuningJob(ctx, testFineTuninigJobID)\n\tchecks.NoError(t, err, \"RetrieveFineTuningJob error\")\n\n\t_, err = client.ListFineTuningJobEvents(ctx, testFineTuninigJobID)\n\tchecks.NoError(t, err, \"ListFineTuningJobEvents error\")\n\n\t_, err = client.ListFineTuningJobEvents(\n\t\tctx,\n\t\ttestFineTuninigJobID,\n\t\topenai.ListFineTuningJobEventsWithAfter(\"last-event-id\"),\n\t)\n\tchecks.NoError(t, err, \"ListFineTuningJobEvents error\")\n\n\t_, err = client.ListFineTuningJobEvents(\n\t\tctx,\n\t\ttestFineTuninigJobID,\n\t\topenai.ListFineTuningJobEventsWithLimit(10),\n\t)\n\tchecks.NoError(t, err, \"ListFineTuningJobEvents error\")\n\n\t_, err = client.ListFineTuningJobEvents(\n\t\tctx,\n\t\ttestFineTuninigJobID,\n\t\topenai.ListFineTuningJobEventsWithAfter(\"last-event-id\"),\n\t\topenai.ListFineTuningJobEventsWithLimit(10),\n\t)\n\tchecks.NoError(t, err, \"ListFineTuningJobEvents error\")\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/sashabaranov/go-openai\n\ngo 1.18\n"
  },
  {
    "path": "image.go",
    "content": "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.\nconst (\n\tCreateImageSize256x256   = \"256x256\"\n\tCreateImageSize512x512   = \"512x512\"\n\tCreateImageSize1024x1024 = \"1024x1024\"\n\n\t// dall-e-3 supported only.\n\tCreateImageSize1792x1024 = \"1792x1024\"\n\tCreateImageSize1024x1792 = \"1024x1792\"\n\n\t// gpt-image-1 supported only.\n\tCreateImageSize1536x1024 = \"1536x1024\" // Landscape\n\tCreateImageSize1024x1536 = \"1024x1536\" // Portrait\n)\n\nconst (\n\t// dall-e-2 and dall-e-3 only.\n\tCreateImageResponseFormatB64JSON = \"b64_json\"\n\tCreateImageResponseFormatURL     = \"url\"\n)\n\nconst (\n\tCreateImageModelDallE2    = \"dall-e-2\"\n\tCreateImageModelDallE3    = \"dall-e-3\"\n\tCreateImageModelGptImage1 = \"gpt-image-1\"\n)\n\nconst (\n\tCreateImageQualityHD       = \"hd\"\n\tCreateImageQualityStandard = \"standard\"\n\n\t// gpt-image-1 only.\n\tCreateImageQualityHigh   = \"high\"\n\tCreateImageQualityMedium = \"medium\"\n\tCreateImageQualityLow    = \"low\"\n)\n\nconst (\n\t// dall-e-3 only.\n\tCreateImageStyleVivid   = \"vivid\"\n\tCreateImageStyleNatural = \"natural\"\n)\n\nconst (\n\t// gpt-image-1 only.\n\tCreateImageBackgroundTransparent = \"transparent\"\n\tCreateImageBackgroundOpaque      = \"opaque\"\n)\n\nconst (\n\t// gpt-image-1 only.\n\tCreateImageModerationLow = \"low\"\n)\n\nconst (\n\t// gpt-image-1 only.\n\tCreateImageOutputFormatPNG  = \"png\"\n\tCreateImageOutputFormatJPEG = \"jpeg\"\n\tCreateImageOutputFormatWEBP = \"webp\"\n)\n\n// ImageRequest represents the request structure for the image API.\ntype ImageRequest struct {\n\tPrompt            string `json:\"prompt,omitempty\"`\n\tModel             string `json:\"model,omitempty\"`\n\tN                 int    `json:\"n,omitempty\"`\n\tQuality           string `json:\"quality,omitempty\"`\n\tSize              string `json:\"size,omitempty\"`\n\tStyle             string `json:\"style,omitempty\"`\n\tResponseFormat    string `json:\"response_format,omitempty\"`\n\tUser              string `json:\"user,omitempty\"`\n\tBackground        string `json:\"background,omitempty\"`\n\tModeration        string `json:\"moderation,omitempty\"`\n\tOutputCompression int    `json:\"output_compression,omitempty\"`\n\tOutputFormat      string `json:\"output_format,omitempty\"`\n}\n\n// ImageResponse represents a response structure for image API.\ntype ImageResponse struct {\n\tCreated int64                    `json:\"created,omitempty\"`\n\tData    []ImageResponseDataInner `json:\"data,omitempty\"`\n\tUsage   ImageResponseUsage       `json:\"usage,omitempty\"`\n\n\thttpHeader\n}\n\n// ImageResponseInputTokensDetails represents the token breakdown for input tokens.\ntype ImageResponseInputTokensDetails struct {\n\tTextTokens  int `json:\"text_tokens,omitempty\"`\n\tImageTokens int `json:\"image_tokens,omitempty\"`\n}\n\n// ImageResponseUsage represents the token usage information for image API.\ntype ImageResponseUsage struct {\n\tTotalTokens        int                             `json:\"total_tokens,omitempty\"`\n\tInputTokens        int                             `json:\"input_tokens,omitempty\"`\n\tOutputTokens       int                             `json:\"output_tokens,omitempty\"`\n\tInputTokensDetails ImageResponseInputTokensDetails `json:\"input_tokens_details,omitempty\"`\n}\n\n// ImageResponseDataInner represents a response data structure for image API.\ntype ImageResponseDataInner struct {\n\tURL           string `json:\"url,omitempty\"`\n\tB64JSON       string `json:\"b64_json,omitempty\"`\n\tRevisedPrompt string `json:\"revised_prompt,omitempty\"`\n}\n\n// CreateImage - API call to create an image. This is the main endpoint of the DALL-E API.\nfunc (c *Client) CreateImage(ctx context.Context, request ImageRequest) (response ImageResponse, err error) {\n\turlSuffix := \"/images/generations\"\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// WrapReader wraps an io.Reader with filename and Content-type.\nfunc WrapReader(rdr io.Reader, filename string, contentType string) io.Reader {\n\treturn file{rdr, filename, contentType}\n}\n\ntype file struct {\n\tio.Reader\n\tname        string\n\tcontentType string\n}\n\nfunc (f file) Name() string {\n\tif f.name != \"\" {\n\t\treturn f.name\n\t} else if named, ok := f.Reader.(interface{ Name() string }); ok {\n\t\treturn named.Name()\n\t}\n\treturn \"\"\n}\n\nfunc (f file) ContentType() string {\n\treturn f.contentType\n}\n\n// ImageEditRequest represents the request structure for the image API.\n// Use WrapReader to wrap an io.Reader with filename and Content-type.\ntype ImageEditRequest struct {\n\tImage          io.Reader `json:\"image,omitempty\"`\n\tMask           io.Reader `json:\"mask,omitempty\"`\n\tPrompt         string    `json:\"prompt,omitempty\"`\n\tModel          string    `json:\"model,omitempty\"`\n\tN              int       `json:\"n,omitempty\"`\n\tSize           string    `json:\"size,omitempty\"`\n\tResponseFormat string    `json:\"response_format,omitempty\"`\n\tQuality        string    `json:\"quality,omitempty\"`\n\tUser           string    `json:\"user,omitempty\"`\n}\n\n// CreateEditImage - API call to create an image. This is the main endpoint of the DALL-E API.\nfunc (c *Client) CreateEditImage(ctx context.Context, request ImageEditRequest) (response ImageResponse, err error) {\n\tbody := &bytes.Buffer{}\n\tbuilder := c.createFormBuilder(body)\n\n\t// image, filename verification can be postponed\n\terr = builder.CreateFormFileReader(\"image\", request.Image, \"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// mask, it is optional\n\tif request.Mask != nil {\n\t\t// filename verification can be postponed\n\t\terr = builder.CreateFormFileReader(\"mask\", request.Mask, \"\")\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = builder.WriteField(\"prompt\", request.Prompt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"n\", strconv.Itoa(request.N))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"size\", request.Size)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"response_format\", request.ResponseFormat)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.Close()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/images/edits\", withModel(request.Model)),\n\t\twithBody(body),\n\t\twithContentType(builder.FormDataContentType()),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ImageVariRequest represents the request structure for the image API.\n// Use WrapReader to wrap an io.Reader with filename and Content-type.\ntype ImageVariRequest struct {\n\tImage          io.Reader `json:\"image,omitempty\"`\n\tModel          string    `json:\"model,omitempty\"`\n\tN              int       `json:\"n,omitempty\"`\n\tSize           string    `json:\"size,omitempty\"`\n\tResponseFormat string    `json:\"response_format,omitempty\"`\n\tUser           string    `json:\"user,omitempty\"`\n}\n\n// CreateVariImage - API call to create an image variation. This is the main endpoint of the DALL-E API.\n// Use abbreviations(vari for variation) because ci-lint has a single-line length limit ...\nfunc (c *Client) CreateVariImage(ctx context.Context, request ImageVariRequest) (response ImageResponse, err error) {\n\tbody := &bytes.Buffer{}\n\tbuilder := c.createFormBuilder(body)\n\n\t// image, filename verification can be postponed\n\terr = builder.CreateFormFileReader(\"image\", request.Image, \"\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"n\", strconv.Itoa(request.N))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"size\", request.Size)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.WriteField(\"response_format\", request.ResponseFormat)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = builder.Close()\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/images/variations\", withModel(request.Model)),\n\t\twithBody(body),\n\t\twithContentType(builder.FormDataContentType()),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "image_api_test.go",
    "content": "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\"time\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestImages(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/images/generations\", handleImageEndpoint)\n\t_, err := client.CreateImage(context.Background(), openai.ImageRequest{\n\t\tPrompt:         \"Lorem ipsum\",\n\t\tModel:          openai.CreateImageModelDallE3,\n\t\tN:              1,\n\t\tQuality:        openai.CreateImageQualityHD,\n\t\tSize:           openai.CreateImageSize1024x1024,\n\t\tStyle:          openai.CreateImageStyleVivid,\n\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t\tUser:           \"user\",\n\t})\n\tchecks.NoError(t, err, \"CreateImage error\")\n}\n\n// handleImageEndpoint Handles the images endpoint by the test server.\nfunc handleImageEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// images only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar imageReq openai.ImageRequest\n\tif imageReq, err = getImageBody(r); err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tres := openai.ImageResponse{\n\t\tCreated: time.Now().Unix(),\n\t}\n\tfor i := 0; i < imageReq.N; i++ {\n\t\timageData := openai.ImageResponseDataInner{}\n\t\tswitch imageReq.ResponseFormat {\n\t\tcase openai.CreateImageResponseFormatURL, \"\":\n\t\t\timageData.URL = \"https://example.com/image.png\"\n\t\tcase openai.CreateImageResponseFormatB64JSON:\n\t\t\t// This decodes to \"{}\" in base64.\n\t\t\timageData.B64JSON = \"e30K\"\n\t\tdefault:\n\t\t\thttp.Error(w, \"invalid response format\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tres.Data = append(res.Data, imageData)\n\t}\n\tresBytes, _ = json.Marshal(res)\n\tfmt.Fprintln(w, string(resBytes))\n}\n\n// getImageBody Returns the body of the request to create a image.\nfunc getImageBody(r *http.Request) (openai.ImageRequest, error) {\n\timage := openai.ImageRequest{}\n\t// read the request body\n\treqBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn openai.ImageRequest{}, err\n\t}\n\terr = json.Unmarshal(reqBody, &image)\n\tif err != nil {\n\t\treturn openai.ImageRequest{}, err\n\t}\n\treturn image, nil\n}\n\nfunc TestImageEdit(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/images/edits\", handleEditImageEndpoint)\n\n\torigin, err := os.Create(filepath.Join(t.TempDir(), \"image.png\"))\n\tif err != nil {\n\t\tt.Fatalf(\"open origin file error: %v\", err)\n\t}\n\tdefer origin.Close()\n\n\tmask, err := os.Create(filepath.Join(t.TempDir(), \"mask.png\"))\n\tif err != nil {\n\t\tt.Fatalf(\"open mask file error: %v\", err)\n\t}\n\tdefer mask.Close()\n\n\t_, err = client.CreateEditImage(context.Background(), openai.ImageEditRequest{\n\t\tImage:          origin,\n\t\tMask:           mask,\n\t\tPrompt:         \"There is a turtle in the pool\",\n\t\tN:              3,\n\t\tSize:           openai.CreateImageSize1024x1024,\n\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t})\n\tchecks.NoError(t, err, \"CreateImage error\")\n}\n\nfunc TestImageEditWithoutMask(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/images/edits\", handleEditImageEndpoint)\n\n\torigin, err := os.Create(filepath.Join(t.TempDir(), \"image.png\"))\n\tif err != nil {\n\t\tt.Fatalf(\"open origin file error: %v\", err)\n\t}\n\tdefer origin.Close()\n\n\t_, err = client.CreateEditImage(context.Background(), openai.ImageEditRequest{\n\t\tImage:          origin,\n\t\tPrompt:         \"There is a turtle in the pool\",\n\t\tN:              3,\n\t\tSize:           openai.CreateImageSize1024x1024,\n\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t})\n\tchecks.NoError(t, err, \"CreateImage error\")\n}\n\n// handleEditImageEndpoint Handles the images endpoint by the test server.\nfunc handleEditImageEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar resBytes []byte\n\n\t// images only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\n\tresponses := openai.ImageResponse{\n\t\tCreated: time.Now().Unix(),\n\t\tData: []openai.ImageResponseDataInner{\n\t\t\t{\n\t\t\t\tURL:     \"test-url1\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tURL:     \"test-url2\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tURL:     \"test-url3\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t},\n\t}\n\n\tresBytes, _ = json.Marshal(responses)\n\tfmt.Fprintln(w, string(resBytes))\n}\n\nfunc TestImageVariation(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/images/variations\", handleVariateImageEndpoint)\n\n\torigin, err := os.Create(filepath.Join(t.TempDir(), \"image.png\"))\n\tif err != nil {\n\t\tt.Fatalf(\"open origin file error: %v\", err)\n\t}\n\tdefer origin.Close()\n\n\t_, err = client.CreateVariImage(context.Background(), openai.ImageVariRequest{\n\t\tImage:          origin,\n\t\tN:              3,\n\t\tSize:           openai.CreateImageSize1024x1024,\n\t\tResponseFormat: openai.CreateImageResponseFormatURL,\n\t})\n\tchecks.NoError(t, err, \"CreateImage error\")\n}\n\n// handleVariateImageEndpoint Handles the images endpoint by the test server.\nfunc handleVariateImageEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar resBytes []byte\n\n\t// images only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\n\tresponses := openai.ImageResponse{\n\t\tCreated: time.Now().Unix(),\n\t\tData: []openai.ImageResponseDataInner{\n\t\t\t{\n\t\t\t\tURL:     \"test-url1\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tURL:     \"test-url2\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tURL:     \"test-url3\",\n\t\t\t\tB64JSON: \"\",\n\t\t\t},\n\t\t},\n\t}\n\n\tresBytes, _ = json.Marshal(responses)\n\tfmt.Fprintln(w, string(resBytes))\n}\n"
  },
  {
    "path": "image_test.go",
    "content": "package openai //nolint:testpackage // testing private field\n\nimport (\n\tutils \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n)\n\ntype mockFormBuilder struct {\n\tmockCreateFormFile       func(string, *os.File) error\n\tmockCreateFormFileReader func(string, io.Reader, string) error\n\tmockWriteField           func(string, string) error\n\tmockClose                func() error\n}\n\nfunc (fb *mockFormBuilder) CreateFormFile(fieldname string, file *os.File) error {\n\treturn fb.mockCreateFormFile(fieldname, file)\n}\n\nfunc (fb *mockFormBuilder) CreateFormFileReader(fieldname string, r io.Reader, filename string) error {\n\treturn fb.mockCreateFormFileReader(fieldname, r, filename)\n}\n\nfunc (fb *mockFormBuilder) WriteField(fieldname, value string) error {\n\treturn fb.mockWriteField(fieldname, value)\n}\n\nfunc (fb *mockFormBuilder) Close() error {\n\treturn fb.mockClose()\n}\n\nfunc (fb *mockFormBuilder) FormDataContentType() string {\n\treturn \"\"\n}\n\nfunc TestImageFormBuilderFailures(t *testing.T) {\n\tctx := context.Background()\n\tmockFailedErr := fmt.Errorf(\"mock form builder fail\")\n\n\tnewClient := func(fb *mockFormBuilder) *Client {\n\t\tcfg := DefaultConfig(\"\")\n\t\tcfg.BaseURL = \"\"\n\t\tc := NewClientWithConfig(cfg)\n\t\tc.createFormBuilder = func(io.Writer) utils.FormBuilder { return fb }\n\t\treturn c\n\t}\n\n\ttests := []struct {\n\t\tname  string\n\t\tsetup func(*mockFormBuilder)\n\t\treq   ImageEditRequest\n\t}{\n\t\t{\n\t\t\tname: \"image\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return mockFailedErr }\n\t\t\t\tfb.mockWriteField = func(string, string) error { return nil }\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"mask\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(name string, _ io.Reader, _ string) error {\n\t\t\t\t\tif name == \"mask\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockWriteField = func(string, string) error { return nil }\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"prompt\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field, _ string) error {\n\t\t\t\t\tif field == \"prompt\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"n\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field, _ string) error {\n\t\t\t\t\tif field == \"n\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"size\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field, _ string) error {\n\t\t\t\t\tif field == \"size\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"response_format\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field, _ string) error {\n\t\t\t\t\tif field == \"response_format\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"close\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(string, string) error { return nil }\n\t\t\t\tfb.mockClose = func() error { return mockFailedErr }\n\t\t\t},\n\t\t\treq: ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfb := &mockFormBuilder{}\n\t\t\ttc.setup(fb)\n\t\t\tclient := newClient(fb)\n\t\t\t_, err := client.CreateEditImage(ctx, tc.req)\n\t\t\tchecks.ErrorIs(t, err, mockFailedErr, \"CreateEditImage should return error if form builder fails\")\n\t\t})\n\t}\n\n\tt.Run(\"new request\", func(t *testing.T) {\n\t\tfb := &mockFormBuilder{\n\t\t\tmockCreateFormFileReader: func(string, io.Reader, string) error { return nil },\n\t\t\tmockWriteField:           func(string, string) error { return nil },\n\t\t\tmockClose:                func() error { return nil },\n\t\t}\n\t\tclient := newClient(fb)\n\t\tclient.requestBuilder = &failingRequestBuilder{}\n\n\t\t_, err := client.CreateEditImage(ctx, ImageEditRequest{Image: bytes.NewBuffer(nil), Mask: bytes.NewBuffer(nil)})\n\t\tchecks.ErrorIs(t, err, errTestRequestBuilderFailed, \"CreateEditImage should return error if request builder fails\")\n\t})\n}\n\nfunc TestVariImageFormBuilderFailures(t *testing.T) {\n\tctx := context.Background()\n\tmockFailedErr := fmt.Errorf(\"mock form builder fail\")\n\n\tnewClient := func(fb *mockFormBuilder) *Client {\n\t\tcfg := DefaultConfig(\"\")\n\t\tcfg.BaseURL = \"\"\n\t\tc := NewClientWithConfig(cfg)\n\t\tc.createFormBuilder = func(io.Writer) utils.FormBuilder { return fb }\n\t\treturn c\n\t}\n\n\ttests := []struct {\n\t\tname  string\n\t\tsetup func(*mockFormBuilder)\n\t\treq   ImageVariRequest\n\t}{\n\t\t{\n\t\t\tname: \"image\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return mockFailedErr }\n\t\t\t\tfb.mockWriteField = func(string, string) error { return nil }\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageVariRequest{Image: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"n\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field string, _ string) error {\n\t\t\t\t\tif field == \"n\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageVariRequest{Image: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"size\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field string, _ string) error {\n\t\t\t\t\tif field == \"size\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageVariRequest{Image: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"response_format\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(field string, _ string) error {\n\t\t\t\t\tif field == \"response_format\" {\n\t\t\t\t\t\treturn mockFailedErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfb.mockClose = func() error { return nil }\n\t\t\t},\n\t\t\treq: ImageVariRequest{Image: bytes.NewBuffer(nil)},\n\t\t},\n\t\t{\n\t\t\tname: \"close\",\n\t\t\tsetup: func(fb *mockFormBuilder) {\n\t\t\t\tfb.mockCreateFormFileReader = func(string, io.Reader, string) error { return nil }\n\t\t\t\tfb.mockWriteField = func(string, string) error { return nil }\n\t\t\t\tfb.mockClose = func() error { return mockFailedErr }\n\t\t\t},\n\t\t\treq: ImageVariRequest{Image: bytes.NewBuffer(nil)},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfb := &mockFormBuilder{}\n\t\t\ttc.setup(fb)\n\t\t\tclient := newClient(fb)\n\t\t\t_, err := client.CreateVariImage(ctx, tc.req)\n\t\t\tchecks.ErrorIs(t, err, mockFailedErr, \"CreateVariImage should return error if form builder fails\")\n\t\t})\n\t}\n\n\tt.Run(\"new request\", func(t *testing.T) {\n\t\tfb := &mockFormBuilder{\n\t\t\tmockCreateFormFileReader: func(string, io.Reader, string) error { return nil },\n\t\t\tmockWriteField:           func(string, string) error { return nil },\n\t\t\tmockClose:                func() error { return nil },\n\t\t}\n\t\tclient := newClient(fb)\n\t\tclient.requestBuilder = &failingRequestBuilder{}\n\n\t\t_, err := client.CreateVariImage(ctx, ImageVariRequest{Image: bytes.NewBuffer(nil)})\n\t\tchecks.ErrorIs(t, err, errTestRequestBuilderFailed, \"CreateVariImage should return error if request builder fails\")\n\t})\n}\n\ntype testNamedReader struct{ io.Reader }\n\nfunc (testNamedReader) Name() string { return \"named.txt\" }\n\nfunc TestWrapReader(t *testing.T) {\n\tr := bytes.NewBufferString(\"data\")\n\twrapped := WrapReader(r, \"file.png\", \"image/png\")\n\tf, ok := wrapped.(interface {\n\t\tName() string\n\t\tContentType() string\n\t})\n\tif !ok {\n\t\tt.Fatal(\"wrapped reader missing Name or ContentType\")\n\t}\n\tif f.Name() != \"file.png\" {\n\t\tt.Fatalf(\"expected name file.png, got %s\", f.Name())\n\t}\n\tif f.ContentType() != \"image/png\" {\n\t\tt.Fatalf(\"expected content type image/png, got %s\", f.ContentType())\n\t}\n\n\t// test name from underlying reader\n\tnr := testNamedReader{Reader: bytes.NewBufferString(\"d\")}\n\twrapped = WrapReader(nr, \"\", \"text/plain\")\n\tf, ok = wrapped.(interface {\n\t\tName() string\n\t\tContentType() string\n\t})\n\tif !ok {\n\t\tt.Fatal(\"wrapped named reader missing Name or ContentType\")\n\t}\n\tif f.Name() != \"named.txt\" {\n\t\tt.Fatalf(\"expected name named.txt, got %s\", f.Name())\n\t}\n\tif f.ContentType() != \"text/plain\" {\n\t\tt.Fatalf(\"expected content type text/plain, got %s\", f.ContentType())\n\t}\n\n\t// no name provided\n\twrapped = WrapReader(bytes.NewBuffer(nil), \"\", \"\")\n\tf2, ok := wrapped.(interface{ Name() string })\n\tif !ok {\n\t\tt.Fatal(\"wrapped anonymous reader missing Name\")\n\t}\n\tif f2.Name() != \"\" {\n\t\tt.Fatalf(\"expected empty name, got %s\", f2.Name())\n\t}\n}\n"
  },
  {
    "path": "internal/error_accumulator.go",
    "content": "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() []byte\n}\n\ntype errorBuffer interface {\n\tio.Writer\n\tLen() int\n\tBytes() []byte\n}\n\ntype DefaultErrorAccumulator struct {\n\tBuffer errorBuffer\n}\n\nfunc NewErrorAccumulator() ErrorAccumulator {\n\treturn &DefaultErrorAccumulator{\n\t\tBuffer: &bytes.Buffer{},\n\t}\n}\n\nfunc (e *DefaultErrorAccumulator) Write(p []byte) error {\n\t_, err := e.Buffer.Write(p)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error accumulator write error, %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (e *DefaultErrorAccumulator) Bytes() (errBytes []byte) {\n\tif e.Buffer.Len() == 0 {\n\t\treturn\n\t}\n\terrBytes = e.Buffer.Bytes()\n\treturn\n}\n"
  },
  {
    "path": "internal/error_accumulator_test.go",
    "content": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestDefaultErrorAccumulator_WriteMultiple(t *testing.T) {\n\tea, ok := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator)\n\tif !ok {\n\t\tt.Fatal(\"type assertion to *DefaultErrorAccumulator failed\")\n\t}\n\tchecks.NoError(t, ea.Write([]byte(\"{\\\"error\\\": \\\"test1\\\"}\")))\n\tchecks.NoError(t, ea.Write([]byte(\"{\\\"error\\\": \\\"test2\\\"}\")))\n\n\texpected := \"{\\\"error\\\": \\\"test1\\\"}{\\\"error\\\": \\\"test2\\\"}\"\n\tif string(ea.Bytes()) != expected {\n\t\tt.Fatalf(\"Expected %q, got %q\", expected, ea.Bytes())\n\t}\n}\n\nfunc TestDefaultErrorAccumulator_EmptyBuffer(t *testing.T) {\n\tea, ok := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator)\n\tif !ok {\n\t\tt.Fatal(\"type assertion to *DefaultErrorAccumulator failed\")\n\t}\n\tif len(ea.Bytes()) != 0 {\n\t\tt.Fatal(\"Buffer should be empty initially\")\n\t}\n}\n\nfunc TestDefaultErrorAccumulator_WriteError(t *testing.T) {\n\tea := &openai.DefaultErrorAccumulator{Buffer: &test.FailingErrorBuffer{}}\n\terr := ea.Write([]byte(\"fail\"))\n\tchecks.ErrorIs(t, err, test.ErrTestErrorAccumulatorWriteFailed, \"Write should propagate buffer errors\")\n}\n"
  },
  {
    "path": "internal/form_builder.go",
    "content": "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 FormBuilder interface {\n\tCreateFormFile(fieldname string, file *os.File) error\n\tCreateFormFileReader(fieldname string, r io.Reader, filename string) error\n\tWriteField(fieldname, value string) error\n\tClose() error\n\tFormDataContentType() string\n}\n\ntype DefaultFormBuilder struct {\n\twriter *multipart.Writer\n}\n\nfunc NewFormBuilder(body io.Writer) *DefaultFormBuilder {\n\treturn &DefaultFormBuilder{\n\t\twriter: multipart.NewWriter(body),\n\t}\n}\n\nfunc (fb *DefaultFormBuilder) CreateFormFile(fieldname string, file *os.File) error {\n\treturn fb.createFormFile(fieldname, file, file.Name())\n}\n\nvar quoteEscaper = strings.NewReplacer(\"\\\\\", \"\\\\\\\\\", `\"`, \"\\\\\\\"\")\n\nfunc escapeQuotes(s string) string {\n\treturn quoteEscaper.Replace(s)\n}\n\n// CreateFormFileReader creates a form field with a file reader.\n// The filename in Content-Disposition is required.\nfunc (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r io.Reader, filename string) error {\n\tif filename == \"\" {\n\t\tif f, ok := r.(interface{ Name() string }); ok {\n\t\t\tfilename = f.Name()\n\t\t}\n\t}\n\tvar contentType string\n\tif f, ok := r.(interface{ ContentType() string }); ok {\n\t\tcontentType = f.ContentType()\n\t}\n\n\th := make(textproto.MIMEHeader)\n\th.Set(\n\t\t\"Content-Disposition\",\n\t\tfmt.Sprintf(\n\t\t\t`form-data; name=\"%s\"; filename=\"%s\"`,\n\t\t\tescapeQuotes(fieldname),\n\t\t\tescapeQuotes(filepath.Base(filename)),\n\t\t),\n\t)\n\t// content type is optional, but it can be set\n\tif contentType != \"\" {\n\t\th.Set(\"Content-Type\", contentType)\n\t}\n\n\tfieldWriter, err := fb.writer.CreatePart(h)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(fieldWriter, r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (fb *DefaultFormBuilder) createFormFile(fieldname string, r io.Reader, filename string) error {\n\tif filename == \"\" {\n\t\treturn fmt.Errorf(\"filename cannot be empty\")\n\t}\n\n\tfieldWriter, err := fb.writer.CreateFormFile(fieldname, filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(fieldWriter, r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (fb *DefaultFormBuilder) WriteField(fieldname, value string) error {\n\tif fieldname == \"\" {\n\t\treturn fmt.Errorf(\"fieldname cannot be empty\")\n\t}\n\treturn fb.writer.WriteField(fieldname, value)\n}\n\nfunc (fb *DefaultFormBuilder) Close() error {\n\treturn fb.writer.Close()\n}\n\nfunc (fb *DefaultFormBuilder) FormDataContentType() string {\n\treturn fb.writer.FormDataContentType()\n}\n"
  },
  {
    "path": "internal/form_builder_test.go",
    "content": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"bytes\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\ntype mockFormBuilder struct {\n\tmockCreateFormFile func(string, *os.File) error\n\tmockWriteField     func(string, string) error\n\tmockClose          func() error\n}\n\nfunc (m *mockFormBuilder) CreateFormFile(fieldname string, file *os.File) error {\n\treturn m.mockCreateFormFile(fieldname, file)\n}\n\nfunc (m *mockFormBuilder) WriteField(fieldname, value string) error {\n\treturn m.mockWriteField(fieldname, value)\n}\n\nfunc (m *mockFormBuilder) Close() error {\n\treturn m.mockClose()\n}\n\nfunc (m *mockFormBuilder) FormDataContentType() string {\n\treturn \"\"\n}\n\nfunc TestCloseMethod(t *testing.T) {\n\tt.Run(\"NormalClose\", func(t *testing.T) {\n\t\tbody := &bytes.Buffer{}\n\t\tbuilder := NewFormBuilder(body)\n\t\tchecks.NoError(t, builder.Close(), \"正常关闭应成功\")\n\t})\n\n\tt.Run(\"ErrorPropagation\", func(t *testing.T) {\n\t\terrorMock := errors.New(\"mock close error\")\n\t\tmockBuilder := &mockFormBuilder{\n\t\t\tmockClose: func() error {\n\t\t\t\treturn errorMock\n\t\t\t},\n\t\t}\n\t\terr := mockBuilder.Close()\n\t\tchecks.ErrorIs(t, err, errorMock, \"应传递关闭错误\")\n\t})\n}\n\ntype failingWriter struct {\n}\n\nvar errMockFailingWriterError = errors.New(\"mock writer failed\")\n\nfunc (*failingWriter) Write([]byte) (int, error) {\n\treturn 0, errMockFailingWriterError\n}\n\nfunc TestFormBuilderWithFailingWriter(t *testing.T) {\n\tfile, err := os.CreateTemp(t.TempDir(), \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating tmp file: %v\", err)\n\t}\n\tdefer file.Close()\n\n\tbuilder := NewFormBuilder(&failingWriter{})\n\terr = builder.CreateFormFile(\"file\", file)\n\tchecks.ErrorIs(t, err, errMockFailingWriterError, \"formbuilder should return error if writer fails\")\n}\n\nfunc TestFormBuilderWithClosedFile(t *testing.T) {\n\tfile, err := os.CreateTemp(t.TempDir(), \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating tmp file: %v\", err)\n\t}\n\tfile.Close()\n\n\tbody := &bytes.Buffer{}\n\tbuilder := NewFormBuilder(body)\n\terr = builder.CreateFormFile(\"file\", file)\n\tchecks.HasError(t, err, \"formbuilder should return error if file is closed\")\n\tchecks.ErrorIs(t, err, os.ErrClosed, \"formbuilder should return error if file is closed\")\n}\n\ntype failingReader struct {\n}\n\nvar errMockFailingReaderError = errors.New(\"mock reader failed\")\n\nfunc (*failingReader) Read([]byte) (int, error) {\n\treturn 0, errMockFailingReaderError\n}\n\ntype readerWithNameAndContentType struct {\n\tio.Reader\n}\n\nfunc (*readerWithNameAndContentType) Name() string {\n\treturn \"\"\n}\n\nfunc (*readerWithNameAndContentType) ContentType() string {\n\treturn \"image/png\"\n}\n\nfunc TestFormBuilderWithReader(t *testing.T) {\n\tfile, err := os.CreateTemp(t.TempDir(), \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error creating tmp file: %v\", err)\n\t}\n\tdefer file.Close()\n\tbuilder := NewFormBuilder(&failingWriter{})\n\terr = builder.CreateFormFileReader(\"file\", file, file.Name())\n\tchecks.ErrorIs(t, err, errMockFailingWriterError, \"formbuilder should return error if writer fails\")\n\n\tbuilder = NewFormBuilder(&bytes.Buffer{})\n\treader := &failingReader{}\n\terr = builder.CreateFormFileReader(\"file\", reader, \"\")\n\tchecks.ErrorIs(t, err, errMockFailingReaderError, \"formbuilder should return error if copy reader fails\")\n\n\tsuccessReader := &bytes.Buffer{}\n\terr = builder.CreateFormFileReader(\"file\", successReader, \"\")\n\tchecks.NoError(t, err, \"formbuilder should not return error\")\n\n\trnc := &readerWithNameAndContentType{Reader: &bytes.Buffer{}}\n\terr = builder.CreateFormFileReader(\"file\", rnc, \"\")\n\tchecks.NoError(t, err, \"formbuilder should not return error\")\n}\n\nfunc TestFormDataContentType(t *testing.T) {\n\tt.Run(\"ReturnsUnderlyingWriterContentType\", func(t *testing.T) {\n\t\tbuf := &bytes.Buffer{}\n\t\tbuilder := NewFormBuilder(buf)\n\n\t\tcontentType := builder.FormDataContentType()\n\t\tif contentType == \"\" {\n\t\t\tt.Errorf(\"expected non-empty content type, got empty string\")\n\t\t}\n\t})\n}\n\nfunc TestWriteField(t *testing.T) {\n\tt.Run(\"EmptyFieldNameShouldReturnError\", func(t *testing.T) {\n\t\tbuf := &bytes.Buffer{}\n\t\tbuilder := NewFormBuilder(buf)\n\n\t\terr := builder.WriteField(\"\", \"some value\")\n\t\tchecks.HasError(t, err, \"fieldname is required\")\n\t})\n\n\tt.Run(\"ValidFieldNameShouldSucceed\", func(t *testing.T) {\n\t\tbuf := &bytes.Buffer{}\n\t\tbuilder := NewFormBuilder(buf)\n\n\t\terr := builder.WriteField(\"key\", \"value\")\n\t\tchecks.NoError(t, err, \"should write field without error\")\n\t})\n}\n\nfunc TestCreateFormFile(t *testing.T) {\n\tbuf := &bytes.Buffer{}\n\tbuilder := NewFormBuilder(buf)\n\n\terr := builder.createFormFile(\"file\", bytes.NewBufferString(\"data\"), \"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty filename\")\n\t}\n\n\tbuilder = NewFormBuilder(&failingWriter{})\n\terr = builder.createFormFile(\"file\", bytes.NewBufferString(\"data\"), \"name\")\n\tchecks.ErrorIs(t, err, errMockFailingWriterError, \"should propagate writer error\")\n}\n\nfunc TestCreateFormFileSuccess(t *testing.T) {\n\tbuf := &bytes.Buffer{}\n\tbuilder := NewFormBuilder(buf)\n\n\terr := builder.createFormFile(\"file\", bytes.NewBufferString(\"data\"), \"foo.txt\")\n\tchecks.NoError(t, err, \"createFormFile should succeed\")\n\n\tif !strings.Contains(buf.String(), \"filename=\\\"foo.txt\\\"\") {\n\t\tt.Fatalf(\"expected filename header, got %q\", buf.String())\n\t}\n}\n"
  },
  {
    "path": "internal/marshaller.go",
    "content": "package openai\n\nimport (\n\t\"encoding/json\"\n)\n\ntype Marshaller interface {\n\tMarshal(value any) ([]byte, error)\n}\n\ntype JSONMarshaller struct{}\n\nfunc (jm *JSONMarshaller) Marshal(value any) ([]byte, error) {\n\treturn json.Marshal(value)\n}\n"
  },
  {
    "path": "internal/marshaller_test.go",
    "content": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestJSONMarshaller_Normal(t *testing.T) {\n\tjm := &openai.JSONMarshaller{}\n\tdata := map[string]string{\"key\": \"value\"}\n\n\tb, err := jm.Marshal(data)\n\tchecks.NoError(t, err)\n\tif len(b) == 0 {\n\t\tt.Fatal(\"should return non-empty bytes\")\n\t}\n}\n\nfunc TestJSONMarshaller_InvalidInput(t *testing.T) {\n\tjm := &openai.JSONMarshaller{}\n\t_, err := jm.Marshal(make(chan int))\n\tchecks.HasError(t, err, \"should return error for unsupported type\")\n}\n\nfunc TestJSONMarshaller_EmptyValue(t *testing.T) {\n\tjm := &openai.JSONMarshaller{}\n\tb, err := jm.Marshal(nil)\n\tchecks.NoError(t, err)\n\tif string(b) != \"null\" {\n\t\tt.Fatalf(\"unexpected marshaled value: %s\", string(b))\n\t}\n}\n"
  },
  {
    "path": "internal/request_builder.go",
    "content": "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.Context, method, url string, body any, header http.Header) (*http.Request, error)\n}\n\ntype HTTPRequestBuilder struct {\n\tmarshaller Marshaller\n}\n\nfunc NewRequestBuilder() *HTTPRequestBuilder {\n\treturn &HTTPRequestBuilder{\n\t\tmarshaller: &JSONMarshaller{},\n\t}\n}\n\nfunc (b *HTTPRequestBuilder) Build(\n\tctx context.Context,\n\tmethod string,\n\turl string,\n\tbody any,\n\theader http.Header,\n) (req *http.Request, err error) {\n\tvar bodyReader io.Reader\n\tif body != nil {\n\t\tif v, ok := body.(io.Reader); ok {\n\t\t\tbodyReader = v\n\t\t} else {\n\t\t\tvar reqBytes []byte\n\t\t\treqBytes, err = b.marshaller.Marshal(body)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbodyReader = bytes.NewBuffer(reqBytes)\n\t\t}\n\t}\n\treq, err = http.NewRequestWithContext(ctx, method, url, bodyReader)\n\tif err != nil {\n\t\treturn\n\t}\n\tif header != nil {\n\t\treq.Header = header\n\t}\n\treturn\n}\n"
  },
  {
    "path": "internal/request_builder_test.go",
    "content": "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\"reflect\"\n\t\"testing\"\n)\n\nvar errTestMarshallerFailed = errors.New(\"test marshaller failed\")\n\ntype failingMarshaller struct{}\n\nfunc (*failingMarshaller) Marshal(_ any) ([]byte, error) {\n\treturn []byte{}, errTestMarshallerFailed\n}\n\nfunc TestRequestBuilderReturnsMarshallerErrors(t *testing.T) {\n\tbuilder := HTTPRequestBuilder{\n\t\tmarshaller: &failingMarshaller{},\n\t}\n\n\t_, err := builder.Build(context.Background(), \"\", \"\", struct{}{}, nil)\n\tif !errors.Is(err, errTestMarshallerFailed) {\n\t\tt.Fatalf(\"Did not return error when marshaller failed: %v\", err)\n\t}\n}\n\nfunc TestRequestBuilderReturnsRequest(t *testing.T) {\n\tb := NewRequestBuilder()\n\tvar (\n\t\tctx         = context.Background()\n\t\tmethod      = http.MethodPost\n\t\turl         = \"/foo\"\n\t\trequest     = map[string]string{\"foo\": \"bar\"}\n\t\treqBytes, _ = b.marshaller.Marshal(request)\n\t\twant, _     = http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(reqBytes))\n\t)\n\tgot, _ := b.Build(ctx, method, url, request, nil)\n\tif !reflect.DeepEqual(got.Body, want.Body) ||\n\t\t!reflect.DeepEqual(got.URL, want.URL) ||\n\t\t!reflect.DeepEqual(got.Method, want.Method) {\n\t\tt.Errorf(\"Build() got = %v, want %v\", got, want)\n\t}\n}\n\nfunc TestRequestBuilderReturnsRequestWhenRequestOfArgsIsNil(t *testing.T) {\n\tvar (\n\t\tctx     = context.Background()\n\t\tmethod  = http.MethodGet\n\t\turl     = \"/foo\"\n\t\twant, _ = http.NewRequestWithContext(ctx, method, url, nil)\n\t)\n\tb := NewRequestBuilder()\n\tgot, _ := b.Build(ctx, method, url, nil, nil)\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"Build() got = %v, want %v\", got, want)\n\t}\n}\n\nfunc TestRequestBuilderWithReaderBodyAndHeader(t *testing.T) {\n\tb := NewRequestBuilder()\n\tctx := context.Background()\n\tmethod := http.MethodPost\n\turl := \"/reader\"\n\tbodyContent := \"hello\"\n\tbody := bytes.NewBufferString(bodyContent)\n\theader := http.Header{\"X-Test\": []string{\"val\"}}\n\n\treq, err := b.Build(ctx, method, url, body, header)\n\tif err != nil {\n\t\tt.Fatalf(\"Build returned error: %v\", err)\n\t}\n\n\tgotBody, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read body: %v\", err)\n\t}\n\tif string(gotBody) != bodyContent {\n\t\tt.Fatalf(\"expected body %q, got %q\", bodyContent, string(gotBody))\n\t}\n\tif req.Header.Get(\"X-Test\") != \"val\" {\n\t\tt.Fatalf(\"expected header set to val, got %q\", req.Header.Get(\"X-Test\"))\n\t}\n}\n\nfunc TestRequestBuilderInvalidURL(t *testing.T) {\n\tb := NewRequestBuilder()\n\t_, err := b.Build(context.Background(), http.MethodGet, \":\", nil, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid URL\")\n\t}\n}\n"
  },
  {
    "path": "internal/test/checks/checks.go",
    "content": "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\tif err != nil {\n\t\tt.Error(err, message)\n\t}\n}\n\nfunc NoErrorF(t *testing.T, err error, message ...string) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatal(err, message)\n\t}\n}\n\nfunc HasError(t *testing.T, err error, message ...string) {\n\tt.Helper()\n\tif err == nil {\n\t\tt.Error(err, message)\n\t}\n}\n\nfunc ErrorIs(t *testing.T, err, target error, msg ...string) {\n\tt.Helper()\n\tif !errors.Is(err, target) {\n\t\tt.Fatal(msg)\n\t}\n}\n\nfunc ErrorIsF(t *testing.T, err, target error, format string, msg ...string) {\n\tt.Helper()\n\tif !errors.Is(err, target) {\n\t\tt.Fatalf(format, msg)\n\t}\n}\n\nfunc ErrorIsNot(t *testing.T, err, target error, msg ...string) {\n\tt.Helper()\n\tif errors.Is(err, target) {\n\t\tt.Fatal(msg)\n\t}\n}\n\nfunc ErrorIsNotf(t *testing.T, err, target error, format string, msg ...string) {\n\tt.Helper()\n\tif errors.Is(err, target) {\n\t\tt.Fatalf(format, msg)\n\t}\n}\n"
  },
  {
    "path": "internal/test/checks/checks_test.go",
    "content": "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 TestChecksSuccessPaths(t *testing.T) {\n\tchecks.NoError(t, nil)\n\tchecks.NoErrorF(t, nil)\n\tchecks.HasError(t, errors.New(\"err\"))\n\ttarget := errors.New(\"x\")\n\tchecks.ErrorIs(t, target, target)\n\tchecks.ErrorIsF(t, target, target, \"msg\")\n\tchecks.ErrorIsNot(t, errors.New(\"y\"), target)\n\tchecks.ErrorIsNotf(t, errors.New(\"y\"), target, \"msg\")\n}\n"
  },
  {
    "path": "internal/test/failer.go",
    "content": "package test\n\nimport \"errors\"\n\nvar (\n\tErrTestErrorAccumulatorWriteFailed = errors.New(\"test error accumulator failed\")\n)\n\ntype FailingErrorBuffer struct{}\n\nfunc (b *FailingErrorBuffer) Write(_ []byte) (n int, err error) {\n\treturn 0, ErrTestErrorAccumulatorWriteFailed\n}\n\nfunc (b *FailingErrorBuffer) Len() int {\n\treturn 0\n}\n\nfunc (b *FailingErrorBuffer) Bytes() []byte {\n\treturn []byte{}\n}\n"
  },
  {
    "path": "internal/test/failer_test.go",
    "content": "//nolint:testpackage // need access to unexported fields and types for testing\npackage test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestFailingErrorBuffer(t *testing.T) {\n\tbuf := &FailingErrorBuffer{}\n\tn, err := buf.Write([]byte(\"test\"))\n\tif !errors.Is(err, ErrTestErrorAccumulatorWriteFailed) {\n\t\tt.Fatalf(\"expected %v, got %v\", ErrTestErrorAccumulatorWriteFailed, err)\n\t}\n\tif n != 0 {\n\t\tt.Fatalf(\"expected n=0, got %d\", n)\n\t}\n\tif buf.Len() != 0 {\n\t\tt.Fatalf(\"expected Len 0, got %d\", buf.Len())\n\t}\n\tif len(buf.Bytes()) != 0 {\n\t\tt.Fatalf(\"expected empty bytes\")\n\t}\n}\n"
  },
  {
    "path": "internal/test/helpers.go",
    "content": "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// CreateTestFile creates a fake file with \"hello\" as the content.\nfunc CreateTestFile(t *testing.T, path string) {\n\tfile, err := os.Create(path)\n\tchecks.NoError(t, err, \"failed to create file\")\n\n\tif _, err = file.WriteString(\"hello\"); err != nil {\n\t\tt.Fatalf(\"failed to write to file %v\", err)\n\t}\n\tfile.Close()\n}\n\n// TokenRoundTripper is a struct that implements the RoundTripper\n// interface, specifically to handle the authentication token by adding a token\n// to the request header. We need this because the API requires that each\n// request include a valid API token in the headers for authentication and\n// authorization.\ntype TokenRoundTripper struct {\n\tToken    string\n\tFallback http.RoundTripper\n}\n\n// RoundTrip takes an *http.Request as input and returns an\n// *http.Response and an error.\n//\n// It is expected to use the provided request to create a connection to an HTTP\n// server and return the response, or an error if one occurred. The returned\n// Response should have its Body closed. If the RoundTrip method returns an\n// error, the Client's Get, Head, Post, and PostForm methods return the same\n// error.\nfunc (t *TokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Set(\"Authorization\", \"Bearer \"+t.Token)\n\treturn t.Fallback.RoundTrip(req)\n}\n"
  },
  {
    "path": "internal/test/helpers_test.go",
    "content": "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 \"github.com/sashabaranov/go-openai/internal/test\"\n)\n\nfunc TestCreateTestFile(t *testing.T) {\n\tdir := t.TempDir()\n\tpath := filepath.Join(dir, \"file.txt\")\n\tinternaltest.CreateTestFile(t, path)\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read created file: %v\", err)\n\t}\n\tif string(data) != \"hello\" {\n\t\tt.Fatalf(\"unexpected file contents: %q\", string(data))\n\t}\n}\n\nfunc TestTokenRoundTripperAddsHeader(t *testing.T) {\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Authorization\") != \"Bearer \"+internaltest.GetTestToken() {\n\t\t\tt.Fatalf(\"authorization header not set\")\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer srv.Close()\n\n\tclient := srv.Client()\n\tclient.Transport = &internaltest.TokenRoundTripper{Token: internaltest.GetTestToken(), Fallback: client.Transport}\n\n\treq, err := http.NewRequest(http.MethodGet, srv.URL, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"request error: %v\", err)\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"client request error: %v\", err)\n\t}\n\tif _, err = io.Copy(io.Discard, resp.Body); err != nil {\n\t\tt.Fatalf(\"read body: %v\", err)\n\t}\n\tresp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tt.Fatalf(\"unexpected status: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "internal/test/server.go",
    "content": "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-secure-token-do-not-steal!!\"\n\nfunc GetTestToken() string {\n\treturn testAPI\n}\n\ntype ServerTest struct {\n\thandlers map[string]handler\n}\ntype handler func(w http.ResponseWriter, r *http.Request)\n\nfunc NewTestServer() *ServerTest {\n\treturn &ServerTest{handlers: make(map[string]handler)}\n}\n\n// HandlerCount returns the number of registered handlers.\nfunc (ts *ServerTest) HandlerCount() int {\n\treturn len(ts.handlers)\n}\n\n// HasHandler checks if a handler was registered for the given path.\nfunc (ts *ServerTest) HasHandler(path string) bool {\n\tpath = strings.ReplaceAll(path, \"*\", \".*\")\n\t_, ok := ts.handlers[path]\n\treturn ok\n}\n\nfunc (ts *ServerTest) RegisterHandler(path string, handler handler) {\n\t// to make the registered paths friendlier to a regex match in the route handler\n\t// in OpenAITestServer\n\tpath = strings.ReplaceAll(path, \"*\", \".*\")\n\tts.handlers[path] = handler\n}\n\n// OpenAITestServer Creates a mocked OpenAI server which can pretend to handle requests during testing.\nfunc (ts *ServerTest) OpenAITestServer() *httptest.Server {\n\treturn httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tlog.Printf(\"received a %s request at path %q\\n\", r.Method, r.URL.Path)\n\n\t\t// check auth\n\t\tif r.Header.Get(\"Authorization\") != \"Bearer \"+GetTestToken() && r.Header.Get(\"api-key\") != GetTestToken() {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\t// Handle /path/* routes.\n\t\t// Note: the * is converted to a .* in register handler for proper regex handling\n\t\tfor route, handler := range ts.handlers {\n\t\t\t// Adding ^ and $ to make path matching deterministic since go map iteration isn't ordered\n\t\t\tpattern, _ := regexp.Compile(\"^\" + route + \"$\")\n\t\t\tif pattern.MatchString(r.URL.Path) {\n\t\t\t\thandler(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\thttp.Error(w, \"the resource path doesn't exist\", http.StatusNotFound)\n\t}))\n}\n"
  },
  {
    "path": "internal/test/server_test.go",
    "content": "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\"\n)\n\nfunc TestGetTestToken(t *testing.T) {\n\tif internaltest.GetTestToken() != \"this-is-my-secure-token-do-not-steal!!\" {\n\t\tt.Fatalf(\"unexpected token\")\n\t}\n}\n\nfunc TestNewTestServer(t *testing.T) {\n\tts := internaltest.NewTestServer()\n\tif ts == nil {\n\t\tt.Fatalf(\"server not properly initialized\")\n\t}\n\tif ts.HandlerCount() != 0 {\n\t\tt.Fatalf(\"expected no handlers initially\")\n\t}\n}\n\nfunc TestRegisterHandlerTransformsPath(t *testing.T) {\n\tts := internaltest.NewTestServer()\n\th := func(_ http.ResponseWriter, _ *http.Request) {}\n\tts.RegisterHandler(\"/foo/*\", h)\n\tif !ts.HasHandler(\"/foo/*\") {\n\t\tt.Fatalf(\"handler not registered with transformed path\")\n\t}\n}\n\nfunc TestOpenAITestServer(t *testing.T) {\n\tts := internaltest.NewTestServer()\n\tts.RegisterHandler(\"/v1/test/*\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tif _, err := io.WriteString(w, \"ok\"); err != nil {\n\t\t\tt.Fatalf(\"write: %v\", err)\n\t\t}\n\t})\n\tsrv := ts.OpenAITestServer()\n\tsrv.Start()\n\tdefer srv.Close()\n\n\tbase := srv.Client().Transport\n\tclient := &http.Client{Transport: &internaltest.TokenRoundTripper{Token: internaltest.GetTestToken(), Fallback: base}}\n\tresp, err := client.Get(srv.URL + \"/v1/test/123\")\n\tif err != nil {\n\t\tt.Fatalf(\"request error: %v\", err)\n\t}\n\tbody, err := io.ReadAll(resp.Body)\n\tresp.Body.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"read response body: %v\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK || string(body) != \"ok\" {\n\t\tt.Fatalf(\"unexpected response: %d %q\", resp.StatusCode, string(body))\n\t}\n\n\t// unregistered path\n\tresp, err = client.Get(srv.URL + \"/unknown\")\n\tif err != nil {\n\t\tt.Fatalf(\"request error: %v\", err)\n\t}\n\tif resp.StatusCode != http.StatusNotFound {\n\t\tt.Fatalf(\"expected 404, got %d\", resp.StatusCode)\n\t}\n\n\t// missing token should return unauthorized\n\tclientNoToken := srv.Client()\n\tresp, err = clientNoToken.Get(srv.URL + \"/v1/test/123\")\n\tif err != nil {\n\t\tt.Fatalf(\"request error: %v\", err)\n\t}\n\tif resp.StatusCode != http.StatusUnauthorized {\n\t\tt.Fatalf(\"expected 401, got %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "internal/unmarshaler.go",
    "content": "package openai\n\nimport (\n\t\"encoding/json\"\n)\n\ntype Unmarshaler interface {\n\tUnmarshal(data []byte, v any) error\n}\n\ntype JSONUnmarshaler struct{}\n\nfunc (jm *JSONUnmarshaler) Unmarshal(data []byte, v any) error {\n\treturn json.Unmarshal(data, v)\n}\n"
  },
  {
    "path": "internal/unmarshaler_test.go",
    "content": "package openai_test\n\nimport (\n\t\"testing\"\n\n\topenai \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestJSONUnmarshaler_Normal(t *testing.T) {\n\tjm := &openai.JSONUnmarshaler{}\n\tdata := []byte(`{\"key\":\"value\"}`)\n\tvar v map[string]string\n\n\terr := jm.Unmarshal(data, &v)\n\tchecks.NoError(t, err)\n\tif v[\"key\"] != \"value\" {\n\t\tt.Fatal(\"unmarshal result mismatch\")\n\t}\n}\n\nfunc TestJSONUnmarshaler_InvalidJSON(t *testing.T) {\n\tjm := &openai.JSONUnmarshaler{}\n\tdata := []byte(`{invalid}`)\n\tvar v map[string]interface{}\n\n\terr := jm.Unmarshal(data, &v)\n\tchecks.HasError(t, err, \"should return error for invalid JSON\")\n}\n\nfunc TestJSONUnmarshaler_EmptyInput(t *testing.T) {\n\tjm := &openai.JSONUnmarshaler{}\n\tvar v interface{}\n\n\terr := jm.Unmarshal(nil, &v)\n\tchecks.HasError(t, err, \"should return error for nil input\")\n}\n"
  },
  {
    "path": "jsonschema/containsref_test.go",
    "content": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// SelfRef struct used to produce a self-referential schema.\ntype SelfRef struct {\n\tFriends []SelfRef `json:\"friends\"`\n}\n\n// Address struct referenced by Person without self-reference.\ntype Address struct {\n\tStreet string `json:\"street\"`\n}\n\ntype Person struct {\n\tAddress Address `json:\"address\"`\n}\n\n// TestGenerateSchemaForType_SelfRef ensures that self-referential types are not\n// flattened during schema generation.\nfunc TestGenerateSchemaForType_SelfRef(t *testing.T) {\n\tschema, err := jsonschema.GenerateSchemaForType(SelfRef{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := schema.Defs[\"SelfRef\"]; !ok {\n\t\tt.Fatal(\"expected defs to contain SelfRef for self reference\")\n\t}\n}\n\n// TestGenerateSchemaForType_NoSelfRef ensures that non-self-referential types\n// are flattened and do not reappear in $defs.\nfunc TestGenerateSchemaForType_NoSelfRef(t *testing.T) {\n\tschema, err := jsonschema.GenerateSchemaForType(Person{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, ok := schema.Defs[\"Person\"]; ok {\n\t\tt.Fatal(\"unexpected Person definition in defs\")\n\t}\n\tif _, ok := schema.Defs[\"Address\"]; !ok {\n\t\tt.Fatal(\"expected Address definition in defs\")\n\t}\n}\n"
  },
  {
    "path": "jsonschema/json.go",
    "content": "// Package jsonschema provides very simple functionality for representing a JSON schema as a\n// (nested) struct. This struct can be used with the chat completion \"function call\" feature.\n// For more complicated schemas, it is recommended to use a dedicated JSON schema library\n// and/or pass in the schema in []byte format.\npackage jsonschema\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype DataType string\n\nconst (\n\tObject  DataType = \"object\"\n\tNumber  DataType = \"number\"\n\tInteger DataType = \"integer\"\n\tString  DataType = \"string\"\n\tArray   DataType = \"array\"\n\tNull    DataType = \"null\"\n\tBoolean DataType = \"boolean\"\n)\n\n// Definition is a struct for describing a JSON Schema.\n// It is fairly limited, and you may have better luck using a third-party library.\ntype Definition struct {\n\t// Type specifies the data type of the schema.\n\tType DataType `json:\"type,omitempty\"`\n\t// Description is the description of the schema.\n\tDescription string `json:\"description,omitempty\"`\n\t// Enum is used to restrict a value to a fixed set of values. It must be an array with at least\n\t// one element, where each element is unique. You will probably only use this with strings.\n\tEnum []string `json:\"enum,omitempty\"`\n\t// Properties describes the properties of an object, if the schema type is Object.\n\tProperties map[string]Definition `json:\"properties,omitempty\"`\n\t// Required specifies which properties are required, if the schema type is Object.\n\tRequired []string `json:\"required,omitempty\"`\n\t// Items specifies which data type an array contains, if the schema type is Array.\n\tItems *Definition `json:\"items,omitempty\"`\n\t// AdditionalProperties is used to control the handling of properties in an object\n\t// that are not explicitly defined in the properties section of the schema. example:\n\t// additionalProperties: true\n\t// additionalProperties: false\n\t// additionalProperties: jsonschema.Definition{Type: jsonschema.String}\n\tAdditionalProperties any `json:\"additionalProperties,omitempty\"`\n\t// Whether the schema is nullable or not.\n\tNullable bool `json:\"nullable,omitempty\"`\n\n\t// Ref Reference to a definition in $defs or external schema.\n\tRef string `json:\"$ref,omitempty\"`\n\t// Defs A map of reusable schema definitions.\n\tDefs map[string]Definition `json:\"$defs,omitempty\"`\n}\n\nfunc (d *Definition) MarshalJSON() ([]byte, error) {\n\tif d.Properties == nil {\n\t\td.Properties = make(map[string]Definition)\n\t}\n\ttype Alias Definition\n\treturn json.Marshal(struct {\n\t\tAlias\n\t}{\n\t\tAlias: (Alias)(*d),\n\t})\n}\n\nfunc (d *Definition) Unmarshal(content string, v any) error {\n\treturn VerifySchemaAndUnmarshal(*d, []byte(content), v)\n}\n\nfunc GenerateSchemaForType(v any) (*Definition, error) {\n\tvar defs = make(map[string]Definition)\n\tdef, err := reflectSchema(reflect.TypeOf(v), defs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// If the schema has a root $ref, resolve it by:\n\t// 1. Extracting the key from the $ref.\n\t// 2. Detaching the referenced definition from $defs.\n\t// 3. Checking for self-references in the detached definition.\n\t//    - If a self-reference is found, restore the original $defs structure.\n\t// 4. Flattening the referenced definition into the root schema.\n\t// 5. Clearing the $ref field in the root schema.\n\tif def.Ref != \"\" {\n\t\torigRef := def.Ref\n\t\tkey := strings.TrimPrefix(origRef, \"#/$defs/\")\n\t\tif root, ok := defs[key]; ok {\n\t\t\tdelete(defs, key)\n\t\t\troot.Defs = defs\n\t\t\tif containsRef(root, origRef) {\n\t\t\t\troot.Defs = nil\n\t\t\t\tdefs[key] = root\n\t\t\t}\n\t\t\t*def = root\n\t\t}\n\t\tdef.Ref = \"\"\n\t}\n\tdef.Defs = defs\n\treturn def, nil\n}\n\nfunc reflectSchema(t reflect.Type, defs map[string]Definition) (*Definition, error) {\n\tvar d Definition\n\tswitch t.Kind() {\n\tcase reflect.String:\n\t\td.Type = String\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,\n\t\treflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\td.Type = Integer\n\tcase reflect.Float32, reflect.Float64:\n\t\td.Type = Number\n\tcase reflect.Bool:\n\t\td.Type = Boolean\n\tcase reflect.Slice, reflect.Array:\n\t\td.Type = Array\n\t\titems, err := reflectSchema(t.Elem(), defs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\td.Items = items\n\tcase reflect.Struct:\n\t\tif t.Name() != \"\" {\n\t\t\tif _, ok := defs[t.Name()]; !ok {\n\t\t\t\tdefs[t.Name()] = Definition{}\n\t\t\t\tobject, err := reflectSchemaObject(t, defs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tdefs[t.Name()] = *object\n\t\t\t}\n\t\t\treturn &Definition{Ref: \"#/$defs/\" + t.Name()}, nil\n\t\t}\n\t\td.Type = Object\n\t\td.AdditionalProperties = false\n\t\tobject, err := reflectSchemaObject(t, defs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\td = *object\n\tcase reflect.Ptr:\n\t\tdefinition, err := reflectSchema(t.Elem(), defs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\td = *definition\n\tcase reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128,\n\t\treflect.Chan, reflect.Func, reflect.Interface, reflect.Map,\n\t\treflect.UnsafePointer:\n\t\treturn nil, fmt.Errorf(\"unsupported type: %s\", t.Kind().String())\n\tdefault:\n\t}\n\treturn &d, nil\n}\n\nfunc reflectSchemaObject(t reflect.Type, defs map[string]Definition) (*Definition, error) {\n\tvar d = Definition{\n\t\tType:                 Object,\n\t\tAdditionalProperties: false,\n\t}\n\tproperties := make(map[string]Definition)\n\tvar requiredFields []string\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tfield := t.Field(i)\n\t\tif !field.IsExported() {\n\t\t\tcontinue\n\t\t}\n\t\tjsonTag := field.Tag.Get(\"json\")\n\t\tvar required = true\n\t\tswitch {\n\t\tcase jsonTag == \"-\":\n\t\t\tcontinue\n\t\tcase jsonTag == \"\":\n\t\t\tjsonTag = field.Name\n\t\tcase strings.HasSuffix(jsonTag, \",omitempty\"):\n\t\t\tjsonTag = strings.TrimSuffix(jsonTag, \",omitempty\")\n\t\t\trequired = false\n\t\t}\n\n\t\titem, err := reflectSchema(field.Type, defs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdescription := field.Tag.Get(\"description\")\n\t\tif description != \"\" {\n\t\t\titem.Description = description\n\t\t}\n\t\tenum := field.Tag.Get(\"enum\")\n\t\tif enum != \"\" {\n\t\t\titem.Enum = strings.Split(enum, \",\")\n\t\t}\n\n\t\tif n := field.Tag.Get(\"nullable\"); n != \"\" {\n\t\t\tnullable, _ := strconv.ParseBool(n)\n\t\t\titem.Nullable = nullable\n\t\t}\n\n\t\tproperties[jsonTag] = *item\n\n\t\tif s := field.Tag.Get(\"required\"); s != \"\" {\n\t\t\trequired, _ = strconv.ParseBool(s)\n\t\t}\n\t\tif required {\n\t\t\trequiredFields = append(requiredFields, jsonTag)\n\t\t}\n\t}\n\td.Required = requiredFields\n\td.Properties = properties\n\treturn &d, nil\n}\n\nfunc containsRef(def Definition, targetRef string) bool {\n\tif def.Ref == targetRef {\n\t\treturn true\n\t}\n\n\tfor _, d := range def.Defs {\n\t\tif containsRef(d, targetRef) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tfor _, prop := range def.Properties {\n\t\tif containsRef(prop, targetRef) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif def.Items != nil && containsRef(*def.Items, targetRef) {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "jsonschema/json_additional_test.go",
    "content": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// Test Definition.Unmarshal, including success path, validation error,\n// JSON syntax error and type mismatch during unmarshalling.\nfunc TestDefinitionUnmarshal(t *testing.T) {\n\tschema := jsonschema.Definition{\n\t\tType: jsonschema.Object,\n\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\"name\": {Type: jsonschema.String},\n\t\t},\n\t}\n\n\tvar dst struct {\n\t\tName string `json:\"name\"`\n\t}\n\tif err := schema.Unmarshal(`{\"name\":\"foo\"}`, &dst); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif dst.Name != \"foo\" {\n\t\tt.Errorf(\"expected name to be foo, got %q\", dst.Name)\n\t}\n\n\tif err := schema.Unmarshal(`{`, &dst); err == nil {\n\t\tt.Error(\"expected error for malformed json\")\n\t}\n\n\tif err := schema.Unmarshal(`{\"name\":1}`, &dst); err == nil {\n\t\tt.Error(\"expected validation error\")\n\t}\n\n\tnumSchema := jsonschema.Definition{Type: jsonschema.Number}\n\tvar s string\n\tif err := numSchema.Unmarshal(`123`, &s); err == nil {\n\t\tt.Error(\"expected unmarshal type error\")\n\t}\n}\n\n// Ensure GenerateSchemaForType returns an error when encountering unsupported types.\nfunc TestGenerateSchemaForTypeUnsupported(t *testing.T) {\n\ttype Bad struct {\n\t\tCh chan int `json:\"ch\"`\n\t}\n\tif _, err := jsonschema.GenerateSchemaForType(Bad{}); err == nil {\n\t\tt.Fatal(\"expected error for unsupported type\")\n\t}\n}\n\n// Validate should fail when provided data does not match the expected container types.\nfunc TestValidateInvalidContainers(t *testing.T) {\n\tobjSchema := jsonschema.Definition{Type: jsonschema.Object}\n\tif jsonschema.Validate(objSchema, 1) {\n\t\tt.Error(\"expected object validation to fail for non-map input\")\n\t}\n\n\tarrSchema := jsonschema.Definition{Type: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.String}}\n\tif jsonschema.Validate(arrSchema, 1) {\n\t\tt.Error(\"expected array validation to fail for non-slice input\")\n\t}\n}\n\n// Validate should return false when $ref cannot be resolved.\nfunc TestValidateRefNotFound(t *testing.T) {\n\trefSchema := jsonschema.Definition{Ref: \"#/$defs/Missing\"}\n\tif jsonschema.Validate(refSchema, \"data\", jsonschema.WithDefs(map[string]jsonschema.Definition{})) {\n\t\tt.Error(\"expected validation to fail when reference is missing\")\n\t}\n}\n"
  },
  {
    "path": "jsonschema/json_errors_test.go",
    "content": "package jsonschema_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\n// TestGenerateSchemaForType_ErrorPaths verifies error handling for unsupported types.\nfunc TestGenerateSchemaForType_ErrorPaths(t *testing.T) {\n\ttype anon struct{ Ch chan int }\n\ttests := []struct {\n\t\tname string\n\t\tv    any\n\t}{\n\t\t{\"slice\", []chan int{}},\n\t\t{\"anon struct\", anon{}},\n\t\t{\"pointer\", (*chan int)(nil)},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif _, err := jsonschema.GenerateSchemaForType(tt.v); err == nil {\n\t\t\t\tt.Errorf(\"expected error for %s\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "jsonschema/json_test.go",
    "content": "package jsonschema_test\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nfunc TestDefinition_MarshalJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tdef  jsonschema.Definition\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Test with empty Definition\",\n\t\t\tdef:  jsonschema.Definition{},\n\t\t\twant: `{}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with Definition properties set\",\n\t\t\tdef: jsonschema.Definition{\n\t\t\t\tType:        jsonschema.String,\n\t\t\t\tDescription: \"A string type\",\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"name\": {\n\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"string\",\n\t\t\t\t\"description\":\"A string type\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with nested Definition properties\",\n\t\t\tdef: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"user\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"age\": {\n\t\t\t\t\t\t\t\tType: jsonschema.Integer,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\"user\":{\n\t\t\t\t\t\t\t\"type\":\"object\",\n\t\t\t\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"age\":{\n\t\t\t\t\t\t\t\t\t\t\"type\":\"integer\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with complex nested Definition\",\n\t\t\tdef: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"user\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"name\": {\n\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"age\": {\n\t\t\t\t\t\t\t\tType: jsonschema.Integer,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"address\": {\n\t\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\t\"city\": {\n\t\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"country\": {\n\t\t\t\t\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\"user\":{\n\t\t\t\t\t\t\t\"type\":\"object\",\n\t\t\t\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"age\":{\n\t\t\t\t\t\t\t\t\t\t\"type\":\"integer\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"address\":{\n\t\t\t\t\t\t\t\t\t\t\"type\":\"object\",\n\t\t\t\t\t\t\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\t\t\t\t\t\t\"city\":{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\"country\":{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with Array type Definition\",\n\t\t\tdef: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Array,\n\t\t\t\tItems: &jsonschema.Definition{\n\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t},\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"name\": {\n\t\t\t\t\t\tType: jsonschema.String,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"array\",\n\t\t\t\t\"items\":{\n\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t},\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\twantBytes := []byte(tt.want)\n\t\t\tvar want map[string]interface{}\n\t\t\terr := json.Unmarshal(wantBytes, &want)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Failed to Unmarshal JSON: error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgot := structToMap(t, tt.def)\n\t\t\tgotPtr := structToMap(t, &tt.def)\n\n\t\t\tif !reflect.DeepEqual(got, want) {\n\t\t\t\tt.Errorf(\"MarshalJSON() got = %v, want %v\", got, want)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotPtr, want) {\n\t\t\t\tt.Errorf(\"MarshalJSON() gotPtr = %v, want %v\", gotPtr, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype User struct {\n\tID     int      `json:\"id,omitempty\"`\n\tName   string   `json:\"name,omitempty\"`\n\tOrders []*Order `json:\"orders,omitempty\"`\n}\n\ntype Order struct {\n\tID     int     `json:\"id,omitempty\"`\n\tAmount float64 `json:\"amount,omitempty\"`\n\tBuyer  *User   `json:\"buyer,omitempty\"`\n}\n\nfunc TestStructToSchema(t *testing.T) {\n\ttype Tweet struct {\n\t\tText string `json:\"text\"`\n\t}\n\n\ttype Person struct {\n\t\tName    string   `json:\"name,omitempty\"`\n\t\tAge     int      `json:\"age,omitempty\"`\n\t\tFriends []Person `json:\"friends,omitempty\"`\n\t\tTweets  []Tweet  `json:\"tweets,omitempty\"`\n\t}\n\n\ttype MyStructuredResponse struct {\n\t\tPascalCase string `json:\"pascal_case\" required:\"true\" description:\"PascalCase\"`\n\t\tCamelCase  string `json:\"camel_case\" required:\"true\" description:\"CamelCase\"`\n\t\tKebabCase  string `json:\"kebab_case\" required:\"true\" description:\"KebabCase\"`\n\t\tSnakeCase  string `json:\"snake_case\" required:\"true\" description:\"SnakeCase\"`\n\t}\n\n\ttests := []struct {\n\t\tname string\n\t\tin   any\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"Test with empty struct\",\n\t\t\tin:   struct{}{},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with struct containing many fields\",\n\t\t\tin: struct {\n\t\t\t\tName   string  `json:\"name\"`\n\t\t\t\tAge    int     `json:\"age\"`\n\t\t\t\tActive bool    `json:\"active\"`\n\t\t\t\tHeight float64 `json:\"height\"`\n\t\t\t\tCities []struct {\n\t\t\t\t\tName  string `json:\"name\"`\n\t\t\t\t\tState string `json:\"state\"`\n\t\t\t\t} `json:\"cities\"`\n\t\t\t}{\n\t\t\t\tName: \"John Doe\",\n\t\t\t\tAge:  30,\n\t\t\t\tCities: []struct {\n\t\t\t\t\tName  string `json:\"name\"`\n\t\t\t\t\tState string `json:\"state\"`\n\t\t\t\t}{\n\t\t\t\t\t{Name: \"New York\", State: \"NY\"},\n\t\t\t\t\t{Name: \"Los Angeles\", State: \"CA\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t},\n\t\t\t\t\t\"age\":{\n\t\t\t\t\t\t\"type\":\"integer\"\n\t\t\t\t\t},\n\t\t\t\t\t\"active\":{\n\t\t\t\t\t\t\"type\":\"boolean\"\n\t\t\t\t\t},\n\t\t\t\t\t\"height\":{\n\t\t\t\t\t\t\"type\":\"number\"\n\t\t\t\t\t},\n\t\t\t\t\t\"cities\":{\n\t\t\t\t\t\t\"type\":\"array\",\n\t\t\t\t\t\t\"items\":{\n\t\t\t\t\t\t\t\"additionalProperties\":false,\n\t\t\t\t\t\t\t\"type\":\"object\",\n\t\t\t\t\t\t\t\"properties\":{\n\t\t\t\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"state\":{\n\t\t\t\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"required\":[\"name\",\"state\"]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"required\":[\"name\",\"age\",\"active\",\"height\",\"cities\"],\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with description tag\",\n\t\t\tin: struct {\n\t\t\t\tName string `json:\"name\" description:\"The name of the person\"`\n\t\t\t}{\n\t\t\t\tName: \"John Doe\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\"type\":\"string\",\n\t\t\t\t\t\t\"description\":\"The name of the person\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"required\":[\"name\"],\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with required tag\",\n\t\t\tin: struct {\n\t\t\t\tName string `json:\"name\" required:\"false\"`\n\t\t\t}{\n\t\t\t\tName: \"John Doe\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with enum tag\",\n\t\t\tin: struct {\n\t\t\t\tColor string `json:\"color\" enum:\"red,green,blue\"`\n\t\t\t}{\n\t\t\t\tColor: \"red\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"color\":{\n\t\t\t\t\t\t\"type\":\"string\",\n\t\t\t\t\t\t\"enum\":[\"red\",\"green\",\"blue\"]\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"required\":[\"color\"],\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with nullable tag\",\n\t\t\tin: struct {\n\t\t\t\tName *string `json:\"name\" nullable:\"true\"`\n\t\t\t}{\n\t\t\t\tName: nil,\n\t\t\t},\n\t\t\twant: `{\n\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\"type\":\"string\",\n\t\t\t\t\t\t\"nullable\":true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"required\":[\"name\"],\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with exclude mark\",\n\t\t\tin: struct {\n\t\t\t\tName string `json:\"-\"`\n\t\t\t}{\n\t\t\t\tName: \"Name\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with no json tag\",\n\t\t\tin: struct {\n\t\t\t\tName string\n\t\t\t}{\n\t\t\t\tName: \"\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"Name\":{\n\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"required\":[\"Name\"],\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with omitempty tag\",\n\t\t\tin: struct {\n\t\t\t\tName string `json:\"name,omitempty\"`\n\t\t\t}{\n\t\t\t\tName: \"\",\n\t\t\t},\n\t\t\twant: `{\n\t\t\t\t\"type\":\"object\",\n\t\t\t\t\"properties\":{\n\t\t\t\t\t\"name\":{\n\t\t\t\t\t\t\"type\":\"string\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"additionalProperties\":false\n\t\t\t}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test with $ref and $defs\",\n\t\t\tin: struct {\n\t\t\t\tPerson Person  `json:\"person\"`\n\t\t\t\tTweets []Tweet `json:\"tweets\"`\n\t\t\t}{},\n\t\t\twant: `{\n  \"type\" : \"object\",\n  \"properties\" : {\n    \"person\" : {\n      \"$ref\" : \"#/$defs/Person\"\n    },\n    \"tweets\" : {\n      \"type\" : \"array\",\n      \"items\" : {\n        \"$ref\" : \"#/$defs/Tweet\"\n      }\n    }\n  },\n  \"required\" : [ \"person\", \"tweets\" ],\n  \"additionalProperties\" : false,\n  \"$defs\" : {\n    \"Person\" : {\n      \"type\" : \"object\",\n      \"properties\" : {\n        \"age\" : {\n          \"type\" : \"integer\"\n        },\n        \"friends\" : {\n          \"type\" : \"array\",\n          \"items\" : {\n            \"$ref\" : \"#/$defs/Person\"\n          }\n        },\n        \"name\" : {\n          \"type\" : \"string\"\n        },\n        \"tweets\" : {\n          \"type\" : \"array\",\n          \"items\" : {\n            \"$ref\" : \"#/$defs/Tweet\"\n          }\n        }\n      },\n      \"additionalProperties\" : false\n    },\n    \"Tweet\" : {\n      \"type\" : \"object\",\n      \"properties\" : {\n        \"text\" : {\n          \"type\" : \"string\"\n        }\n      },\n      \"required\" : [ \"text\" ],\n      \"additionalProperties\" : false\n    }\n  }\n}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test Person\",\n\t\t\tin:   Person{},\n\t\t\twant: `{\n  \"type\": \"object\",\n  \"properties\": {\n    \"age\": {\n      \"type\": \"integer\"\n    },\n    \"friends\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/Person\"\n      }\n    },\n    \"name\": {\n      \"type\": \"string\"\n    },\n    \"tweets\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/Tweet\"\n      }\n    }\n  },\n  \"additionalProperties\": false,\n  \"$defs\": {\n    \"Person\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"age\": {\n          \"type\": \"integer\"\n        },\n        \"friends\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/Person\"\n          }\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"tweets\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/Tweet\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Tweet\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"text\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\n        \"text\"\n      ],\n      \"additionalProperties\": false\n    }\n  }\n}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test MyStructuredResponse\",\n\t\t\tin:   MyStructuredResponse{},\n\t\t\twant: `{\n  \"type\": \"object\",\n  \"properties\": {\n    \"camel_case\": {\n      \"type\": \"string\",\n      \"description\": \"CamelCase\"\n    },\n    \"kebab_case\": {\n      \"type\": \"string\",\n      \"description\": \"KebabCase\"\n    },\n    \"pascal_case\": {\n      \"type\": \"string\",\n      \"description\": \"PascalCase\"\n    },\n    \"snake_case\": {\n      \"type\": \"string\",\n      \"description\": \"SnakeCase\"\n    }\n  },\n  \"required\": [\n    \"pascal_case\",\n    \"camel_case\",\n    \"kebab_case\",\n    \"snake_case\"\n  ],\n  \"additionalProperties\": false\n}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Test User\",\n\t\t\tin:   User{},\n\t\t\twant: `{\n  \"type\": \"object\",\n  \"properties\": {\n    \"id\": {\n      \"type\": \"integer\"\n    },\n    \"name\": {\n      \"type\": \"string\"\n    },\n    \"orders\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/Order\"\n      }\n    }\n  },\n  \"additionalProperties\": false,\n  \"$defs\": {\n    \"Order\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"amount\": {\n          \"type\": \"number\"\n        },\n        \"buyer\": {\n          \"$ref\": \"#/$defs/User\"\n        },\n        \"id\": {\n          \"type\": \"integer\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"User\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"orders\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/Order\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    }\n  }\n}`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\twantBytes := []byte(tt.want)\n\n\t\t\tschema, err := jsonschema.GenerateSchemaForType(tt.in)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Failed to generate schema: error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvar want map[string]interface{}\n\t\t\terr = json.Unmarshal(wantBytes, &want)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Failed to Unmarshal JSON: error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgot := structToMap(t, schema)\n\t\t\tgotPtr := structToMap(t, &schema)\n\n\t\t\tif !reflect.DeepEqual(got, want) {\n\t\t\t\tt.Errorf(\"MarshalJSON() got = %v, want %v\", got, want)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotPtr, want) {\n\t\t\t\tt.Errorf(\"MarshalJSON() gotPtr = %v, want %v\", gotPtr, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc structToMap(t *testing.T, v any) map[string]any {\n\tt.Helper()\n\tgotBytes, err := json.Marshal(v)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to Marshal JSON: error = %v\", err)\n\t\treturn nil\n\t}\n\n\tvar got map[string]interface{}\n\terr = json.Unmarshal(gotBytes, &got)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to Unmarshal JSON: error =  %v\", err)\n\t\treturn nil\n\t}\n\treturn got\n}\n"
  },
  {
    "path": "jsonschema/validate.go",
    "content": "package jsonschema\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\nfunc CollectDefs(def Definition) map[string]Definition {\n\tresult := make(map[string]Definition)\n\tcollectDefsRecursive(def, result, \"#\")\n\treturn result\n}\n\nfunc collectDefsRecursive(def Definition, result map[string]Definition, prefix string) {\n\tfor k, v := range def.Defs {\n\t\tpath := prefix + \"/$defs/\" + k\n\t\tresult[path] = v\n\t\tcollectDefsRecursive(v, result, path)\n\t}\n\tfor k, sub := range def.Properties {\n\t\tcollectDefsRecursive(sub, result, prefix+\"/properties/\"+k)\n\t}\n\tif def.Items != nil {\n\t\tcollectDefsRecursive(*def.Items, result, prefix)\n\t}\n}\n\nfunc VerifySchemaAndUnmarshal(schema Definition, content []byte, v any) error {\n\tvar data any\n\terr := json.Unmarshal(content, &data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !Validate(schema, data, WithDefs(CollectDefs(schema))) {\n\t\treturn errors.New(\"data validation failed against the provided schema\")\n\t}\n\treturn json.Unmarshal(content, &v)\n}\n\ntype validateArgs struct {\n\tDefs map[string]Definition\n}\n\ntype ValidateOption func(*validateArgs)\n\nfunc WithDefs(defs map[string]Definition) ValidateOption {\n\treturn func(option *validateArgs) {\n\t\toption.Defs = defs\n\t}\n}\n\nfunc Validate(schema Definition, data any, opts ...ValidateOption) bool {\n\targs := validateArgs{}\n\tfor _, opt := range opts {\n\t\topt(&args)\n\t}\n\tif len(opts) == 0 {\n\t\targs.Defs = CollectDefs(schema)\n\t}\n\tswitch schema.Type {\n\tcase Object:\n\t\treturn validateObject(schema, data, args.Defs)\n\tcase Array:\n\t\treturn validateArray(schema, data, args.Defs)\n\tcase String:\n\t\tv, ok := data.(string)\n\t\tif ok && len(schema.Enum) > 0 {\n\t\t\treturn contains(schema.Enum, v)\n\t\t}\n\t\treturn ok\n\tcase Number: // float64 and int\n\t\t_, ok := data.(float64)\n\t\tif !ok {\n\t\t\t_, ok = data.(int)\n\t\t}\n\t\treturn ok\n\tcase Boolean:\n\t\t_, ok := data.(bool)\n\t\treturn ok\n\tcase Integer:\n\t\t// Golang unmarshals all numbers as float64, so we need to check if the float64 is an integer\n\t\tif num, ok := data.(float64); ok {\n\t\t\treturn num == float64(int64(num))\n\t\t}\n\t\t_, ok := data.(int)\n\t\treturn ok\n\tcase Null:\n\t\treturn data == nil\n\tdefault:\n\t\tif schema.Ref != \"\" && args.Defs != nil {\n\t\t\tif v, ok := args.Defs[schema.Ref]; ok {\n\t\t\t\treturn Validate(v, data, WithDefs(args.Defs))\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n}\n\nfunc validateObject(schema Definition, data any, defs map[string]Definition) bool {\n\tdataMap, ok := data.(map[string]any)\n\tif !ok {\n\t\treturn false\n\t}\n\tfor _, field := range schema.Required {\n\t\tif _, exists := dataMap[field]; !exists {\n\t\t\treturn false\n\t\t}\n\t}\n\tfor key, valueSchema := range schema.Properties {\n\t\tvalue, exists := dataMap[key]\n\t\tif exists && !Validate(valueSchema, value, WithDefs(defs)) {\n\t\t\treturn false\n\t\t} else if !exists && contains(schema.Required, key) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc validateArray(schema Definition, data any, defs map[string]Definition) bool {\n\tdataArray, ok := data.([]any)\n\tif !ok {\n\t\treturn false\n\t}\n\tfor _, item := range dataArray {\n\t\tif !Validate(*schema.Items, item, WithDefs(defs)) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc contains[S ~[]E, E comparable](s S, v E) bool {\n\tfor i := range s {\n\t\tif v == s[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "jsonschema/validate_test.go",
    "content": "package jsonschema_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sashabaranov/go-openai/jsonschema\"\n)\n\nfunc Test_Validate(t *testing.T) {\n\ttype args struct {\n\t\tdata   any\n\t\tschema jsonschema.Definition\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t// string integer number boolean\n\t\t{\"\", args{data: \"ABC\", schema: jsonschema.Definition{Type: jsonschema.String}}, true},\n\t\t{\"\", args{data: 123, schema: jsonschema.Definition{Type: jsonschema.String}}, false},\n\t\t{\"\", args{data: 123, schema: jsonschema.Definition{Type: jsonschema.Integer}}, true},\n\t\t{\"\", args{data: 123.4, schema: jsonschema.Definition{Type: jsonschema.Integer}}, false},\n\t\t{\"\", args{data: \"ABC\", schema: jsonschema.Definition{Type: jsonschema.Number}}, false},\n\t\t{\"\", args{data: 123, schema: jsonschema.Definition{Type: jsonschema.Number}}, true},\n\t\t{\"\", args{data: false, schema: jsonschema.Definition{Type: jsonschema.Boolean}}, true},\n\t\t{\"\", args{data: 123, schema: jsonschema.Definition{Type: jsonschema.Boolean}}, false},\n\t\t{\"\", args{data: nil, schema: jsonschema.Definition{Type: jsonschema.Null}}, true},\n\t\t{\"\", args{data: 0, schema: jsonschema.Definition{Type: jsonschema.Null}}, false},\n\t\t// array\n\t\t{\"\", args{data: []any{\"a\", \"b\", \"c\"}, schema: jsonschema.Definition{\n\t\t\tType: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.String}},\n\t\t}, true},\n\t\t{\"\", args{data: []any{1, 2, 3}, schema: jsonschema.Definition{\n\t\t\tType: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.String}},\n\t\t}, false},\n\t\t{\"\", args{data: []any{1, 2, 3}, schema: jsonschema.Definition{\n\t\t\tType: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.Integer}},\n\t\t}, true},\n\t\t{\"\", args{data: []any{1, 2, 3.4}, schema: jsonschema.Definition{\n\t\t\tType: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.Integer}},\n\t\t}, false},\n\t\t// object\n\t\t{\"\", args{data: map[string]any{\n\t\t\t\"string\":  \"abc\",\n\t\t\t\"integer\": 123,\n\t\t\t\"number\":  123.4,\n\t\t\t\"boolean\": false,\n\t\t\t\"array\":   []any{1, 2, 3},\n\t\t}, schema: jsonschema.Definition{Type: jsonschema.Object, Properties: map[string]jsonschema.Definition{\n\t\t\t\"string\":  {Type: jsonschema.String},\n\t\t\t\"integer\": {Type: jsonschema.Integer},\n\t\t\t\"number\":  {Type: jsonschema.Number},\n\t\t\t\"boolean\": {Type: jsonschema.Boolean},\n\t\t\t\"array\":   {Type: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.Number}},\n\t\t},\n\t\t\tRequired: []string{\"string\"},\n\t\t}}, true},\n\t\t{\"\", args{data: map[string]any{\n\t\t\t\"integer\": 123,\n\t\t\t\"number\":  123.4,\n\t\t\t\"boolean\": false,\n\t\t\t\"array\":   []any{1, 2, 3},\n\t\t}, schema: jsonschema.Definition{Type: jsonschema.Object, Properties: map[string]jsonschema.Definition{\n\t\t\t\"string\":  {Type: jsonschema.String},\n\t\t\t\"integer\": {Type: jsonschema.Integer},\n\t\t\t\"number\":  {Type: jsonschema.Number},\n\t\t\t\"boolean\": {Type: jsonschema.Boolean},\n\t\t\t\"array\":   {Type: jsonschema.Array, Items: &jsonschema.Definition{Type: jsonschema.Number}},\n\t\t},\n\t\t\tRequired: []string{\"string\"},\n\t\t}}, false},\n\t\t{\n\t\t\t\"test schema with ref and defs\", args{data: map[string]any{\n\t\t\t\t\"person\": map[string]any{\n\t\t\t\t\t\"name\":   \"John\",\n\t\t\t\t\t\"gender\": \"male\",\n\t\t\t\t\t\"age\":    28,\n\t\t\t\t\t\"profile\": map[string]any{\n\t\t\t\t\t\t\"full_name\": \"John Doe\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, schema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t},\n\t\t\t\tRequired: []string{\"person\"},\n\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\"Person\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"name\":    {Type: jsonschema.String},\n\t\t\t\t\t\t\t\"gender\":  {Type: jsonschema.String, Enum: []string{\"male\", \"female\", \"unknown\"}},\n\t\t\t\t\t\t\t\"age\":     {Type: jsonschema.Integer},\n\t\t\t\t\t\t\t\"profile\": {Ref: \"#/$defs/Person/$defs/Profile\"},\n\t\t\t\t\t\t\t\"tweets\":  {Type: jsonschema.Array, Items: &jsonschema.Definition{Ref: \"#/$defs/Tweet\"}},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRequired: []string{\"name\", \"gender\", \"age\", \"profile\"},\n\t\t\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"Profile\": {\n\t\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\t\"full_name\": {Type: jsonschema.String},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Tweet\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"text\":   {Type: jsonschema.String},\n\t\t\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}}, true},\n\t\t{\n\t\t\t\"test enum invalid value\", args{data: map[string]any{\n\t\t\t\t\"person\": map[string]any{\n\t\t\t\t\t\"name\":   \"John\",\n\t\t\t\t\t\"gender\": \"other\",\n\t\t\t\t\t\"age\":    28,\n\t\t\t\t\t\"profile\": map[string]any{\n\t\t\t\t\t\t\"full_name\": \"John Doe\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, schema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t},\n\t\t\t\tRequired: []string{\"person\"},\n\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\"Person\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"name\":    {Type: jsonschema.String},\n\t\t\t\t\t\t\t\"gender\":  {Type: jsonschema.String, Enum: []string{\"male\", \"female\", \"unknown\"}},\n\t\t\t\t\t\t\t\"age\":     {Type: jsonschema.Integer},\n\t\t\t\t\t\t\t\"profile\": {Ref: \"#/$defs/Person/$defs/Profile\"},\n\t\t\t\t\t\t\t\"tweets\":  {Type: jsonschema.Array, Items: &jsonschema.Definition{Ref: \"#/$defs/Tweet\"}},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRequired: []string{\"name\", \"gender\", \"age\", \"profile\"},\n\t\t\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"Profile\": {\n\t\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\t\"full_name\": {Type: jsonschema.String},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Tweet\": {\n\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\"text\":   {Type: jsonschema.String},\n\t\t\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := jsonschema.Validate(tt.args.schema, tt.args.data); got != tt.want {\n\t\t\t\tt.Errorf(\"Validate() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUnmarshal(t *testing.T) {\n\ttype args struct {\n\t\tschema  jsonschema.Definition\n\t\tcontent []byte\n\t\tv       any\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\"\", args{\n\t\t\tschema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"string\": {Type: jsonschema.String},\n\t\t\t\t\t\"number\": {Type: jsonschema.Number},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontent: []byte(`{\"string\":\"abc\",\"number\":123.4}`),\n\t\t\tv: &struct {\n\t\t\t\tString string  `json:\"string\"`\n\t\t\t\tNumber float64 `json:\"number\"`\n\t\t\t}{},\n\t\t}, false},\n\t\t{\"\", args{\n\t\t\tschema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"string\": {Type: jsonschema.String},\n\t\t\t\t\t\"number\": {Type: jsonschema.Number},\n\t\t\t\t},\n\t\t\t\tRequired: []string{\"string\", \"number\"},\n\t\t\t},\n\t\t\tcontent: []byte(`{\"string\":\"abc\"}`),\n\t\t\tv: struct {\n\t\t\t\tString string  `json:\"string\"`\n\t\t\t\tNumber float64 `json:\"number\"`\n\t\t\t}{},\n\t\t}, true},\n\t\t{\"validate integer\", args{\n\t\t\tschema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"string\":  {Type: jsonschema.String},\n\t\t\t\t\t\"integer\": {Type: jsonschema.Integer},\n\t\t\t\t},\n\t\t\t\tRequired: []string{\"string\", \"integer\"},\n\t\t\t},\n\t\t\tcontent: []byte(`{\"string\":\"abc\",\"integer\":123}`),\n\t\t\tv: &struct {\n\t\t\t\tString  string `json:\"string\"`\n\t\t\t\tInteger int    `json:\"integer\"`\n\t\t\t}{},\n\t\t}, false},\n\t\t{\"validate integer failed\", args{\n\t\t\tschema: jsonschema.Definition{\n\t\t\t\tType: jsonschema.Object,\n\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\"string\":  {Type: jsonschema.String},\n\t\t\t\t\t\"integer\": {Type: jsonschema.Integer},\n\t\t\t\t},\n\t\t\t\tRequired: []string{\"string\", \"integer\"},\n\t\t\t},\n\t\t\tcontent: []byte(`{\"string\":\"abc\",\"integer\":123.4}`),\n\t\t\tv: &struct {\n\t\t\t\tString  string `json:\"string\"`\n\t\t\t\tInteger int    `json:\"integer\"`\n\t\t\t}{},\n\t\t}, true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := jsonschema.VerifySchemaAndUnmarshal(tt.args.schema, tt.args.content, tt.args.v)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Unmarshal() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCollectDefs(t *testing.T) {\n\ttype args struct {\n\t\tschema jsonschema.Definition\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant map[string]jsonschema.Definition\n\t}{\n\t\t{\n\t\t\t\"test collect defs\",\n\t\t\targs{\n\t\t\t\tschema: jsonschema.Definition{\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t\t},\n\t\t\t\t\tRequired: []string{\"person\"},\n\t\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"Person\": {\n\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\"name\":    {Type: jsonschema.String},\n\t\t\t\t\t\t\t\t\"gender\":  {Type: jsonschema.String, Enum: []string{\"male\", \"female\", \"unknown\"}},\n\t\t\t\t\t\t\t\t\"age\":     {Type: jsonschema.Integer},\n\t\t\t\t\t\t\t\t\"profile\": {Ref: \"#/$defs/Person/$defs/Profile\"},\n\t\t\t\t\t\t\t\t\"tweets\":  {Type: jsonschema.Array, Items: &jsonschema.Definition{Ref: \"#/$defs/Tweet\"}},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRequired: []string{\"name\", \"gender\", \"age\", \"profile\"},\n\t\t\t\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\"Profile\": {\n\t\t\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\t\t\"full_name\": {Type: jsonschema.String},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Tweet\": {\n\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\"text\":   {Type: jsonschema.String},\n\t\t\t\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tmap[string]jsonschema.Definition{\n\t\t\t\t\"#/$defs/Person\": {\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"name\":    {Type: jsonschema.String},\n\t\t\t\t\t\t\"gender\":  {Type: jsonschema.String, Enum: []string{\"male\", \"female\", \"unknown\"}},\n\t\t\t\t\t\t\"age\":     {Type: jsonschema.Integer},\n\t\t\t\t\t\t\"profile\": {Ref: \"#/$defs/Person/$defs/Profile\"},\n\t\t\t\t\t\t\"tweets\":  {Type: jsonschema.Array, Items: &jsonschema.Definition{Ref: \"#/$defs/Tweet\"}},\n\t\t\t\t\t},\n\t\t\t\t\tRequired: []string{\"name\", \"gender\", \"age\", \"profile\"},\n\t\t\t\t\tDefs: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"Profile\": {\n\t\t\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\t\t\"full_name\": {Type: jsonschema.String},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"#/$defs/Person/$defs/Profile\": {\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"full_name\": {Type: jsonschema.String},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"#/$defs/Tweet\": {\n\t\t\t\t\tType: jsonschema.Object,\n\t\t\t\t\tProperties: map[string]jsonschema.Definition{\n\t\t\t\t\t\t\"text\":   {Type: jsonschema.String},\n\t\t\t\t\t\t\"person\": {Ref: \"#/$defs/Person\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := jsonschema.CollectDefs(tt.args.schema)\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"CollectDefs() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "messages.go",
    "content": "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 Message struct {\n\tID          string           `json:\"id\"`\n\tObject      string           `json:\"object\"`\n\tCreatedAt   int              `json:\"created_at\"`\n\tThreadID    string           `json:\"thread_id\"`\n\tRole        string           `json:\"role\"`\n\tContent     []MessageContent `json:\"content\"`\n\tFileIds     []string         `json:\"file_ids\"` //nolint:revive //backwards-compatibility\n\tAssistantID *string          `json:\"assistant_id,omitempty\"`\n\tRunID       *string          `json:\"run_id,omitempty\"`\n\tMetadata    map[string]any   `json:\"metadata\"`\n\n\thttpHeader\n}\n\ntype MessagesList struct {\n\tMessages []Message `json:\"data\"`\n\n\tObject  string  `json:\"object\"`\n\tFirstID *string `json:\"first_id\"`\n\tLastID  *string `json:\"last_id\"`\n\tHasMore bool    `json:\"has_more\"`\n\n\thttpHeader\n}\n\ntype MessageContent struct {\n\tType      string       `json:\"type\"`\n\tText      *MessageText `json:\"text,omitempty\"`\n\tImageFile *ImageFile   `json:\"image_file,omitempty\"`\n\tImageURL  *ImageURL    `json:\"image_url,omitempty\"`\n}\ntype MessageText struct {\n\tValue       string `json:\"value\"`\n\tAnnotations []any  `json:\"annotations\"`\n}\n\ntype ImageFile struct {\n\tFileID string `json:\"file_id\"`\n}\n\ntype ImageURL struct {\n\tURL    string `json:\"url\"`\n\tDetail string `json:\"detail\"`\n}\n\ntype MessageRequest struct {\n\tRole        string             `json:\"role\"`\n\tContent     string             `json:\"content\"`\n\tFileIds     []string           `json:\"file_ids,omitempty\"` //nolint:revive // backwards-compatibility\n\tMetadata    map[string]any     `json:\"metadata,omitempty\"`\n\tAttachments []ThreadAttachment `json:\"attachments,omitempty\"`\n}\n\ntype MessageFile struct {\n\tID        string `json:\"id\"`\n\tObject    string `json:\"object\"`\n\tCreatedAt int    `json:\"created_at\"`\n\tMessageID string `json:\"message_id\"`\n\n\thttpHeader\n}\n\ntype MessageFilesList struct {\n\tMessageFiles []MessageFile `json:\"data\"`\n\n\thttpHeader\n}\n\ntype MessageDeletionStatus struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\n// CreateMessage creates a new message.\nfunc (c *Client) CreateMessage(ctx context.Context, threadID string, request MessageRequest) (msg Message, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s\", threadID, messagesSuffix)\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &msg)\n\treturn\n}\n\n// ListMessage fetches all messages in the thread.\nfunc (c *Client) ListMessage(ctx context.Context, threadID string,\n\tlimit *int,\n\torder *string,\n\tafter *string,\n\tbefore *string,\n\trunID *string,\n) (messages MessagesList, err error) {\n\turlValues := url.Values{}\n\tif limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *limit))\n\t}\n\tif order != nil {\n\t\turlValues.Add(\"order\", *order)\n\t}\n\tif after != nil {\n\t\turlValues.Add(\"after\", *after)\n\t}\n\tif before != nil {\n\t\turlValues.Add(\"before\", *before)\n\t}\n\tif runID != nil {\n\t\turlValues.Add(\"run_id\", *runID)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s%s\", threadID, messagesSuffix, encodedValues)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &messages)\n\treturn\n}\n\n// RetrieveMessage retrieves a Message.\nfunc (c *Client) RetrieveMessage(\n\tctx context.Context,\n\tthreadID, messageID string,\n) (msg Message, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s/%s\", threadID, messagesSuffix, messageID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &msg)\n\treturn\n}\n\n// ModifyMessage modifies a message.\nfunc (c *Client) ModifyMessage(\n\tctx context.Context,\n\tthreadID, messageID string,\n\tmetadata map[string]string,\n) (msg Message, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s/%s\", threadID, messagesSuffix, messageID)\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),\n\t\twithBody(map[string]any{\"metadata\": metadata}), withBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &msg)\n\treturn\n}\n\n// RetrieveMessageFile fetches a message file.\nfunc (c *Client) RetrieveMessageFile(\n\tctx context.Context,\n\tthreadID, messageID, fileID string,\n) (file MessageFile, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s/%s/files/%s\", threadID, messagesSuffix, messageID, fileID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &file)\n\treturn\n}\n\n// ListMessageFiles fetches all files attached to a message.\nfunc (c *Client) ListMessageFiles(\n\tctx context.Context,\n\tthreadID, messageID string,\n) (files MessageFilesList, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s/%s/files\", threadID, messagesSuffix, messageID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &files)\n\treturn\n}\n\n// DeleteMessage deletes a message..\nfunc (c *Client) DeleteMessage(\n\tctx context.Context,\n\tthreadID, messageID string,\n) (status MessageDeletionStatus, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/%s/%s\", threadID, messagesSuffix, messageID)\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &status)\n\treturn\n}\n"
  },
  {
    "path": "messages_test.go",
    "content": "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-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nvar emptyStr = \"\"\n\nfunc setupServerForTestMessage(t *testing.T, server *test.ServerTest) {\n\tthreadID := \"thread_abc123\"\n\tmessageID := \"msg_abc123\"\n\tfileID := \"file_abc123\"\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/messages/\"+messageID+\"/files/\"+fileID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(\n\t\t\t\t\topenai.MessageFile{\n\t\t\t\t\t\tID:        fileID,\n\t\t\t\t\t\tObject:    \"thread.message.file\",\n\t\t\t\t\t\tCreatedAt: 1699061776,\n\t\t\t\t\t\tMessageID: messageID,\n\t\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unsupported messages http method: %s\", r.Method)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/messages/\"+messageID+\"/files\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(\n\t\t\t\t\topenai.MessageFilesList{MessageFiles: []openai.MessageFile{{\n\t\t\t\t\t\tID:        fileID,\n\t\t\t\t\t\tObject:    \"thread.message.file\",\n\t\t\t\t\t\tCreatedAt: 0,\n\t\t\t\t\t\tMessageID: messageID,\n\t\t\t\t\t}}})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unsupported messages http method: %s\", r.Method)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/messages/\"+messageID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodPost:\n\t\t\t\tmetadata := map[string]any{}\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&metadata)\n\t\t\t\tchecks.NoError(t, err, \"unable to decode metadata in modify message call\")\n\t\t\t\tpayload, ok := metadata[\"metadata\"].(map[string]any)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatalf(\"metadata payload improperly wrapped %+v\", metadata)\n\t\t\t\t}\n\n\t\t\t\tresBytes, _ := json.Marshal(\n\t\t\t\t\topenai.Message{\n\t\t\t\t\t\tID:        messageID,\n\t\t\t\t\t\tObject:    \"thread.message\",\n\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\tThreadID:  threadID,\n\t\t\t\t\t\tRole:      \"user\",\n\t\t\t\t\t\tContent: []openai.MessageContent{{\n\t\t\t\t\t\t\tType: \"text\",\n\t\t\t\t\t\t\tText: &openai.MessageText{\n\t\t\t\t\t\t\t\tValue:       \"How does AI work?\",\n\t\t\t\t\t\t\t\tAnnotations: nil,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tFileIds:     nil,\n\t\t\t\t\t\tAssistantID: &emptyStr,\n\t\t\t\t\t\tRunID:       &emptyStr,\n\t\t\t\t\t\tMetadata:    payload,\n\t\t\t\t\t})\n\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(\n\t\t\t\t\topenai.Message{\n\t\t\t\t\t\tID:        messageID,\n\t\t\t\t\t\tObject:    \"thread.message\",\n\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\tThreadID:  threadID,\n\t\t\t\t\t\tRole:      \"user\",\n\t\t\t\t\t\tContent: []openai.MessageContent{{\n\t\t\t\t\t\t\tType: \"text\",\n\t\t\t\t\t\t\tText: &openai.MessageText{\n\t\t\t\t\t\t\t\tValue:       \"How does AI work?\",\n\t\t\t\t\t\t\t\tAnnotations: nil,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tFileIds:     nil,\n\t\t\t\t\t\tAssistantID: &emptyStr,\n\t\t\t\t\t\tRunID:       &emptyStr,\n\t\t\t\t\t\tMetadata:    nil,\n\t\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tresBytes, _ := json.Marshal(openai.MessageDeletionStatus{\n\t\t\t\t\tID:      messageID,\n\t\t\t\t\tObject:  \"thread.message.deleted\",\n\t\t\t\t\tDeleted: true,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unsupported messages http method: %s\", r.Method)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/messages\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodPost:\n\t\t\t\tresBytes, _ := json.Marshal(openai.Message{\n\t\t\t\t\tID:        messageID,\n\t\t\t\t\tObject:    \"thread.message\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tThreadID:  threadID,\n\t\t\t\t\tRole:      \"user\",\n\t\t\t\t\tContent: []openai.MessageContent{{\n\t\t\t\t\t\tType: \"text\",\n\t\t\t\t\t\tText: &openai.MessageText{\n\t\t\t\t\t\t\tValue:       \"How does AI work?\",\n\t\t\t\t\t\t\tAnnotations: nil,\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t\tFileIds:     nil,\n\t\t\t\t\tAssistantID: &emptyStr,\n\t\t\t\t\tRunID:       &emptyStr,\n\t\t\t\t\tMetadata:    nil,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.MessagesList{\n\t\t\t\t\tObject: \"list\",\n\t\t\t\t\tMessages: []openai.Message{{\n\t\t\t\t\t\tID:        messageID,\n\t\t\t\t\t\tObject:    \"thread.message\",\n\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\tThreadID:  threadID,\n\t\t\t\t\t\tRole:      \"user\",\n\t\t\t\t\t\tContent: []openai.MessageContent{{\n\t\t\t\t\t\t\tType: \"text\",\n\t\t\t\t\t\t\tText: &openai.MessageText{\n\t\t\t\t\t\t\t\tValue:       \"How does AI work?\",\n\t\t\t\t\t\t\t\tAnnotations: nil,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tFileIds:     nil,\n\t\t\t\t\t\tAssistantID: &emptyStr,\n\t\t\t\t\t\tRunID:       &emptyStr,\n\t\t\t\t\t\tMetadata:    nil,\n\t\t\t\t\t}},\n\t\t\t\t\tFirstID: &messageID,\n\t\t\t\t\tLastID:  &messageID,\n\t\t\t\t\tHasMore: false,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unsupported messages http method: %s\", r.Method)\n\t\t\t}\n\t\t},\n\t)\n}\n\n// TestMessages Tests the messages endpoint of the API using the mocked server.\nfunc TestMessages(t *testing.T) {\n\tthreadID := \"thread_abc123\"\n\tmessageID := \"msg_abc123\"\n\tfileID := \"file_abc123\"\n\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tsetupServerForTestMessage(t, server)\n\tctx := context.Background()\n\n\t// static assertion of return type\n\tvar msg openai.Message\n\tmsg, err := client.CreateMessage(ctx, threadID, openai.MessageRequest{\n\t\tRole:     \"user\",\n\t\tContent:  \"How does AI work?\",\n\t\tFileIds:  nil,\n\t\tMetadata: nil,\n\t})\n\tchecks.NoError(t, err, \"CreateMessage error\")\n\tif msg.ID != messageID {\n\t\tt.Fatalf(\"unexpected message id: '%s'\", msg.ID)\n\t}\n\n\tvar msgs openai.MessagesList\n\tmsgs, err = client.ListMessage(ctx, threadID, nil, nil, nil, nil, nil)\n\tchecks.NoError(t, err, \"ListMessages error\")\n\tif len(msgs.Messages) != 1 {\n\t\tt.Fatalf(\"unexpected length of fetched messages\")\n\t}\n\n\t// with pagination options set\n\tlimit := 1\n\torder := \"desc\"\n\tafter := \"obj_foo\"\n\tbefore := \"obj_bar\"\n\trunID := \"run_abc123\"\n\tmsgs, err = client.ListMessage(ctx, threadID, &limit, &order, &after, &before, &runID)\n\tchecks.NoError(t, err, \"ListMessages error\")\n\tif len(msgs.Messages) != 1 {\n\t\tt.Fatalf(\"unexpected length of fetched messages\")\n\t}\n\n\tmsg, err = client.RetrieveMessage(ctx, threadID, messageID)\n\tchecks.NoError(t, err, \"RetrieveMessage error\")\n\tif msg.ID != messageID {\n\t\tt.Fatalf(\"unexpected message id: '%s'\", msg.ID)\n\t}\n\n\tmsg, err = client.ModifyMessage(ctx, threadID, messageID,\n\t\tmap[string]string{\n\t\t\t\"foo\": \"bar\",\n\t\t})\n\tchecks.NoError(t, err, \"ModifyMessage error\")\n\tif msg.Metadata[\"foo\"] != \"bar\" {\n\t\tt.Fatalf(\"expected message metadata to get modified\")\n\t}\n\n\tmsgDel, err := client.DeleteMessage(ctx, threadID, messageID)\n\tchecks.NoError(t, err, \"DeleteMessage error\")\n\tif msgDel.ID != messageID {\n\t\tt.Fatalf(\"unexpected message id: '%s'\", msg.ID)\n\t}\n\tif !msgDel.Deleted {\n\t\tt.Fatalf(\"expected deleted is true\")\n\t}\n\t_, err = client.DeleteMessage(ctx, threadID, \"not_exist_id\")\n\tchecks.HasError(t, err, \"DeleteMessage error\")\n\n\t// message files\n\tvar msgFile openai.MessageFile\n\tmsgFile, err = client.RetrieveMessageFile(ctx, threadID, messageID, fileID)\n\tchecks.NoError(t, err, \"RetrieveMessageFile error\")\n\tif msgFile.ID != fileID {\n\t\tt.Fatalf(\"unexpected message file id: '%s'\", msgFile.ID)\n\t}\n\n\tvar msgFiles openai.MessageFilesList\n\tmsgFiles, err = client.ListMessageFiles(ctx, threadID, messageID)\n\tchecks.NoError(t, err, \"RetrieveMessageFile error\")\n\tif len(msgFiles.MessageFiles) != 1 {\n\t\tt.Fatalf(\"unexpected count of message files: %d\", len(msgFiles.MessageFiles))\n\t}\n\tif msgFiles.MessageFiles[0].ID != fileID {\n\t\tt.Fatalf(\"unexpected message file id: '%s' in list message files\", msgFiles.MessageFiles[0].ID)\n\t}\n}\n"
  },
  {
    "path": "models.go",
    "content": "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 {\n\tCreatedAt  int64        `json:\"created\"`\n\tID         string       `json:\"id\"`\n\tObject     string       `json:\"object\"`\n\tOwnedBy    string       `json:\"owned_by\"`\n\tPermission []Permission `json:\"permission\"`\n\tRoot       string       `json:\"root\"`\n\tParent     string       `json:\"parent\"`\n\n\thttpHeader\n}\n\n// Permission struct represents an OpenAPI permission.\ntype Permission struct {\n\tCreatedAt          int64       `json:\"created\"`\n\tID                 string      `json:\"id\"`\n\tObject             string      `json:\"object\"`\n\tAllowCreateEngine  bool        `json:\"allow_create_engine\"`\n\tAllowSampling      bool        `json:\"allow_sampling\"`\n\tAllowLogprobs      bool        `json:\"allow_logprobs\"`\n\tAllowSearchIndices bool        `json:\"allow_search_indices\"`\n\tAllowView          bool        `json:\"allow_view\"`\n\tAllowFineTuning    bool        `json:\"allow_fine_tuning\"`\n\tOrganization       string      `json:\"organization\"`\n\tGroup              interface{} `json:\"group\"`\n\tIsBlocking         bool        `json:\"is_blocking\"`\n}\n\n// FineTuneModelDeleteResponse represents the deletion status of a fine-tuned model.\ntype FineTuneModelDeleteResponse struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\n// ModelsList is a list of models, including those that belong to the user or organization.\ntype ModelsList struct {\n\tModels []Model `json:\"data\"`\n\n\thttpHeader\n}\n\n// ListModels Lists the currently available models,\n// and provides basic information about each model such as the model id and parent.\nfunc (c *Client) ListModels(ctx context.Context) (models ModelsList, err error) {\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(\"/models\"))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &models)\n\treturn\n}\n\n// GetModel Retrieves a model instance, providing basic information about\n// the model such as the owner and permissioning.\nfunc (c *Client) GetModel(ctx context.Context, modelID string) (model Model, err error) {\n\turlSuffix := fmt.Sprintf(\"/models/%s\", modelID)\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &model)\n\treturn\n}\n\n// DeleteFineTuneModel Deletes a fine-tune model. You must have the Owner\n// role in your organization to delete a model.\nfunc (c *Client) DeleteFineTuneModel(ctx context.Context, modelID string) (\n\tresponse FineTuneModelDeleteResponse, err error) {\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(\"/models/\"+modelID))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "models_test.go",
    "content": "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/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nconst testFineTuneModelID = \"fine-tune-model-id\"\n\n// TestListModels Tests the list models endpoint of the API using the mocked server.\nfunc TestListModels(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models\", handleListModelsEndpoint)\n\t_, err := client.ListModels(context.Background())\n\tchecks.NoError(t, err, \"ListModels error\")\n}\n\nfunc TestAzureListModels(t *testing.T) {\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/openai/models\", handleListModelsEndpoint)\n\t_, err := client.ListModels(context.Background())\n\tchecks.NoError(t, err, \"ListModels error\")\n}\n\n// handleListModelsEndpoint Handles the list models endpoint by the test server.\nfunc handleListModelsEndpoint(w http.ResponseWriter, _ *http.Request) {\n\tresBytes, _ := json.Marshal(openai.ModelsList{})\n\tfmt.Fprintln(w, string(resBytes))\n}\n\n// TestGetModel Tests the retrieve model endpoint of the API using the mocked server.\nfunc TestGetModel(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models/text-davinci-003\", handleGetModelEndpoint)\n\t_, err := client.GetModel(context.Background(), \"text-davinci-003\")\n\tchecks.NoError(t, err, \"GetModel error\")\n}\n\n// TestGetModelO3 Tests the retrieve O3 model endpoint of the API using the mocked server.\nfunc TestGetModelO3(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models/o3\", handleGetModelEndpoint)\n\t_, err := client.GetModel(context.Background(), \"o3\")\n\tchecks.NoError(t, err, \"GetModel error for O3\")\n}\n\n// TestGetModelO4Mini Tests the retrieve O4Mini model endpoint of the API using the mocked server.\nfunc TestGetModelO4Mini(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models/o4-mini\", handleGetModelEndpoint)\n\t_, err := client.GetModel(context.Background(), \"o4-mini\")\n\tchecks.NoError(t, err, \"GetModel error for O4Mini\")\n}\n\nfunc TestAzureGetModel(t *testing.T) {\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/openai/models/text-davinci-003\", handleGetModelEndpoint)\n\t_, err := client.GetModel(context.Background(), \"text-davinci-003\")\n\tchecks.NoError(t, err, \"GetModel error\")\n}\n\n// handleGetModelsEndpoint Handles the get model endpoint by the test server.\nfunc handleGetModelEndpoint(w http.ResponseWriter, _ *http.Request) {\n\tresBytes, _ := json.Marshal(openai.Model{})\n\tfmt.Fprintln(w, string(resBytes))\n}\n\nfunc TestGetModelReturnTimeoutError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models/text-davinci-003\", func(http.ResponseWriter, *http.Request) {\n\t\ttime.Sleep(10 * time.Nanosecond)\n\t})\n\tctx := context.Background()\n\tctx, cancel := context.WithTimeout(ctx, time.Nanosecond)\n\tdefer cancel()\n\n\t_, err := client.GetModel(ctx, \"text-davinci-003\")\n\tif err == nil {\n\t\tt.Fatal(\"Did not return error\")\n\t}\n\tif !os.IsTimeout(err) {\n\t\tt.Fatal(\"Did not return timeout error\")\n\t}\n}\n\nfunc TestDeleteFineTuneModel(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/models/\"+testFineTuneModelID, handleDeleteFineTuneModelEndpoint)\n\t_, err := client.DeleteFineTuneModel(context.Background(), testFineTuneModelID)\n\tchecks.NoError(t, err, \"DeleteFineTuneModel error\")\n}\n\nfunc handleDeleteFineTuneModelEndpoint(w http.ResponseWriter, _ *http.Request) {\n\tresBytes, _ := json.Marshal(openai.FineTuneModelDeleteResponse{})\n\tfmt.Fprintln(w, string(resBytes))\n}\n"
  },
  {
    "path": "moderation.go",
    "content": "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 whether content complies with OpenAI's usage policies.\n// Developers can thus identify content that our usage policies prohibits and take action, for instance by filtering it.\n\n// The default is text-moderation-latest which will be automatically upgraded over time.\n// This ensures you are always using our most accurate model.\n// If you use text-moderation-stable, we will provide advanced notice before updating the model.\n// Accuracy of text-moderation-stable may be slightly lower than for text-moderation-latest.\nconst (\n\tModerationOmniLatest   = \"omni-moderation-latest\"\n\tModerationOmni20240926 = \"omni-moderation-2024-09-26\"\n\tModerationTextStable   = \"text-moderation-stable\"\n\tModerationTextLatest   = \"text-moderation-latest\"\n\t// Deprecated: use ModerationTextStable and ModerationTextLatest instead.\n\tModerationText001 = \"text-moderation-001\"\n)\n\nvar (\n\tErrModerationInvalidModel = errors.New(\"this model is not supported with moderation, please use text-moderation-stable or text-moderation-latest instead\") //nolint:lll\n)\n\nvar validModerationModel = map[string]struct{}{\n\tModerationOmniLatest:   {},\n\tModerationOmni20240926: {},\n\tModerationTextStable:   {},\n\tModerationTextLatest:   {},\n}\n\n// ModerationRequest represents a request structure for moderation API.\ntype ModerationRequest struct {\n\tInput string `json:\"input,omitempty\"`\n\tModel string `json:\"model,omitempty\"`\n}\n\n// Result represents one of possible moderation results.\ntype Result struct {\n\tCategories     ResultCategories     `json:\"categories\"`\n\tCategoryScores ResultCategoryScores `json:\"category_scores\"`\n\tFlagged        bool                 `json:\"flagged\"`\n}\n\n// ResultCategories represents Categories of Result.\ntype ResultCategories struct {\n\tHate                  bool `json:\"hate\"`\n\tHateThreatening       bool `json:\"hate/threatening\"`\n\tHarassment            bool `json:\"harassment\"`\n\tHarassmentThreatening bool `json:\"harassment/threatening\"`\n\tSelfHarm              bool `json:\"self-harm\"`\n\tSelfHarmIntent        bool `json:\"self-harm/intent\"`\n\tSelfHarmInstructions  bool `json:\"self-harm/instructions\"`\n\tSexual                bool `json:\"sexual\"`\n\tSexualMinors          bool `json:\"sexual/minors\"`\n\tViolence              bool `json:\"violence\"`\n\tViolenceGraphic       bool `json:\"violence/graphic\"`\n}\n\n// ResultCategoryScores represents CategoryScores of Result.\ntype ResultCategoryScores struct {\n\tHate                  float32 `json:\"hate\"`\n\tHateThreatening       float32 `json:\"hate/threatening\"`\n\tHarassment            float32 `json:\"harassment\"`\n\tHarassmentThreatening float32 `json:\"harassment/threatening\"`\n\tSelfHarm              float32 `json:\"self-harm\"`\n\tSelfHarmIntent        float32 `json:\"self-harm/intent\"`\n\tSelfHarmInstructions  float32 `json:\"self-harm/instructions\"`\n\tSexual                float32 `json:\"sexual\"`\n\tSexualMinors          float32 `json:\"sexual/minors\"`\n\tViolence              float32 `json:\"violence\"`\n\tViolenceGraphic       float32 `json:\"violence/graphic\"`\n}\n\n// ModerationResponse represents a response structure for moderation API.\ntype ModerationResponse struct {\n\tID      string   `json:\"id\"`\n\tModel   string   `json:\"model\"`\n\tResults []Result `json:\"results\"`\n\n\thttpHeader\n}\n\n// Moderations — perform a moderation api call over a string.\n// Input can be an array or slice but a string will reduce the complexity.\nfunc (c *Client) Moderations(ctx context.Context, request ModerationRequest) (response ModerationResponse, err error) {\n\tif _, ok := validModerationModel[request.Model]; len(request.Model) > 0 && !ok {\n\t\terr = ErrModerationInvalidModel\n\t\treturn\n\t}\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/moderations\", withModel(request.Model)),\n\t\twithBody(&request),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "moderation_test.go",
    "content": "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\"time\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\n// TestModeration Tests the moderations endpoint of the API using the mocked server.\nfunc TestModerations(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/moderations\", handleModerationEndpoint)\n\t_, err := client.Moderations(context.Background(), openai.ModerationRequest{\n\t\tModel: openai.ModerationTextStable,\n\t\tInput: \"I want to kill them.\",\n\t})\n\tchecks.NoError(t, err, \"Moderation error\")\n}\n\n// TestModerationsWithIncorrectModel Tests passing valid and invalid models to moderations endpoint.\nfunc TestModerationsWithDifferentModelOptions(t *testing.T) {\n\tvar modelOptions []struct {\n\t\tmodel  string\n\t\texpect error\n\t}\n\tmodelOptions = append(modelOptions,\n\t\tgetModerationModelTestOption(openai.GPT3Dot5Turbo, openai.ErrModerationInvalidModel),\n\t\tgetModerationModelTestOption(openai.ModerationTextStable, nil),\n\t\tgetModerationModelTestOption(openai.ModerationTextLatest, nil),\n\t\tgetModerationModelTestOption(openai.ModerationOmni20240926, nil),\n\t\tgetModerationModelTestOption(openai.ModerationOmniLatest, nil),\n\t\tgetModerationModelTestOption(\"\", nil),\n\t)\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/moderations\", handleModerationEndpoint)\n\tfor _, modelTest := range modelOptions {\n\t\t_, err := client.Moderations(context.Background(), openai.ModerationRequest{\n\t\t\tModel: modelTest.model,\n\t\t\tInput: \"I want to kill them.\",\n\t\t})\n\t\tchecks.ErrorIs(t, err, modelTest.expect,\n\t\t\tfmt.Sprintf(\"Moderations(..) expects err: %v, actual err:%v\", modelTest.expect, err))\n\t}\n}\n\nfunc getModerationModelTestOption(model string, expect error) struct {\n\tmodel  string\n\texpect error\n} {\n\treturn struct {\n\t\tmodel  string\n\t\texpect error\n\t}{model: model, expect: expect}\n}\n\n// handleModerationEndpoint Handles the moderation endpoint by the test server.\nfunc handleModerationEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar err error\n\tvar resBytes []byte\n\n\t// completions only accepts POST requests\n\tif r.Method != \"POST\" {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t}\n\tvar moderationReq openai.ModerationRequest\n\tif moderationReq, err = getModerationBody(r); err != nil {\n\t\thttp.Error(w, \"could not read request\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tresCat := openai.ResultCategories{}\n\tresCatScore := openai.ResultCategoryScores{}\n\tswitch {\n\tcase strings.Contains(moderationReq.Input, \"hate\"):\n\t\tresCat = openai.ResultCategories{Hate: true}\n\t\tresCatScore = openai.ResultCategoryScores{Hate: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"hate more\"):\n\t\tresCat = openai.ResultCategories{HateThreatening: true}\n\t\tresCatScore = openai.ResultCategoryScores{HateThreatening: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"harass\"):\n\t\tresCat = openai.ResultCategories{Harassment: true}\n\t\tresCatScore = openai.ResultCategoryScores{Harassment: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"harass hard\"):\n\t\tresCat = openai.ResultCategories{Harassment: true}\n\t\tresCatScore = openai.ResultCategoryScores{HarassmentThreatening: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"suicide\"):\n\t\tresCat = openai.ResultCategories{SelfHarm: true}\n\t\tresCatScore = openai.ResultCategoryScores{SelfHarm: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"wanna suicide\"):\n\t\tresCat = openai.ResultCategories{SelfHarmIntent: true}\n\t\tresCatScore = openai.ResultCategoryScores{SelfHarm: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"drink bleach\"):\n\t\tresCat = openai.ResultCategories{SelfHarmInstructions: true}\n\t\tresCatScore = openai.ResultCategoryScores{SelfHarmInstructions: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"porn\"):\n\t\tresCat = openai.ResultCategories{Sexual: true}\n\t\tresCatScore = openai.ResultCategoryScores{Sexual: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"child porn\"):\n\t\tresCat = openai.ResultCategories{SexualMinors: true}\n\t\tresCatScore = openai.ResultCategoryScores{SexualMinors: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"kill\"):\n\t\tresCat = openai.ResultCategories{Violence: true}\n\t\tresCatScore = openai.ResultCategoryScores{Violence: 1}\n\n\tcase strings.Contains(moderationReq.Input, \"corpse\"):\n\t\tresCat = openai.ResultCategories{ViolenceGraphic: true}\n\t\tresCatScore = openai.ResultCategoryScores{ViolenceGraphic: 1}\n\t}\n\n\tresult := openai.Result{Categories: resCat, CategoryScores: resCatScore, Flagged: true}\n\n\tres := openai.ModerationResponse{\n\t\tID:    strconv.Itoa(int(time.Now().Unix())),\n\t\tModel: moderationReq.Model,\n\t}\n\tres.Results = append(res.Results, result)\n\n\tresBytes, _ = json.Marshal(res)\n\tfmt.Fprintln(w, string(resBytes))\n}\n\n// getModerationBody Returns the body of the request to do a moderation.\nfunc getModerationBody(r *http.Request) (openai.ModerationRequest, error) {\n\tmoderation := openai.ModerationRequest{}\n\t// read the request body\n\treqBody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\treturn openai.ModerationRequest{}, err\n\t}\n\terr = json.Unmarshal(reqBody, &moderation)\n\tif err != nil {\n\t\treturn openai.ModerationRequest{}, err\n\t}\n\treturn moderation, nil\n}\n"
  },
  {
    "path": "openai_test.go",
    "content": "package openai_test\n\nimport (\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n)\n\nfunc setupOpenAITestServer() (client *openai.Client, server *test.ServerTest, teardown func()) {\n\tserver = test.NewTestServer()\n\tts := server.OpenAITestServer()\n\tts.Start()\n\tteardown = ts.Close\n\tconfig := openai.DefaultConfig(test.GetTestToken())\n\tconfig.BaseURL = ts.URL + \"/v1\"\n\tclient = openai.NewClientWithConfig(config)\n\treturn\n}\n\nfunc setupAzureTestServer() (client *openai.Client, server *test.ServerTest, teardown func()) {\n\tserver = test.NewTestServer()\n\tts := server.OpenAITestServer()\n\tts.Start()\n\tteardown = ts.Close\n\tconfig := openai.DefaultAzureConfig(test.GetTestToken(), \"https://dummylab.openai.azure.com/\")\n\tconfig.BaseURL = ts.URL\n\tclient = openai.NewClientWithConfig(config)\n\treturn\n}\n\n// numTokens Returns the number of GPT-3 encoded tokens in the given text.\n// This function approximates based on the rule of thumb stated by OpenAI:\n// https://beta.openai.com/tokenizer.\n//\n// TODO: implement an actual tokenizer for GPT-3 and Codex (once available).\nfunc numTokens(s string) int {\n\treturn int(float32(len(s)) / 4)\n}\n"
  },
  {
    "path": "ratelimit.go",
    "content": "package openai\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n)\n\n// RateLimitHeaders struct represents Openai rate limits headers.\ntype RateLimitHeaders struct {\n\tLimitRequests     int       `json:\"x-ratelimit-limit-requests\"`\n\tLimitTokens       int       `json:\"x-ratelimit-limit-tokens\"`\n\tRemainingRequests int       `json:\"x-ratelimit-remaining-requests\"`\n\tRemainingTokens   int       `json:\"x-ratelimit-remaining-tokens\"`\n\tResetRequests     ResetTime `json:\"x-ratelimit-reset-requests\"`\n\tResetTokens       ResetTime `json:\"x-ratelimit-reset-tokens\"`\n}\n\ntype ResetTime string\n\nfunc (r ResetTime) String() string {\n\treturn string(r)\n}\n\nfunc (r ResetTime) Time() time.Time {\n\td, _ := time.ParseDuration(string(r))\n\treturn time.Now().Add(d)\n}\n\nfunc newRateLimitHeaders(h http.Header) RateLimitHeaders {\n\tlimitReq, _ := strconv.Atoi(h.Get(\"x-ratelimit-limit-requests\"))\n\tlimitTokens, _ := strconv.Atoi(h.Get(\"x-ratelimit-limit-tokens\"))\n\tremainingReq, _ := strconv.Atoi(h.Get(\"x-ratelimit-remaining-requests\"))\n\tremainingTokens, _ := strconv.Atoi(h.Get(\"x-ratelimit-remaining-tokens\"))\n\treturn RateLimitHeaders{\n\t\tLimitRequests:     limitReq,\n\t\tLimitTokens:       limitTokens,\n\t\tRemainingRequests: remainingReq,\n\t\tRemainingTokens:   remainingTokens,\n\t\tResetRequests:     ResetTime(h.Get(\"x-ratelimit-reset-requests\")),\n\t\tResetTokens:       ResetTime(h.Get(\"x-ratelimit-reset-tokens\")),\n\t}\n}\n"
  },
  {
    "path": "reasoning_validator.go",
    "content": "package openai\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\nvar (\n\t// Deprecated: use ErrReasoningModelMaxTokensDeprecated instead.\n\tErrO1MaxTokensDeprecated                   = errors.New(\"this model is not supported MaxTokens, please use MaxCompletionTokens\")                               //nolint:lll\n\tErrCompletionUnsupportedModel              = errors.New(\"this model is not supported with this method, please use CreateChatCompletion client method instead\") //nolint:lll\n\tErrCompletionStreamNotSupported            = errors.New(\"streaming is not supported with this method, please use CreateCompletionStream\")                      //nolint:lll\n\tErrCompletionRequestPromptTypeNotSupported = errors.New(\"the type of CompletionRequest.Prompt only supports string and []string\")                              //nolint:lll\n)\n\nvar (\n\tErrO1BetaLimitationsMessageTypes = errors.New(\"this model has beta-limitations, user and assistant messages only, system messages are not supported\")       //nolint:lll\n\tErrO1BetaLimitationsTools        = errors.New(\"this model has beta-limitations, tools, function calling, and response format parameters are not supported\") //nolint:lll\n\t// Deprecated: use ErrReasoningModelLimitations* instead.\n\tErrO1BetaLimitationsLogprobs = errors.New(\"this model has beta-limitations, logprobs not supported\")                                                                               //nolint:lll\n\tErrO1BetaLimitationsOther    = errors.New(\"this model has beta-limitations, temperature, top_p and n are fixed at 1, while presence_penalty and frequency_penalty are fixed at 0\") //nolint:lll\n)\n\nvar (\n\t//nolint:lll\n\tErrReasoningModelMaxTokensDeprecated = errors.New(\"this model is not supported MaxTokens, please use MaxCompletionTokens\")\n\tErrReasoningModelLimitationsLogprobs = errors.New(\"this model has beta-limitations, logprobs not supported\")                                                                               //nolint:lll\n\tErrReasoningModelLimitationsOther    = errors.New(\"this model has beta-limitations, temperature, top_p and n are fixed at 1, while presence_penalty and frequency_penalty are fixed at 0\") //nolint:lll\n)\n\n// ReasoningValidator handles validation for reasoning model requests.\ntype ReasoningValidator struct{}\n\n// NewReasoningValidator creates a new validator for reasoning models.\nfunc NewReasoningValidator() *ReasoningValidator {\n\treturn &ReasoningValidator{}\n}\n\n// Validate performs all validation checks for reasoning models.\nfunc (v *ReasoningValidator) Validate(request ChatCompletionRequest) error {\n\to1Series := strings.HasPrefix(request.Model, \"o1\")\n\to3Series := strings.HasPrefix(request.Model, \"o3\")\n\to4Series := strings.HasPrefix(request.Model, \"o4\")\n\tgpt5Series := strings.HasPrefix(request.Model, \"gpt-5\")\n\n\tif !o1Series && !o3Series && !o4Series && !gpt5Series {\n\t\treturn nil\n\t}\n\n\tif err := v.validateReasoningModelParams(request); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// validateReasoningModelParams checks reasoning model parameters.\nfunc (v *ReasoningValidator) validateReasoningModelParams(request ChatCompletionRequest) error {\n\tif request.MaxTokens > 0 {\n\t\treturn ErrReasoningModelMaxTokensDeprecated\n\t}\n\tif request.LogProbs {\n\t\treturn ErrReasoningModelLimitationsLogprobs\n\t}\n\tif request.Temperature > 0 && request.Temperature != 1 {\n\t\treturn ErrReasoningModelLimitationsOther\n\t}\n\tif request.TopP > 0 && request.TopP != 1 {\n\t\treturn ErrReasoningModelLimitationsOther\n\t}\n\tif request.N > 0 && request.N != 1 {\n\t\treturn ErrReasoningModelLimitationsOther\n\t}\n\tif request.PresencePenalty > 0 {\n\t\treturn ErrReasoningModelLimitationsOther\n\t}\n\tif request.FrequencyPenalty > 0 {\n\t\treturn ErrReasoningModelLimitationsOther\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "run.go",
    "content": "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             `json:\"id\"`\n\tObject         string             `json:\"object\"`\n\tCreatedAt      int64              `json:\"created_at\"`\n\tThreadID       string             `json:\"thread_id\"`\n\tAssistantID    string             `json:\"assistant_id\"`\n\tStatus         RunStatus          `json:\"status\"`\n\tRequiredAction *RunRequiredAction `json:\"required_action,omitempty\"`\n\tLastError      *RunLastError      `json:\"last_error,omitempty\"`\n\tExpiresAt      int64              `json:\"expires_at\"`\n\tStartedAt      *int64             `json:\"started_at,omitempty\"`\n\tCancelledAt    *int64             `json:\"cancelled_at,omitempty\"`\n\tFailedAt       *int64             `json:\"failed_at,omitempty\"`\n\tCompletedAt    *int64             `json:\"completed_at,omitempty\"`\n\tModel          string             `json:\"model\"`\n\tInstructions   string             `json:\"instructions,omitempty\"`\n\tTools          []Tool             `json:\"tools\"`\n\tFileIDS        []string           `json:\"file_ids\"` //nolint:revive // backwards-compatibility\n\tMetadata       map[string]any     `json:\"metadata\"`\n\tUsage          Usage              `json:\"usage,omitempty\"`\n\n\tTemperature *float32 `json:\"temperature,omitempty\"`\n\t// The maximum number of prompt tokens that may be used over the course of the run.\n\t// If the run exceeds the number of prompt tokens specified, the run will end with status 'incomplete'.\n\tMaxPromptTokens int `json:\"max_prompt_tokens,omitempty\"`\n\t// The maximum number of completion tokens that may be used over the course of the run.\n\t// If the run exceeds the number of completion tokens specified, the run will end with status 'incomplete'.\n\tMaxCompletionTokens int `json:\"max_completion_tokens,omitempty\"`\n\t// ThreadTruncationStrategy defines the truncation strategy to use for the thread.\n\tTruncationStrategy *ThreadTruncationStrategy `json:\"truncation_strategy,omitempty\"`\n\n\thttpHeader\n}\n\ntype RunStatus string\n\nconst (\n\tRunStatusQueued         RunStatus = \"queued\"\n\tRunStatusInProgress     RunStatus = \"in_progress\"\n\tRunStatusRequiresAction RunStatus = \"requires_action\"\n\tRunStatusCancelling     RunStatus = \"cancelling\"\n\tRunStatusFailed         RunStatus = \"failed\"\n\tRunStatusCompleted      RunStatus = \"completed\"\n\tRunStatusIncomplete     RunStatus = \"incomplete\"\n\tRunStatusExpired        RunStatus = \"expired\"\n\tRunStatusCancelled      RunStatus = \"cancelled\"\n)\n\ntype RunRequiredAction struct {\n\tType              RequiredActionType `json:\"type\"`\n\tSubmitToolOutputs *SubmitToolOutputs `json:\"submit_tool_outputs,omitempty\"`\n}\n\ntype RequiredActionType string\n\nconst (\n\tRequiredActionTypeSubmitToolOutputs RequiredActionType = \"submit_tool_outputs\"\n)\n\ntype SubmitToolOutputs struct {\n\tToolCalls []ToolCall `json:\"tool_calls\"`\n}\n\ntype RunLastError struct {\n\tCode    RunError `json:\"code\"`\n\tMessage string   `json:\"message\"`\n}\n\ntype RunError string\n\nconst (\n\tRunErrorServerError       RunError = \"server_error\"\n\tRunErrorRateLimitExceeded RunError = \"rate_limit_exceeded\"\n)\n\ntype RunRequest struct {\n\tAssistantID            string          `json:\"assistant_id\"`\n\tModel                  string          `json:\"model,omitempty\"`\n\tInstructions           string          `json:\"instructions,omitempty\"`\n\tAdditionalInstructions string          `json:\"additional_instructions,omitempty\"`\n\tAdditionalMessages     []ThreadMessage `json:\"additional_messages,omitempty\"`\n\tTools                  []Tool          `json:\"tools,omitempty\"`\n\tMetadata               map[string]any  `json:\"metadata,omitempty\"`\n\n\t// Sampling temperature between 0 and 2. Higher values like 0.8 are  more random.\n\t// lower values are more focused and deterministic.\n\tTemperature *float32 `json:\"temperature,omitempty\"`\n\tTopP        *float32 `json:\"top_p,omitempty\"`\n\n\t// The maximum number of prompt tokens that may be used over the course of the run.\n\t// If the run exceeds the number of prompt tokens specified, the run will end with status 'incomplete'.\n\tMaxPromptTokens int `json:\"max_prompt_tokens,omitempty\"`\n\n\t// The maximum number of completion tokens that may be used over the course of the run.\n\t// If the run exceeds the number of completion tokens specified, the run will end with status 'incomplete'.\n\tMaxCompletionTokens int `json:\"max_completion_tokens,omitempty\"`\n\n\t// ThreadTruncationStrategy defines the truncation strategy to use for the thread.\n\tTruncationStrategy *ThreadTruncationStrategy `json:\"truncation_strategy,omitempty\"`\n\n\t// This can be either a string or a ToolChoice object.\n\tToolChoice any `json:\"tool_choice,omitempty\"`\n\t// This can be either a string or a ResponseFormat object.\n\tResponseFormat any `json:\"response_format,omitempty\"`\n\t// Disable the default behavior of parallel tool calls by setting it: false.\n\tParallelToolCalls any `json:\"parallel_tool_calls,omitempty\"`\n}\n\n// ThreadTruncationStrategy defines the truncation strategy to use for the thread.\n// https://platform.openai.com/docs/assistants/how-it-works/truncation-strategy.\ntype ThreadTruncationStrategy struct {\n\t// default 'auto'.\n\tType TruncationStrategy `json:\"type,omitempty\"`\n\t// this field should be set if the truncation strategy is set to LastMessages.\n\tLastMessages *int `json:\"last_messages,omitempty\"`\n}\n\n// TruncationStrategy defines the existing truncation strategies existing for thread management in an assistant.\ntype TruncationStrategy string\n\nconst (\n\t// TruncationStrategyAuto messages in the middle of the thread will be dropped to fit the context length of the model.\n\tTruncationStrategyAuto = TruncationStrategy(\"auto\")\n\t// TruncationStrategyLastMessages the thread will be truncated to the n most recent messages in the thread.\n\tTruncationStrategyLastMessages = TruncationStrategy(\"last_messages\")\n)\n\n// ReponseFormat specifies the format the model must output.\n// https://platform.openai.com/docs/api-reference/runs/createRun#runs-createrun-response_format.\n// Type can either be text or json_object.\ntype ReponseFormat struct {\n\tType string `json:\"type\"`\n}\n\ntype RunModifyRequest struct {\n\tMetadata map[string]any `json:\"metadata,omitempty\"`\n}\n\n// RunList is a list of runs.\ntype RunList struct {\n\tRuns []Run `json:\"data\"`\n\n\thttpHeader\n}\n\ntype SubmitToolOutputsRequest struct {\n\tToolOutputs []ToolOutput `json:\"tool_outputs\"`\n}\n\ntype ToolOutput struct {\n\tToolCallID string `json:\"tool_call_id\"`\n\tOutput     any    `json:\"output\"`\n}\n\ntype CreateThreadAndRunRequest struct {\n\tRunRequest\n\tThread ThreadRequest `json:\"thread\"`\n}\n\ntype RunStep struct {\n\tID          string         `json:\"id\"`\n\tObject      string         `json:\"object\"`\n\tCreatedAt   int64          `json:\"created_at\"`\n\tAssistantID string         `json:\"assistant_id\"`\n\tThreadID    string         `json:\"thread_id\"`\n\tRunID       string         `json:\"run_id\"`\n\tType        RunStepType    `json:\"type\"`\n\tStatus      RunStepStatus  `json:\"status\"`\n\tStepDetails StepDetails    `json:\"step_details\"`\n\tLastError   *RunLastError  `json:\"last_error,omitempty\"`\n\tExpiredAt   *int64         `json:\"expired_at,omitempty\"`\n\tCancelledAt *int64         `json:\"cancelled_at,omitempty\"`\n\tFailedAt    *int64         `json:\"failed_at,omitempty\"`\n\tCompletedAt *int64         `json:\"completed_at,omitempty\"`\n\tMetadata    map[string]any `json:\"metadata\"`\n\n\thttpHeader\n}\n\ntype RunStepStatus string\n\nconst (\n\tRunStepStatusInProgress RunStepStatus = \"in_progress\"\n\tRunStepStatusCancelling RunStepStatus = \"cancelled\"\n\tRunStepStatusFailed     RunStepStatus = \"failed\"\n\tRunStepStatusCompleted  RunStepStatus = \"completed\"\n\tRunStepStatusExpired    RunStepStatus = \"expired\"\n)\n\ntype RunStepType string\n\nconst (\n\tRunStepTypeMessageCreation RunStepType = \"message_creation\"\n\tRunStepTypeToolCalls       RunStepType = \"tool_calls\"\n)\n\ntype StepDetails struct {\n\tType            RunStepType                 `json:\"type\"`\n\tMessageCreation *StepDetailsMessageCreation `json:\"message_creation,omitempty\"`\n\tToolCalls       []ToolCall                  `json:\"tool_calls,omitempty\"`\n}\n\ntype StepDetailsMessageCreation struct {\n\tMessageID string `json:\"message_id\"`\n}\n\n// RunStepList is a list of steps.\ntype RunStepList struct {\n\tRunSteps []RunStep `json:\"data\"`\n\n\tFirstID string `json:\"first_id\"`\n\tLastID  string `json:\"last_id\"`\n\tHasMore bool   `json:\"has_more\"`\n\n\thttpHeader\n}\n\ntype Pagination struct {\n\tLimit  *int\n\tOrder  *string\n\tAfter  *string\n\tBefore *string\n}\n\n// CreateRun creates a new run.\nfunc (c *Client) CreateRun(\n\tctx context.Context,\n\tthreadID string,\n\trequest RunRequest,\n) (response Run, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs\", threadID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveRun retrieves a run.\nfunc (c *Client) RetrieveRun(\n\tctx context.Context,\n\tthreadID string,\n\trunID string,\n) (response Run, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s\", threadID, runID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tc.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ModifyRun modifies a run.\nfunc (c *Client) ModifyRun(\n\tctx context.Context,\n\tthreadID string,\n\trunID string,\n\trequest RunModifyRequest,\n) (response Run, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s\", threadID, runID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ListRuns lists runs.\nfunc (c *Client) ListRuns(\n\tctx context.Context,\n\tthreadID string,\n\tpagination Pagination,\n) (response RunList, err error) {\n\turlValues := url.Values{}\n\tif pagination.Limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *pagination.Limit))\n\t}\n\tif pagination.Order != nil {\n\t\turlValues.Add(\"order\", *pagination.Order)\n\t}\n\tif pagination.After != nil {\n\t\turlValues.Add(\"after\", *pagination.After)\n\t}\n\tif pagination.Before != nil {\n\t\turlValues.Add(\"before\", *pagination.Before)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs%s\", threadID, encodedValues)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tc.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// SubmitToolOutputs submits tool outputs.\nfunc (c *Client) SubmitToolOutputs(\n\tctx context.Context,\n\tthreadID string,\n\trunID string,\n\trequest SubmitToolOutputsRequest) (response Run, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s/submit_tool_outputs\", threadID, runID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CancelRun cancels a run.\nfunc (c *Client) CancelRun(\n\tctx context.Context,\n\tthreadID string,\n\trunID string) (response Run, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s/cancel\", threadID, runID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CreateThreadAndRun submits tool outputs.\nfunc (c *Client) CreateThreadAndRun(\n\tctx context.Context,\n\trequest CreateThreadAndRunRequest) (response Run, err error) {\n\turlSuffix := \"/threads/runs\"\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveRunStep retrieves a run step.\nfunc (c *Client) RetrieveRunStep(\n\tctx context.Context,\n\tthreadID string,\n\trunID string,\n\tstepID string,\n) (response RunStep, err error) {\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s/steps/%s\", threadID, runID, stepID)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tc.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ListRunSteps lists run steps.\nfunc (c *Client) ListRunSteps(\n\tctx context.Context,\n\tthreadID string,\n\trunID string,\n\tpagination Pagination,\n) (response RunStepList, err error) {\n\turlValues := url.Values{}\n\tif pagination.Limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *pagination.Limit))\n\t}\n\tif pagination.Order != nil {\n\t\turlValues.Add(\"order\", *pagination.Order)\n\t}\n\tif pagination.After != nil {\n\t\turlValues.Add(\"after\", *pagination.After)\n\t}\n\tif pagination.Before != nil {\n\t\turlValues.Add(\"before\", *pagination.Before)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"/threads/%s/runs/%s/steps%s\", threadID, runID, encodedValues)\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\tc.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "run_test.go",
    "content": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n)\n\n// TestAssistant Tests the assistant endpoint of the API using the mocked server.\nfunc TestRun(t *testing.T) {\n\tassistantID := \"asst_abc123\"\n\tthreadID := \"thread_abc123\"\n\trunID := \"run_abc123\"\n\tstepID := \"step_abc123\"\n\tlimit := 20\n\torder := \"desc\"\n\tafter := \"asst_abc122\"\n\tbefore := \"asst_abc124\"\n\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs/\"+runID+\"/steps/\"+stepID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.RunStep{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStepStatusCompleted,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs/\"+runID+\"/steps\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.RunStepList{\n\t\t\t\t\tRunSteps: []openai.RunStep{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:        runID,\n\t\t\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\t\tStatus:    openai.RunStepStatusCompleted,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs/\"+runID+\"/cancel\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusCancelling,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs/\"+runID+\"/submit_tool_outputs\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusCancelling,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs/\"+runID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusQueued,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodPost {\n\t\t\t\tvar request openai.RunModifyRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusQueued,\n\t\t\t\t\tMetadata:  request.Metadata,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID+\"/runs\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.RunRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusQueued,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.RunList{\n\t\t\t\t\tRuns: []openai.Run{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:        runID,\n\t\t\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\t\tStatus:    openai.RunStatusQueued,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/runs\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.CreateThreadAndRunRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Run{\n\t\t\t\t\tID:        runID,\n\t\t\t\t\tObject:    \"run\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tStatus:    openai.RunStatusQueued,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.CreateRun(ctx, threadID, openai.RunRequest{\n\t\tAssistantID: assistantID,\n\t})\n\tchecks.NoError(t, err, \"CreateRun error\")\n\n\t_, err = client.RetrieveRun(ctx, threadID, runID)\n\tchecks.NoError(t, err, \"RetrieveRun error\")\n\n\t_, err = client.ModifyRun(ctx, threadID, runID, openai.RunModifyRequest{\n\t\tMetadata: map[string]any{\n\t\t\t\"key\": \"value\",\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"ModifyRun error\")\n\n\t_, err = client.ListRuns(\n\t\tctx,\n\t\tthreadID,\n\t\topenai.Pagination{\n\t\t\tLimit:  &limit,\n\t\t\tOrder:  &order,\n\t\t\tAfter:  &after,\n\t\t\tBefore: &before,\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"ListRuns error\")\n\n\t_, err = client.SubmitToolOutputs(ctx, threadID, runID,\n\t\topenai.SubmitToolOutputsRequest{})\n\tchecks.NoError(t, err, \"SubmitToolOutputs error\")\n\n\t_, err = client.CancelRun(ctx, threadID, runID)\n\tchecks.NoError(t, err, \"CancelRun error\")\n\n\t_, err = client.CreateThreadAndRun(ctx, openai.CreateThreadAndRunRequest{\n\t\tRunRequest: openai.RunRequest{\n\t\t\tAssistantID: assistantID,\n\t\t},\n\t\tThread: openai.ThreadRequest{\n\t\t\tMessages: []openai.ThreadMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ThreadMessageRoleUser,\n\t\t\t\t\tContent: \"Hello, World!\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateThreadAndRun error\")\n\n\t_, err = client.RetrieveRunStep(ctx, threadID, runID, stepID)\n\tchecks.NoError(t, err, \"RetrieveRunStep error\")\n\n\t_, err = client.ListRunSteps(\n\t\tctx,\n\t\tthreadID,\n\t\trunID,\n\t\topenai.Pagination{\n\t\t\tLimit:  &limit,\n\t\t\tOrder:  &order,\n\t\t\tAfter:  &after,\n\t\t\tBefore: &before,\n\t\t},\n\t)\n\tchecks.NoError(t, err, \"ListRunSteps error\")\n}\n"
  },
  {
    "path": "speech.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\ntype SpeechModel string\n\nconst (\n\tTTSModel1         SpeechModel = \"tts-1\"\n\tTTSModel1HD       SpeechModel = \"tts-1-hd\"\n\tTTSModelCanary    SpeechModel = \"canary-tts\"\n\tTTSModelGPT4oMini SpeechModel = \"gpt-4o-mini-tts\"\n)\n\ntype SpeechVoice string\n\nconst (\n\tVoiceAlloy   SpeechVoice = \"alloy\"\n\tVoiceAsh     SpeechVoice = \"ash\"\n\tVoiceBallad  SpeechVoice = \"ballad\"\n\tVoiceCoral   SpeechVoice = \"coral\"\n\tVoiceEcho    SpeechVoice = \"echo\"\n\tVoiceFable   SpeechVoice = \"fable\"\n\tVoiceOnyx    SpeechVoice = \"onyx\"\n\tVoiceNova    SpeechVoice = \"nova\"\n\tVoiceShimmer SpeechVoice = \"shimmer\"\n\tVoiceVerse   SpeechVoice = \"verse\"\n)\n\ntype SpeechResponseFormat string\n\nconst (\n\tSpeechResponseFormatMp3  SpeechResponseFormat = \"mp3\"\n\tSpeechResponseFormatOpus SpeechResponseFormat = \"opus\"\n\tSpeechResponseFormatAac  SpeechResponseFormat = \"aac\"\n\tSpeechResponseFormatFlac SpeechResponseFormat = \"flac\"\n\tSpeechResponseFormatWav  SpeechResponseFormat = \"wav\"\n\tSpeechResponseFormatPcm  SpeechResponseFormat = \"pcm\"\n)\n\ntype CreateSpeechRequest struct {\n\tModel          SpeechModel          `json:\"model\"`\n\tInput          string               `json:\"input\"`\n\tVoice          SpeechVoice          `json:\"voice\"`\n\tInstructions   string               `json:\"instructions,omitempty\"`    // Optional, Doesnt work with tts-1 or tts-1-hd.\n\tResponseFormat SpeechResponseFormat `json:\"response_format,omitempty\"` // Optional, default to mp3\n\tSpeed          float64              `json:\"speed,omitempty\"`           // Optional, default to 1.0\n}\n\nfunc (c *Client) CreateSpeech(ctx context.Context, request CreateSpeechRequest) (response RawResponse, err error) {\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(\"/audio/speech\", withModel(string(request.Model))),\n\t\twithBody(request),\n\t\twithContentType(\"application/json\"),\n\t)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn c.sendRequestRaw(req)\n}\n"
  },
  {
    "path": "speech_test.go",
    "content": "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\"testing\"\n\n\t\"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestSpeechIntegration(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\"/v1/audio/speech\", func(w http.ResponseWriter, r *http.Request) {\n\t\tpath := filepath.Join(t.TempDir(), \"fake.mp3\")\n\t\ttest.CreateTestFile(t, path)\n\n\t\t// audio endpoints only accept POST requests\n\t\tif r.Method != \"POST\" {\n\t\t\thttp.Error(w, \"method not allowed\", http.StatusMethodNotAllowed)\n\t\t\treturn\n\t\t}\n\n\t\tmediaType, _, err := mime.ParseMediaType(r.Header.Get(\"Content-Type\"))\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to parse media type\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tif mediaType != \"application/json\" {\n\t\t\thttp.Error(w, \"request is not json\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t// Parse the JSON body of the request\n\t\tvar params map[string]interface{}\n\t\terr = json.NewDecoder(r.Body).Decode(&params)\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to parse request body\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t// Check if each required field is present in the parsed JSON object\n\t\treqParams := []string{\"model\", \"input\", \"voice\"}\n\t\tfor _, param := range reqParams {\n\t\t\t_, ok := params[param]\n\t\t\tif !ok {\n\t\t\t\thttp.Error(w, fmt.Sprintf(\"no %s in params\", param), http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// read audio file content\n\t\taudioFile, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read audio file\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\t// write audio file content to response\n\t\tw.Header().Set(\"Content-Type\", \"audio/mpeg\")\n\t\tw.Header().Set(\"Transfer-Encoding\", \"chunked\")\n\t\tw.Header().Set(\"Connection\", \"keep-alive\")\n\t\t_, err = w.Write(audioFile)\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to write body\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t})\n\n\tt.Run(\"happy path\", func(t *testing.T) {\n\t\tres, err := client.CreateSpeech(context.Background(), openai.CreateSpeechRequest{\n\t\t\tModel: openai.TTSModel1,\n\t\t\tInput: \"Hello!\",\n\t\t\tVoice: openai.VoiceAlloy,\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateSpeech error\")\n\t\tdefer res.Close()\n\n\t\tbuf, err := io.ReadAll(res)\n\t\tchecks.NoError(t, err, \"ReadAll error\")\n\n\t\t// save buf to file as mp3\n\t\terr = os.WriteFile(\"test.mp3\", buf, 0644)\n\t\tchecks.NoError(t, err, \"Create error\")\n\t})\n}\n"
  },
  {
    "path": "stream.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n)\n\nvar (\n\tErrTooManyEmptyStreamMessages = errors.New(\"stream has sent too many empty messages\")\n)\n\ntype CompletionStream struct {\n\t*streamReader[CompletionResponse]\n}\n\n// CreateCompletionStream — API call to create a completion w/ streaming\n// support. It sets whether to stream back partial progress. If set, tokens will be\n// sent as data-only server-sent events as they become available, with the\n// stream terminated by a data: [DONE] message.\nfunc (c *Client) CreateCompletionStream(\n\tctx context.Context,\n\trequest CompletionRequest,\n) (stream *CompletionStream, err error) {\n\turlSuffix := \"/completions\"\n\tif !checkEndpointSupportsModel(urlSuffix, request.Model) {\n\t\terr = ErrCompletionUnsupportedModel\n\t\treturn\n\t}\n\n\tif !checkPromptType(request.Prompt) {\n\t\terr = ErrCompletionRequestPromptTypeNotSupported\n\t\treturn\n\t}\n\n\trequest.Stream = true\n\treq, err := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(urlSuffix, withModel(request.Model)),\n\t\twithBody(request),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := sendRequestStream[CompletionResponse](c, req)\n\tif err != nil {\n\t\treturn\n\t}\n\tstream = &CompletionStream{\n\t\tstreamReader: resp,\n\t}\n\treturn\n}\n"
  },
  {
    "path": "stream_reader.go",
    "content": "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/internal\"\n)\n\nvar (\n\theaderData  = regexp.MustCompile(`^data:\\s*`)\n\terrorPrefix = regexp.MustCompile(`^data:\\s*{\"error\":`)\n)\n\ntype streamable interface {\n\tChatCompletionStreamResponse | CompletionResponse\n}\n\ntype streamReader[T streamable] struct {\n\temptyMessagesLimit uint\n\tisFinished         bool\n\n\treader         *bufio.Reader\n\tresponse       *http.Response\n\terrAccumulator utils.ErrorAccumulator\n\tunmarshaler    utils.Unmarshaler\n\n\thttpHeader\n}\n\nfunc (stream *streamReader[T]) Recv() (response T, err error) {\n\trawLine, err := stream.RecvRaw()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = stream.unmarshaler.Unmarshal(rawLine, &response)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn response, nil\n}\n\nfunc (stream *streamReader[T]) RecvRaw() ([]byte, error) {\n\tif stream.isFinished {\n\t\treturn nil, io.EOF\n\t}\n\n\treturn stream.processLines()\n}\n\n//nolint:gocognit\nfunc (stream *streamReader[T]) processLines() ([]byte, error) {\n\tvar (\n\t\temptyMessagesCount uint\n\t\thasErrorPrefix     bool\n\t)\n\n\tfor {\n\t\trawLine, readErr := stream.reader.ReadBytes('\\n')\n\t\tif readErr != nil || hasErrorPrefix {\n\t\t\trespErr := stream.unmarshalError()\n\t\t\tif respErr != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error, %w\", respErr.Error)\n\t\t\t}\n\t\t\treturn nil, readErr\n\t\t}\n\n\t\tnoSpaceLine := bytes.TrimSpace(rawLine)\n\t\tif errorPrefix.Match(noSpaceLine) {\n\t\t\thasErrorPrefix = true\n\t\t}\n\t\tif !headerData.Match(noSpaceLine) || hasErrorPrefix {\n\t\t\tif hasErrorPrefix {\n\t\t\t\tnoSpaceLine = headerData.ReplaceAll(noSpaceLine, nil)\n\t\t\t}\n\t\t\twriteErr := stream.errAccumulator.Write(noSpaceLine)\n\t\t\tif writeErr != nil {\n\t\t\t\treturn nil, writeErr\n\t\t\t}\n\t\t\temptyMessagesCount++\n\t\t\tif emptyMessagesCount > stream.emptyMessagesLimit {\n\t\t\t\treturn nil, ErrTooManyEmptyStreamMessages\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tnoPrefixLine := headerData.ReplaceAll(noSpaceLine, nil)\n\t\tif string(noPrefixLine) == \"[DONE]\" {\n\t\t\tstream.isFinished = true\n\t\t\treturn nil, io.EOF\n\t\t}\n\n\t\treturn noPrefixLine, nil\n\t}\n}\n\nfunc (stream *streamReader[T]) unmarshalError() (errResp *ErrorResponse) {\n\terrBytes := stream.errAccumulator.Bytes()\n\tif len(errBytes) == 0 {\n\t\treturn\n\t}\n\n\terr := stream.unmarshaler.Unmarshal(errBytes, &errResp)\n\tif err != nil {\n\t\terrResp = nil\n\t}\n\n\treturn\n}\n\nfunc (stream *streamReader[T]) Close() error {\n\treturn stream.response.Body.Close()\n}\n"
  },
  {
    "path": "stream_reader_test.go",
    "content": "package openai //nolint:testpackage // testing private field\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n\n\tutils \"github.com/sashabaranov/go-openai/internal\"\n\t\"github.com/sashabaranov/go-openai/internal/test\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nvar errTestUnmarshalerFailed = errors.New(\"test unmarshaler failed\")\n\ntype failingUnMarshaller struct{}\n\nfunc (*failingUnMarshaller) Unmarshal(_ []byte, _ any) error {\n\treturn errTestUnmarshalerFailed\n}\n\nfunc TestStreamReaderReturnsUnmarshalerErrors(t *testing.T) {\n\tstream := &streamReader[ChatCompletionStreamResponse]{\n\t\terrAccumulator: utils.NewErrorAccumulator(),\n\t\tunmarshaler:    &failingUnMarshaller{},\n\t}\n\n\trespErr := stream.unmarshalError()\n\tif respErr != nil {\n\t\tt.Fatalf(\"Did not return nil with empty buffer: %v\", respErr)\n\t}\n\n\terr := stream.errAccumulator.Write([]byte(\"{\"))\n\tif err != nil {\n\t\tt.Fatalf(\"%+v\", err)\n\t}\n\n\trespErr = stream.unmarshalError()\n\tif respErr != nil {\n\t\tt.Fatalf(\"Did not return nil when unmarshaler failed: %v\", respErr)\n\t}\n}\n\nfunc TestStreamReaderReturnsErrTooManyEmptyStreamMessages(t *testing.T) {\n\tstream := &streamReader[ChatCompletionStreamResponse]{\n\t\temptyMessagesLimit: 3,\n\t\treader:             bufio.NewReader(bytes.NewReader([]byte(\"\\n\\n\\n\\n\"))),\n\t\terrAccumulator:     utils.NewErrorAccumulator(),\n\t\tunmarshaler:        &utils.JSONUnmarshaler{},\n\t}\n\t_, err := stream.Recv()\n\tchecks.ErrorIs(t, err, ErrTooManyEmptyStreamMessages, \"Did not return error when recv failed\", err.Error())\n}\n\nfunc TestStreamReaderReturnsErrTestErrorAccumulatorWriteFailed(t *testing.T) {\n\tstream := &streamReader[ChatCompletionStreamResponse]{\n\t\treader: bufio.NewReader(bytes.NewReader([]byte(\"\\n\"))),\n\t\terrAccumulator: &utils.DefaultErrorAccumulator{\n\t\t\tBuffer: &test.FailingErrorBuffer{},\n\t\t},\n\t\tunmarshaler: &utils.JSONUnmarshaler{},\n\t}\n\t_, err := stream.Recv()\n\tchecks.ErrorIs(t, err, test.ErrTestErrorAccumulatorWriteFailed, \"Did not return error when write failed\", err.Error())\n}\n\nfunc TestStreamReaderRecvRaw(t *testing.T) {\n\tstream := &streamReader[ChatCompletionStreamResponse]{\n\t\treader: bufio.NewReader(bytes.NewReader([]byte(\"data: {\\\"key\\\": \\\"value\\\"}\\n\"))),\n\t}\n\trawLine, err := stream.RecvRaw()\n\tif err != nil {\n\t\tt.Fatalf(\"Did not return raw line: %v\", err)\n\t}\n\tif !bytes.Equal(rawLine, []byte(\"{\\\"key\\\": \\\"value\\\"}\")) {\n\t\tt.Fatalf(\"Did not return raw line: %v\", string(rawLine))\n\t}\n}\n"
  },
  {
    "path": "stream_test.go",
    "content": "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.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\nfunc TestCompletionsStreamWrongModel(t *testing.T) {\n\tconfig := openai.DefaultConfig(\"whatever\")\n\tconfig.BaseURL = \"http://localhost/v1\"\n\tclient := openai.NewClientWithConfig(config)\n\n\t_, err := client.CreateCompletionStream(\n\t\tcontext.Background(),\n\t\topenai.CompletionRequest{\n\t\t\tMaxTokens: 5,\n\t\t\tModel:     openai.GPT3Dot5Turbo,\n\t\t},\n\t)\n\tif !errors.Is(err, openai.ErrCompletionUnsupportedModel) {\n\t\tt.Fatalf(\"CreateCompletion should return ErrCompletionUnsupportedModel, but returned: %v\", err)\n\t}\n}\n\nfunc TestCreateCompletionStream(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata := `{\"id\":\"1\",\"object\":\"completion\",\"created\":1598069254,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response1\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata = `{\"id\":\"2\",\"object\":\"completion\",\"created\":1598069255,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response2\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: done\\n\")...)\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     \"text-davinci-002\",\n\t\tMaxTokens: 10,\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\texpectedResponses := []openai.CompletionResponse{\n\t\t{\n\t\t\tID:      \"1\",\n\t\t\tObject:  \"completion\",\n\t\t\tCreated: 1598069254,\n\t\t\tModel:   \"text-davinci-002\",\n\t\t\tChoices: []openai.CompletionChoice{{Text: \"response1\", FinishReason: \"max_tokens\"}},\n\t\t},\n\t\t{\n\t\t\tID:      \"2\",\n\t\t\tObject:  \"completion\",\n\t\t\tCreated: 1598069255,\n\t\t\tModel:   \"text-davinci-002\",\n\t\t\tChoices: []openai.CompletionChoice{{Text: \"response2\", FinishReason: \"max_tokens\"}},\n\t\t},\n\t}\n\n\tfor ix, expectedResponse := range expectedResponses {\n\t\treceivedResponse, streamErr := stream.Recv()\n\t\tif streamErr != nil {\n\t\t\tt.Errorf(\"stream.Recv() failed: %v\", streamErr)\n\t\t}\n\t\tif !compareResponses(expectedResponse, receivedResponse) {\n\t\t\tt.Errorf(\"Stream response %v is %v, expected %v\", ix, receivedResponse, expectedResponse)\n\t\t}\n\t}\n\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF in the end: %v\", streamErr)\n\t}\n\n\t_, streamErr = stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"stream.Recv() did not return EOF when the stream is finished: %v\", streamErr)\n\t}\n}\n\nfunc TestCreateCompletionStreamError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataStr := []string{\n\t\t\t`{`,\n\t\t\t`\"error\": {`,\n\t\t\t`\"message\": \"Incorrect API key provided: sk-***************************************\",`,\n\t\t\t`\"type\": \"invalid_request_error\",`,\n\t\t\t`\"param\": null,`,\n\t\t\t`\"code\": \"invalid_api_key\"`,\n\t\t\t`}`,\n\t\t\t`}`,\n\t\t}\n\t\tfor _, str := range dataStr {\n\t\t\tdataBytes = append(dataBytes, []byte(str+\"\\n\")...)\n\t\t}\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3TextDavinci003,\n\t\tPrompt:    \"Hello!\",\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, streamErr := stream.Recv()\n\tchecks.HasError(t, streamErr, \"stream.Recv() did not return error\")\n\n\tvar apiErr *openai.APIError\n\tif !errors.As(streamErr, &apiErr) {\n\t\tt.Errorf(\"stream.Recv() did not return APIError\")\n\t}\n\tt.Logf(\"%+v\\n\", apiErr)\n}\n\nfunc TestCreateCompletionStreamRateLimitError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(429)\n\n\t\t// Send test responses\n\t\tdataBytes := []byte(`{\"error\":{` +\n\t\t\t`\"message\": \"You are sending requests too quickly.\",` +\n\t\t\t`\"type\":\"rate_limit_reached\",` +\n\t\t\t`\"param\":null,` +\n\t\t\t`\"code\":\"rate_limit_reached\"}}`)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tvar apiErr *openai.APIError\n\t_, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tMaxTokens: 5,\n\t\tModel:     openai.GPT3Babbage002,\n\t\tPrompt:    \"Hello!\",\n\t\tStream:    true,\n\t})\n\tif !errors.As(err, &apiErr) {\n\t\tt.Errorf(\"TestCreateCompletionStreamRateLimitError did not return APIError\")\n\t}\n\tt.Logf(\"%+v\\n\", apiErr)\n}\n\nfunc TestCreateCompletionStreamTooManyEmptyStreamMessagesError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata := `{\"id\":\"1\",\"object\":\"completion\",\"created\":1598069254,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response1\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\t// Totally 301 empty messages (300 is the limit)\n\t\tfor i := 0; i < 299; i++ {\n\t\t\tdataBytes = append(dataBytes, '\\n')\n\t\t}\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata = `{\"id\":\"2\",\"object\":\"completion\",\"created\":1598069255,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response2\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: done\\n\")...)\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     \"text-davinci-002\",\n\t\tMaxTokens: 10,\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, _ = stream.Recv()\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, openai.ErrTooManyEmptyStreamMessages) {\n\t\tt.Errorf(\"TestCreateCompletionStreamTooManyEmptyStreamMessagesError did not return ErrTooManyEmptyStreamMessages\")\n\t}\n}\n\nfunc TestCreateCompletionStreamUnexpectedTerminatedError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata := `{\"id\":\"1\",\"object\":\"completion\",\"created\":1598069254,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response1\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\t// Stream is terminated without sending \"done\" message\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     \"text-davinci-002\",\n\t\tMaxTokens: 10,\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, _ = stream.Recv()\n\t_, streamErr := stream.Recv()\n\tif !errors.Is(streamErr, io.EOF) {\n\t\tt.Errorf(\"TestCreateCompletionStreamUnexpectedTerminatedError did not return io.EOF\")\n\t}\n}\n\nfunc TestCreateCompletionStreamBrokenJSONError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/event-stream\")\n\n\t\t// Send test responses\n\t\tdataBytes := []byte{}\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\t//nolint:lll\n\t\tdata := `{\"id\":\"1\",\"object\":\"completion\",\"created\":1598069254,\"model\":\"text-davinci-002\",\"choices\":[{\"text\":\"response1\",\"finish_reason\":\"max_tokens\"}]}`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\t// Send broken json\n\t\tdataBytes = append(dataBytes, []byte(\"event: message\\n\")...)\n\t\tdata = `{\"id\":\"2\",\"object\":\"completion\",\"created\":1598069255,\"model\":`\n\t\tdataBytes = append(dataBytes, []byte(\"data: \"+data+\"\\n\\n\")...)\n\n\t\tdataBytes = append(dataBytes, []byte(\"event: done\\n\")...)\n\t\tdataBytes = append(dataBytes, []byte(\"data: [DONE]\\n\\n\")...)\n\n\t\t_, err := w.Write(dataBytes)\n\t\tchecks.NoError(t, err, \"Write error\")\n\t})\n\n\tstream, err := client.CreateCompletionStream(context.Background(), openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     \"text-davinci-002\",\n\t\tMaxTokens: 10,\n\t\tStream:    true,\n\t})\n\tchecks.NoError(t, err, \"CreateCompletionStream returned error\")\n\tdefer stream.Close()\n\n\t_, _ = stream.Recv()\n\t_, streamErr := stream.Recv()\n\tvar syntaxError *json.SyntaxError\n\tif !errors.As(streamErr, &syntaxError) {\n\t\tt.Errorf(\"TestCreateCompletionStreamBrokenJSONError did not return json.SyntaxError\")\n\t}\n}\n\nfunc TestCreateCompletionStreamReturnTimeoutError(t *testing.T) {\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\tserver.RegisterHandler(\"/v1/completions\", func(http.ResponseWriter, *http.Request) {\n\t\ttime.Sleep(10 * time.Nanosecond)\n\t})\n\tctx := context.Background()\n\tctx, cancel := context.WithTimeout(ctx, time.Nanosecond)\n\tdefer cancel()\n\n\t_, err := client.CreateCompletionStream(ctx, openai.CompletionRequest{\n\t\tPrompt:    \"Ex falso quodlibet\",\n\t\tModel:     \"text-davinci-002\",\n\t\tMaxTokens: 10,\n\t\tStream:    true,\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"Did not return error\")\n\t}\n\tif !os.IsTimeout(err) {\n\t\tt.Fatal(\"Did not return timeout error\")\n\t}\n}\n\n// Helper funcs.\nfunc compareResponses(r1, r2 openai.CompletionResponse) bool {\n\tif r1.ID != r2.ID || r1.Object != r2.Object || r1.Created != r2.Created || r1.Model != r2.Model {\n\t\treturn false\n\t}\n\tif len(r1.Choices) != len(r2.Choices) {\n\t\treturn false\n\t}\n\tfor i := range r1.Choices {\n\t\tif !compareResponseChoices(r1.Choices[i], r2.Choices[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc compareResponseChoices(c1, c2 openai.CompletionChoice) bool {\n\tif c1.Text != c2.Text || c1.FinishReason != c2.FinishReason {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "thread.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\nconst (\n\tthreadsSuffix = \"/threads\"\n)\n\ntype Thread struct {\n\tID            string         `json:\"id\"`\n\tObject        string         `json:\"object\"`\n\tCreatedAt     int64          `json:\"created_at\"`\n\tMetadata      map[string]any `json:\"metadata\"`\n\tToolResources ToolResources  `json:\"tool_resources,omitempty\"`\n\n\thttpHeader\n}\n\ntype ThreadRequest struct {\n\tMessages      []ThreadMessage       `json:\"messages,omitempty\"`\n\tMetadata      map[string]any        `json:\"metadata,omitempty\"`\n\tToolResources *ToolResourcesRequest `json:\"tool_resources,omitempty\"`\n}\n\ntype ToolResources struct {\n\tCodeInterpreter *CodeInterpreterToolResources `json:\"code_interpreter,omitempty\"`\n\tFileSearch      *FileSearchToolResources      `json:\"file_search,omitempty\"`\n}\n\ntype CodeInterpreterToolResources struct {\n\tFileIDs []string `json:\"file_ids,omitempty\"`\n}\n\ntype FileSearchToolResources struct {\n\tVectorStoreIDs []string `json:\"vector_store_ids,omitempty\"`\n}\n\ntype ToolResourcesRequest struct {\n\tCodeInterpreter *CodeInterpreterToolResourcesRequest `json:\"code_interpreter,omitempty\"`\n\tFileSearch      *FileSearchToolResourcesRequest      `json:\"file_search,omitempty\"`\n}\n\ntype CodeInterpreterToolResourcesRequest struct {\n\tFileIDs []string `json:\"file_ids,omitempty\"`\n}\n\ntype FileSearchToolResourcesRequest struct {\n\tVectorStoreIDs []string                   `json:\"vector_store_ids,omitempty\"`\n\tVectorStores   []VectorStoreToolResources `json:\"vector_stores,omitempty\"`\n}\n\ntype VectorStoreToolResources struct {\n\tFileIDs          []string          `json:\"file_ids,omitempty\"`\n\tChunkingStrategy *ChunkingStrategy `json:\"chunking_strategy,omitempty\"`\n\tMetadata         map[string]any    `json:\"metadata,omitempty\"`\n}\n\ntype ChunkingStrategy struct {\n\tType   ChunkingStrategyType    `json:\"type\"`\n\tStatic *StaticChunkingStrategy `json:\"static,omitempty\"`\n}\n\ntype StaticChunkingStrategy struct {\n\tMaxChunkSizeTokens int `json:\"max_chunk_size_tokens\"`\n\tChunkOverlapTokens int `json:\"chunk_overlap_tokens\"`\n}\n\ntype ChunkingStrategyType string\n\nconst (\n\tChunkingStrategyTypeAuto   ChunkingStrategyType = \"auto\"\n\tChunkingStrategyTypeStatic ChunkingStrategyType = \"static\"\n)\n\ntype ModifyThreadRequest struct {\n\tMetadata      map[string]any `json:\"metadata\"`\n\tToolResources *ToolResources `json:\"tool_resources,omitempty\"`\n}\n\ntype ThreadMessageRole string\n\nconst (\n\tThreadMessageRoleAssistant ThreadMessageRole = \"assistant\"\n\tThreadMessageRoleUser      ThreadMessageRole = \"user\"\n)\n\ntype ThreadMessage struct {\n\tRole        ThreadMessageRole  `json:\"role\"`\n\tContent     string             `json:\"content\"`\n\tFileIDs     []string           `json:\"file_ids,omitempty\"`\n\tAttachments []ThreadAttachment `json:\"attachments,omitempty\"`\n\tMetadata    map[string]any     `json:\"metadata,omitempty\"`\n}\n\ntype ThreadAttachment struct {\n\tFileID string                 `json:\"file_id\"`\n\tTools  []ThreadAttachmentTool `json:\"tools\"`\n}\n\ntype ThreadAttachmentTool struct {\n\tType string `json:\"type\"`\n}\n\ntype ThreadDeleteResponse struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\n// CreateThread creates a new thread.\nfunc (c *Client) CreateThread(ctx context.Context, request ThreadRequest) (response Thread, err error) {\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(threadsSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveThread retrieves a thread.\nfunc (c *Client) RetrieveThread(ctx context.Context, threadID string) (response Thread, err error) {\n\turlSuffix := threadsSuffix + \"/\" + threadID\n\treq, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ModifyThread modifies a thread.\nfunc (c *Client) ModifyThread(\n\tctx context.Context,\n\tthreadID string,\n\trequest ModifyThreadRequest,\n) (response Thread, err error) {\n\turlSuffix := threadsSuffix + \"/\" + threadID\n\treq, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// DeleteThread deletes a thread.\nfunc (c *Client) DeleteThread(\n\tctx context.Context,\n\tthreadID string,\n) (response ThreadDeleteResponse, err error) {\n\turlSuffix := threadsSuffix + \"/\" + threadID\n\treq, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "thread_test.go",
    "content": "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/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n)\n\n// TestThread Tests the thread endpoint of the API using the mocked server.\nfunc TestThread(t *testing.T) {\n\tthreadID := \"thread_abc123\"\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads/\"+threadID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodPost:\n\t\t\t\tvar request openai.ThreadRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\t\"id\": \"thread_abc123\",\n\t\t\t\t\t\"object\": \"thread.deleted\",\n\t\t\t\t\t\"deleted\": true\n\t\t\t\t\t}`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/threads\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.ModifyThreadRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tMetadata:  request.Metadata,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.CreateThread(ctx, openai.ThreadRequest{\n\t\tMessages: []openai.ThreadMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ThreadMessageRoleUser,\n\t\t\t\tContent: \"Hello, World!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateThread error\")\n\n\t_, err = client.RetrieveThread(ctx, threadID)\n\tchecks.NoError(t, err, \"RetrieveThread error\")\n\n\t_, err = client.ModifyThread(ctx, threadID, openai.ModifyThreadRequest{\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"key\": \"value\",\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"ModifyThread error\")\n\n\t_, err = client.DeleteThread(ctx, threadID)\n\tchecks.NoError(t, err, \"DeleteThread error\")\n}\n\n// TestAzureThread Tests the thread endpoint of the API using the Azure mocked server.\nfunc TestAzureThread(t *testing.T) {\n\tthreadID := \"thread_abc123\"\n\tclient, server, teardown := setupAzureTestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/openai/threads/\"+threadID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodPost:\n\t\t\t\tvar request openai.ThreadRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\t\"id\": \"thread_abc123\",\n\t\t\t\t\t\"object\": \"thread.deleted\",\n\t\t\t\t\t\"deleted\": true\n\t\t\t\t\t}`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/openai/threads\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.ModifyThreadRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.Thread{\n\t\t\t\t\tID:        threadID,\n\t\t\t\t\tObject:    \"thread\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tMetadata:  request.Metadata,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\t_, err := client.CreateThread(ctx, openai.ThreadRequest{\n\t\tMessages: []openai.ThreadMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ThreadMessageRoleUser,\n\t\t\t\tContent: \"Hello, World!\",\n\t\t\t},\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"CreateThread error\")\n\n\t_, err = client.RetrieveThread(ctx, threadID)\n\tchecks.NoError(t, err, \"RetrieveThread error\")\n\n\t_, err = client.ModifyThread(ctx, threadID, openai.ModifyThreadRequest{\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"key\": \"value\",\n\t\t},\n\t})\n\tchecks.NoError(t, err, \"ModifyThread error\")\n\n\t_, err = client.DeleteThread(ctx, threadID)\n\tchecks.NoError(t, err, \"DeleteThread error\")\n}\n"
  },
  {
    "path": "vector_store.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nconst (\n\tvectorStoresSuffix            = \"/vector_stores\"\n\tvectorStoresFilesSuffix       = \"/files\"\n\tvectorStoresFileBatchesSuffix = \"/file_batches\"\n)\n\ntype VectorStoreFileCount struct {\n\tInProgress int `json:\"in_progress\"`\n\tCompleted  int `json:\"completed\"`\n\tFailed     int `json:\"failed\"`\n\tCancelled  int `json:\"cancelled\"`\n\tTotal      int `json:\"total\"`\n}\n\ntype VectorStore struct {\n\tID           string               `json:\"id\"`\n\tObject       string               `json:\"object\"`\n\tCreatedAt    int64                `json:\"created_at\"`\n\tName         string               `json:\"name\"`\n\tUsageBytes   int                  `json:\"usage_bytes\"`\n\tFileCounts   VectorStoreFileCount `json:\"file_counts\"`\n\tStatus       string               `json:\"status\"`\n\tExpiresAfter *VectorStoreExpires  `json:\"expires_after\"`\n\tExpiresAt    *int                 `json:\"expires_at\"`\n\tMetadata     map[string]any       `json:\"metadata\"`\n\n\thttpHeader\n}\n\ntype VectorStoreExpires struct {\n\tAnchor string `json:\"anchor\"`\n\tDays   int    `json:\"days\"`\n}\n\n// VectorStoreRequest provides the vector store request parameters.\ntype VectorStoreRequest struct {\n\tName         string              `json:\"name,omitempty\"`\n\tFileIDs      []string            `json:\"file_ids,omitempty\"`\n\tExpiresAfter *VectorStoreExpires `json:\"expires_after,omitempty\"`\n\tMetadata     map[string]any      `json:\"metadata,omitempty\"`\n}\n\n// VectorStoresList is a list of vector store.\ntype VectorStoresList struct {\n\tVectorStores []VectorStore `json:\"data\"`\n\tLastID       *string       `json:\"last_id\"`\n\tFirstID      *string       `json:\"first_id\"`\n\tHasMore      bool          `json:\"has_more\"`\n\thttpHeader\n}\n\ntype VectorStoreDeleteResponse struct {\n\tID      string `json:\"id\"`\n\tObject  string `json:\"object\"`\n\tDeleted bool   `json:\"deleted\"`\n\n\thttpHeader\n}\n\ntype VectorStoreFile struct {\n\tID            string `json:\"id\"`\n\tObject        string `json:\"object\"`\n\tCreatedAt     int64  `json:\"created_at\"`\n\tVectorStoreID string `json:\"vector_store_id\"`\n\tUsageBytes    int    `json:\"usage_bytes\"`\n\tStatus        string `json:\"status\"`\n\n\thttpHeader\n}\n\ntype VectorStoreFileRequest struct {\n\tFileID string `json:\"file_id\"`\n}\n\ntype VectorStoreFilesList struct {\n\tVectorStoreFiles []VectorStoreFile `json:\"data\"`\n\tFirstID          *string           `json:\"first_id\"`\n\tLastID           *string           `json:\"last_id\"`\n\tHasMore          bool              `json:\"has_more\"`\n\n\thttpHeader\n}\n\ntype VectorStoreFileBatch struct {\n\tID            string               `json:\"id\"`\n\tObject        string               `json:\"object\"`\n\tCreatedAt     int64                `json:\"created_at\"`\n\tVectorStoreID string               `json:\"vector_store_id\"`\n\tStatus        string               `json:\"status\"`\n\tFileCounts    VectorStoreFileCount `json:\"file_counts\"`\n\n\thttpHeader\n}\n\ntype VectorStoreFileBatchRequest struct {\n\tFileIDs []string `json:\"file_ids\"`\n}\n\n// CreateVectorStore creates a new vector store.\nfunc (c *Client) CreateVectorStore(ctx context.Context, request VectorStoreRequest) (response VectorStore, err error) {\n\treq, _ := c.newRequest(\n\t\tctx,\n\t\thttp.MethodPost,\n\t\tc.fullURL(vectorStoresSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion),\n\t)\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveVectorStore retrieves an vector store.\nfunc (c *Client) RetrieveVectorStore(\n\tctx context.Context,\n\tvectorStoreID string,\n) (response VectorStore, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", vectorStoresSuffix, vectorStoreID)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ModifyVectorStore modifies a vector store.\nfunc (c *Client) ModifyVectorStore(\n\tctx context.Context,\n\tvectorStoreID string,\n\trequest VectorStoreRequest,\n) (response VectorStore, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", vectorStoresSuffix, vectorStoreID)\n\treq, _ := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// DeleteVectorStore deletes an vector store.\nfunc (c *Client) DeleteVectorStore(\n\tctx context.Context,\n\tvectorStoreID string,\n) (response VectorStoreDeleteResponse, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s\", vectorStoresSuffix, vectorStoreID)\n\treq, _ := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ListVectorStores Lists the currently available vector store.\nfunc (c *Client) ListVectorStores(\n\tctx context.Context,\n\tpagination Pagination,\n) (response VectorStoresList, err error) {\n\turlValues := url.Values{}\n\n\tif pagination.After != nil {\n\t\turlValues.Add(\"after\", *pagination.After)\n\t}\n\tif pagination.Order != nil {\n\t\turlValues.Add(\"order\", *pagination.Order)\n\t}\n\tif pagination.Limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *pagination.Limit))\n\t}\n\tif pagination.Before != nil {\n\t\turlValues.Add(\"before\", *pagination.Before)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s%s\", vectorStoresSuffix, encodedValues)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CreateVectorStoreFile creates a new vector store file.\nfunc (c *Client) CreateVectorStoreFile(\n\tctx context.Context,\n\tvectorStoreID string,\n\trequest VectorStoreFileRequest,\n) (response VectorStoreFile, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFilesSuffix)\n\treq, _ := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveVectorStoreFile retrieves a vector store file.\nfunc (c *Client) RetrieveVectorStoreFile(\n\tctx context.Context,\n\tvectorStoreID string,\n\tfileID string,\n) (response VectorStoreFile, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFilesSuffix, fileID)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// DeleteVectorStoreFile deletes an existing file.\nfunc (c *Client) DeleteVectorStoreFile(\n\tctx context.Context,\n\tvectorStoreID string,\n\tfileID string,\n) (err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFilesSuffix, fileID)\n\treq, _ := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, nil)\n\treturn\n}\n\n// ListVectorStoreFiles Lists the currently available files for a vector store.\nfunc (c *Client) ListVectorStoreFiles(\n\tctx context.Context,\n\tvectorStoreID string,\n\tpagination Pagination,\n) (response VectorStoreFilesList, err error) {\n\turlValues := url.Values{}\n\tif pagination.After != nil {\n\t\turlValues.Add(\"after\", *pagination.After)\n\t}\n\tif pagination.Limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *pagination.Limit))\n\t}\n\tif pagination.Before != nil {\n\t\turlValues.Add(\"before\", *pagination.Before)\n\t}\n\tif pagination.Order != nil {\n\t\turlValues.Add(\"order\", *pagination.Order)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s/%s%s%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFilesSuffix, encodedValues)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CreateVectorStoreFileBatch creates a new vector store file batch.\nfunc (c *Client) CreateVectorStoreFileBatch(\n\tctx context.Context,\n\tvectorStoreID string,\n\trequest VectorStoreFileBatchRequest,\n) (response VectorStoreFileBatch, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFileBatchesSuffix)\n\treq, _ := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),\n\t\twithBody(request),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// RetrieveVectorStoreFileBatch retrieves a vector store file batch.\nfunc (c *Client) RetrieveVectorStoreFileBatch(\n\tctx context.Context,\n\tvectorStoreID string,\n\tbatchID string,\n) (response VectorStoreFileBatch, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s\", vectorStoresSuffix, vectorStoreID, vectorStoresFileBatchesSuffix, batchID)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// CancelVectorStoreFileBatch cancel a new vector store file batch.\nfunc (c *Client) CancelVectorStoreFileBatch(\n\tctx context.Context,\n\tvectorStoreID string,\n\tbatchID string,\n) (response VectorStoreFileBatch, err error) {\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s%s\", vectorStoresSuffix,\n\t\tvectorStoreID, vectorStoresFileBatchesSuffix, batchID, \"/cancel\")\n\treq, _ := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n\n// ListVectorStoreFiles Lists the currently available files for a vector store.\nfunc (c *Client) ListVectorStoreFilesInBatch(\n\tctx context.Context,\n\tvectorStoreID string,\n\tbatchID string,\n\tpagination Pagination,\n) (response VectorStoreFilesList, err error) {\n\turlValues := url.Values{}\n\tif pagination.After != nil {\n\t\turlValues.Add(\"after\", *pagination.After)\n\t}\n\tif pagination.Limit != nil {\n\t\turlValues.Add(\"limit\", fmt.Sprintf(\"%d\", *pagination.Limit))\n\t}\n\tif pagination.Before != nil {\n\t\turlValues.Add(\"before\", *pagination.Before)\n\t}\n\tif pagination.Order != nil {\n\t\turlValues.Add(\"order\", *pagination.Order)\n\t}\n\n\tencodedValues := \"\"\n\tif len(urlValues) > 0 {\n\t\tencodedValues = \"?\" + urlValues.Encode()\n\t}\n\n\turlSuffix := fmt.Sprintf(\"%s/%s%s/%s%s%s\", vectorStoresSuffix,\n\t\tvectorStoreID, vectorStoresFileBatchesSuffix, batchID, \"/files\", encodedValues)\n\treq, _ := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix),\n\t\twithBetaAssistantVersion(c.config.AssistantVersion))\n\n\terr = c.sendRequest(req, &response)\n\treturn\n}\n"
  },
  {
    "path": "vector_store_test.go",
    "content": "package openai_test\n\nimport (\n\t\"context\"\n\n\topenai \"github.com/sashabaranov/go-openai\"\n\t\"github.com/sashabaranov/go-openai/internal/test/checks\"\n\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n)\n\n// TestVectorStore Tests the vector store endpoint of the API using the mocked server.\nfunc TestVectorStore(t *testing.T) {\n\tvectorStoreID := \"vs_abc123\"\n\tvectorStoreName := \"TestStore\"\n\tvectorStoreFileID := \"file-wB6RM6wHdA49HfS2DJ9fEyrH\"\n\tvectorStoreFileBatchID := \"vsfb_abc123\"\n\tlimit := 20\n\torder := \"desc\"\n\tafter := \"vs_abc122\"\n\tbefore := \"vs_abc123\"\n\n\tclient, server, teardown := setupOpenAITestServer()\n\tdefer teardown()\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/files/\"+vectorStoreFileID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFile{\n\t\t\t\t\tID:            vectorStoreFileID,\n\t\t\t\t\tObject:        \"vector_store.file\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\tStatus:        \"completed\",\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodDelete {\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\tid: \"file-wB6RM6wHdA49HfS2DJ9fEyrH\",\n\t\t\t\t\tobject: \"vector_store.file.deleted\",\n\t\t\t\t\tdeleted: true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/files\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFilesList{\n\t\t\t\t\tVectorStoreFiles: []openai.VectorStoreFile{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:            vectorStoreFileID,\n\t\t\t\t\t\t\tObject:        \"vector_store.file\",\n\t\t\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodPost {\n\t\t\t\tvar request openai.VectorStoreFileRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFile{\n\t\t\t\t\tID:            request.FileID,\n\t\t\t\t\tObject:        \"vector_store.file\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/file_batches/\"+vectorStoreFileBatchID+\"/files\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFilesList{\n\t\t\t\t\tVectorStoreFiles: []openai.VectorStoreFile{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:            vectorStoreFileID,\n\t\t\t\t\t\t\tObject:        \"vector_store.file\",\n\t\t\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/file_batches/\"+vectorStoreFileBatchID+\"/cancel\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFileBatch{\n\t\t\t\t\tID:            vectorStoreFileBatchID,\n\t\t\t\t\tObject:        \"vector_store.file_batch\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\tStatus:        \"cancelling\",\n\t\t\t\t\tFileCounts: openai.VectorStoreFileCount{\n\t\t\t\t\t\tInProgress: 0,\n\t\t\t\t\t\tCompleted:  1,\n\t\t\t\t\t\tFailed:     0,\n\t\t\t\t\t\tCancelled:  0,\n\t\t\t\t\t\tTotal:      0,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/file_batches/\"+vectorStoreFileBatchID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFileBatch{\n\t\t\t\t\tID:            vectorStoreFileBatchID,\n\t\t\t\t\tObject:        \"vector_store.file_batch\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\tStatus:        \"completed\",\n\t\t\t\t\tFileCounts: openai.VectorStoreFileCount{\n\t\t\t\t\t\tCompleted: 1,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodPost {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFileBatch{\n\t\t\t\t\tID:            vectorStoreFileBatchID,\n\t\t\t\t\tObject:        \"vector_store.file_batch\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\tStatus:        \"cancelling\",\n\t\t\t\t\tFileCounts: openai.VectorStoreFileCount{\n\t\t\t\t\t\tCompleted: 1,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID+\"/file_batches\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.VectorStoreFileBatchRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoreFileBatch{\n\t\t\t\t\tID:            vectorStoreFileBatchID,\n\t\t\t\t\tObject:        \"vector_store.file_batch\",\n\t\t\t\t\tCreatedAt:     1234567890,\n\t\t\t\t\tVectorStoreID: vectorStoreID,\n\t\t\t\t\tStatus:        \"completed\",\n\t\t\t\t\tFileCounts: openai.VectorStoreFileCount{\n\t\t\t\t\t\tInProgress: 0,\n\t\t\t\t\t\tCompleted:  len(request.FileIDs),\n\t\t\t\t\t\tFailed:     0,\n\t\t\t\t\t\tCancelled:  0,\n\t\t\t\t\t\tTotal:      0,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores/\"+vectorStoreID,\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tswitch r.Method {\n\t\t\tcase http.MethodGet:\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStore{\n\t\t\t\t\tID:        vectorStoreID,\n\t\t\t\t\tObject:    \"vector_store\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tName:      vectorStoreName,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodPost:\n\t\t\t\tvar request openai.VectorStore\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStore{\n\t\t\t\t\tID:        vectorStoreID,\n\t\t\t\t\tObject:    \"vector_store\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tName:      request.Name,\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\tcase http.MethodDelete:\n\t\t\t\tfmt.Fprintln(w, `{\n\t\t\t\t\t\"id\": \"vectorstore_abc123\",\n\t\t\t\t\t\"object\": \"vector_store.deleted\",\n\t\t\t\t\t\"deleted\": true\n\t\t\t\t  }`)\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.RegisterHandler(\n\t\t\"/v1/vector_stores\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.Method == http.MethodPost {\n\t\t\t\tvar request openai.VectorStoreRequest\n\t\t\t\terr := json.NewDecoder(r.Body).Decode(&request)\n\t\t\t\tchecks.NoError(t, err, \"Decode error\")\n\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStore{\n\t\t\t\t\tID:        vectorStoreID,\n\t\t\t\t\tObject:    \"vector_store\",\n\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\tName:      request.Name,\n\t\t\t\t\tFileCounts: openai.VectorStoreFileCount{\n\t\t\t\t\t\tInProgress: 0,\n\t\t\t\t\t\tCompleted:  0,\n\t\t\t\t\t\tFailed:     0,\n\t\t\t\t\t\tCancelled:  0,\n\t\t\t\t\t\tTotal:      0,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t} else if r.Method == http.MethodGet {\n\t\t\t\tresBytes, _ := json.Marshal(openai.VectorStoresList{\n\t\t\t\t\tLastID:  &vectorStoreID,\n\t\t\t\t\tFirstID: &vectorStoreID,\n\t\t\t\t\tVectorStores: []openai.VectorStore{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:        vectorStoreID,\n\t\t\t\t\t\t\tObject:    \"vector_store\",\n\t\t\t\t\t\t\tCreatedAt: 1234567890,\n\t\t\t\t\t\t\tName:      vectorStoreName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tfmt.Fprintln(w, string(resBytes))\n\t\t\t}\n\t\t},\n\t)\n\n\tctx := context.Background()\n\n\tt.Run(\"create_vector_store\", func(t *testing.T) {\n\t\t_, err := client.CreateVectorStore(ctx, openai.VectorStoreRequest{\n\t\t\tName: vectorStoreName,\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateVectorStore error\")\n\t})\n\n\tt.Run(\"retrieve_vector_store\", func(t *testing.T) {\n\t\t_, err := client.RetrieveVectorStore(ctx, vectorStoreID)\n\t\tchecks.NoError(t, err, \"RetrieveVectorStore error\")\n\t})\n\n\tt.Run(\"delete_vector_store\", func(t *testing.T) {\n\t\t_, err := client.DeleteVectorStore(ctx, vectorStoreID)\n\t\tchecks.NoError(t, err, \"DeleteVectorStore error\")\n\t})\n\n\tt.Run(\"list_vector_store\", func(t *testing.T) {\n\t\t_, err := client.ListVectorStores(context.TODO(), openai.Pagination{\n\t\t\tLimit:  &limit,\n\t\t\tOrder:  &order,\n\t\t\tAfter:  &after,\n\t\t\tBefore: &before,\n\t\t})\n\t\tchecks.NoError(t, err, \"ListVectorStores error\")\n\t})\n\n\tt.Run(\"create_vector_store_file\", func(t *testing.T) {\n\t\t_, err := client.CreateVectorStoreFile(context.TODO(), vectorStoreID, openai.VectorStoreFileRequest{\n\t\t\tFileID: vectorStoreFileID,\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateVectorStoreFile error\")\n\t})\n\n\tt.Run(\"list_vector_store_files\", func(t *testing.T) {\n\t\t_, err := client.ListVectorStoreFiles(ctx, vectorStoreID, openai.Pagination{\n\t\t\tLimit:  &limit,\n\t\t\tOrder:  &order,\n\t\t\tAfter:  &after,\n\t\t\tBefore: &before,\n\t\t})\n\t\tchecks.NoError(t, err, \"ListVectorStoreFiles error\")\n\t})\n\n\tt.Run(\"retrieve_vector_store_file\", func(t *testing.T) {\n\t\t_, err := client.RetrieveVectorStoreFile(ctx, vectorStoreID, vectorStoreFileID)\n\t\tchecks.NoError(t, err, \"RetrieveVectorStoreFile error\")\n\t})\n\n\tt.Run(\"delete_vector_store_file\", func(t *testing.T) {\n\t\terr := client.DeleteVectorStoreFile(ctx, vectorStoreID, vectorStoreFileID)\n\t\tchecks.NoError(t, err, \"DeleteVectorStoreFile error\")\n\t})\n\n\tt.Run(\"modify_vector_store\", func(t *testing.T) {\n\t\t_, err := client.ModifyVectorStore(ctx, vectorStoreID, openai.VectorStoreRequest{\n\t\t\tName: vectorStoreName,\n\t\t})\n\t\tchecks.NoError(t, err, \"ModifyVectorStore error\")\n\t})\n\n\tt.Run(\"create_vector_store_file_batch\", func(t *testing.T) {\n\t\t_, err := client.CreateVectorStoreFileBatch(ctx, vectorStoreID, openai.VectorStoreFileBatchRequest{\n\t\t\tFileIDs: []string{vectorStoreFileID},\n\t\t})\n\t\tchecks.NoError(t, err, \"CreateVectorStoreFileBatch error\")\n\t})\n\n\tt.Run(\"retrieve_vector_store_file_batch\", func(t *testing.T) {\n\t\t_, err := client.RetrieveVectorStoreFileBatch(ctx, vectorStoreID, vectorStoreFileBatchID)\n\t\tchecks.NoError(t, err, \"RetrieveVectorStoreFileBatch error\")\n\t})\n\n\tt.Run(\"list_vector_store_files_in_batch\", func(t *testing.T) {\n\t\t_, err := client.ListVectorStoreFilesInBatch(\n\t\t\tctx,\n\t\t\tvectorStoreID,\n\t\t\tvectorStoreFileBatchID,\n\t\t\topenai.Pagination{\n\t\t\t\tLimit:  &limit,\n\t\t\t\tOrder:  &order,\n\t\t\t\tAfter:  &after,\n\t\t\t\tBefore: &before,\n\t\t\t})\n\t\tchecks.NoError(t, err, \"ListVectorStoreFilesInBatch error\")\n\t})\n\n\tt.Run(\"cancel_vector_store_file_batch\", func(t *testing.T) {\n\t\t_, err := client.CancelVectorStoreFileBatch(ctx, vectorStoreID, vectorStoreFileBatchID)\n\t\tchecks.NoError(t, err, \"CancelVectorStoreFileBatch error\")\n\t})\n}\n"
  }
]